Skip to content

chore(iast): add more iast tests #13257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
585 changes: 218 additions & 367 deletions ddtrace/appsec/_iast/_ast/iastpatch.c

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions ddtrace/appsec/_iast/_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ def iast_propagation_debug_log(msg, *args, **kwargs):
log.debug("iast::propagation::error::%s", msg, *args, **kwargs)


def iast_propagation_sink_point_debug_log(msg, *args, **kwargs):
log.debug("iast::propagation::sink_point::%s", msg, *args, **kwargs)


def iast_instrumentation_ast_patching_errorr_log(msg):
iast_error(msg, default_prefix="iast::instrumentation::ast_patching::")

Expand Down
5 changes: 4 additions & 1 deletion ddtrace/appsec/_iast/taint_sinks/command_injection.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from ddtrace.settings.asm import config as asm_config

from .._logs import iast_error
from .._logs import iast_propagation_sink_point_debug_log
from .._overhead_control_engine import oce
from ._base import VulnerabilityBase

Expand Down Expand Up @@ -51,6 +52,7 @@ def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None:
try:
if asm_config.is_iast_request_enabled:
if CommandInjection.has_quota():
iast_propagation_sink_point_debug_log("Check command injection sink point")
from .._taint_tracking.aspects import join_aspect

if isinstance(shell_args, (list, tuple)):
Expand All @@ -62,11 +64,12 @@ def _iast_report_cmdi(shell_args: Union[str, List[str]]) -> None:
report_cmdi = shell_args

if report_cmdi:
iast_propagation_sink_point_debug_log("Reporting command injection")
CommandInjection.report(evidence_value=report_cmdi)

# Reports Span Metrics
increment_iast_span_metric(IAST_SPAN_TAGS.TELEMETRY_EXECUTED_SINK, CommandInjection.vulnerability_type)
# Report Telemetry Metrics
_set_metric_iast_executed_sink(CommandInjection.vulnerability_type)
except Exception as e:
iast_error(f"propagation::sink_point::Error in _iast_report_ssrf. {e}")
iast_error(f"propagation::sink_point::Error in _iast_report_cmdi. {e}")
9 changes: 9 additions & 0 deletions tests/appsec/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import copy
import os
import re
import shlex
import subprocess # nosec

from flask import Flask
Expand Down Expand Up @@ -200,6 +201,14 @@ def iast_cmdi_vulnerability():
return resp


@app.route("/iast-cmdi-vulnerability-secure", methods=["GET"])
def view_cmdi_secure():
filename = request.args.get("filename")
subp = subprocess.Popen(args=["ls", "-la", shlex.quote(filename)])
subp.wait()
return Response("OK")


@app.route("/shutdown", methods=["GET"])
def shutdown_view():
tracer._span_aggregator.writer.flush_queue()
Expand Down
232 changes: 223 additions & 9 deletions tests/appsec/iast/_ast/test_ast_patching.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def test_astpatch_source_unchanged(module_name):
def test_should_iast_patch_allow_first_party():
assert iastpatch.should_iast_patch("file_in_my_project.main") == iastpatch.ALLOWED_FIRST_PARTY_ALLOWLIST
assert iastpatch.should_iast_patch("file_in_my_project.print_str") == iastpatch.ALLOWED_FIRST_PARTY_ALLOWLIST
assert iastpatch.should_iast_patch("html") == iastpatch.ALLOWED_FIRST_PARTY_ALLOWLIST


def test_should_iast_patch_allow_user_allowlist():
Expand Down Expand Up @@ -186,15 +187,228 @@ def test_should_not_iast_patch_if_not_in_static_allowlist():
assert iastpatch.should_iast_patch("pip.foo.bar") == iastpatch.DENIED_NOT_FOUND


def test_should_not_iast_patch_if_stdlib():
assert iastpatch.should_iast_patch("base64") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("itertools") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("http") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("os.path") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("os") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("sys.platform") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("sys") == iastpatch.DENIED_BUILTINS_DENYLIST
assert iastpatch.should_iast_patch("sys.my.sub.module") == iastpatch.DENIED_BUILTINS_DENYLIST
@pytest.mark.parametrize(
"module_name",
{
"__future__",
"_ast",
"_compression",
"_thread",
"abc",
"aifc",
"argparse",
"array",
"ast",
"asynchat",
"asyncio",
"asyncore",
"atexit",
"audioop",
"base64",
"bdb",
"binascii",
"bisect",
"builtins",
"bz2",
"cProfile",
"calendar",
"cgi",
"cgitb",
"chunk",
"cmath",
"cmd",
"code",
"codecs",
"codeop",
"collections",
"colorsys",
"compileall",
"concurrent",
"configparser",
"contextlib",
"contextvars",
"copy",
"copyreg",
"crypt",
"csv",
"ctypes",
"curses",
"dataclasses",
"datetime",
"dbm",
"decimal",
"difflib",
"dis",
"distutils",
"doctest",
"email",
"encodings",
"ensurepip",
"enum",
"errno",
"faulthandler",
"fcntl",
"filecmp",
"fileinput",
"fnmatch",
"fractions",
"ftplib",
"functools",
"gc",
"getopt",
"getpass",
"gettext",
"glob",
"graphlib",
"grp",
"gzip",
"hashlib",
"heapq",
"hmac",
"http",
"idlelib",
"imaplib",
"imghdr",
"imp",
"importlib",
"inspect",
"io",
"_io",
"ipaddress",
"itertools",
"json",
"keyword",
"lib2to3",
"linecache",
"locale",
"logging",
"lzma",
"mailbox",
"mailcap",
"marshal",
"math",
"mimetypes",
"mmap",
"modulefinder",
"msilib",
"msvcrt",
"multiprocessing",
"netrc",
"nis",
"nntplib",
"ntpath",
"numbers",
"opcode",
"operator",
"optparse",
"os",
"os.path",
"ossaudiodev",
"pathlib",
"pdb",
"pickle",
"pickletools",
"pipes",
"pkgutil",
"platform",
"plistlib",
"poplib",
"posix",
"posixpath",
"pprint",
"profile",
"pstats",
"pty",
"pwd",
"py_compile",
"pyclbr",
"pydoc",
"queue",
"quopri",
"random",
"re",
"readline",
"reprlib",
"resource",
"rlcompleter",
"runpy",
"sched",
"secrets",
"select",
"selectors",
"shelve",
"shutil",
"signal",
"site",
"smtpd",
"smtplib",
"sndhdr",
"socket",
"socketserver",
"spwd",
"sqlite3",
"sre",
"sre_compile",
"sre_constants",
"sre_parse",
"ssl",
"stat",
"statistics",
"string",
"stringprep",
"struct",
"subprocess",
"sunau",
"symtable",
"sys",
"sysconfig",
"syslog",
"tabnanny",
"tarfile",
"telnetlib",
"tempfile",
"termios",
"test",
"textwrap",
"threading",
"time",
"timeit",
"tkinter",
"token",
"tokenize",
"tomllib",
"trace",
"traceback",
"tracemalloc",
"tty",
"turtle",
"turtledemo",
"types",
"typing",
"unicodedata",
"unittest",
"uu",
"uuid",
"venv",
"warnings",
"wave",
"weakref",
"webbrowser",
"winreg",
"winsound",
"wsgiref",
"xdrlib",
"xml",
"xmlrpc",
"zipapp",
"zipfile",
"zipimport",
"zlib",
"zoneinfo",
},
)
def test_should_not_iast_patch_if_stdlib(module_name):
assert iastpatch.should_iast_patch(module_name) == iastpatch.DENIED_BUILTINS_DENYLIST


def test_module_path_none(caplog):
Expand Down
12 changes: 12 additions & 0 deletions tests/appsec/iast/aspects/test_split_aspect.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import logging
import sys

from hypothesis import given
from hypothesis.strategies import one_of
import pytest

from ddtrace.appsec._iast._taint_tracking import OriginType
Expand All @@ -15,6 +17,7 @@
from ddtrace.appsec._iast._taint_tracking._context import reset_context
from ddtrace.appsec._iast._taint_tracking._taint_objects import taint_pyobject
from tests.appsec.iast.aspects.test_aspect_helpers import _build_sample_range
from tests.appsec.iast.iast_utils import non_empty_text
from tests.utils import override_global_config


Expand All @@ -23,6 +26,15 @@ def wrap_somesplit(func, *args, **kwargs):
return func(None, 0, *args, **kwargs)


@given(one_of(non_empty_text))
def test_aspect_split(text):
text_1 = text
text_2 = text * 3
s = text_1 + " " + text_2
res = wrap_somesplit(_aspect_split, s)
assert res == s.split()


# These tests are simple ones testing the calls and replacements since most of the
# actual testing is in test_aspect_helpers' test for set_ranges_on_splitted which these
# functions call internally.
Expand Down
Loading
Loading