2026-09-04 13:57:33 +02:00
|
|
|
version = "1"
|
|
|
|
|
language = "python"
|
|
|
|
|
|
|
|
|
|
[[source]]
|
|
|
|
|
id = "request"
|
|
|
|
|
patterns = [
|
|
|
|
|
"request.args",
|
|
|
|
|
"request.form",
|
|
|
|
|
"request.data",
|
|
|
|
|
"request.json",
|
|
|
|
|
"request.cookies",
|
|
|
|
|
"request.values",
|
|
|
|
|
"request.get_json",
|
|
|
|
|
"request.headers",
|
|
|
|
|
"request.files",
|
|
|
|
|
"request.get",
|
|
|
|
|
]
|
|
|
|
|
|
2026-09-25 12:37:22 +02:00
|
|
|
# Django and Django REST framework. The source vocabulary above is Flask's alone, so on a Django project no
|
|
|
|
|
# flow could begin anywhere: OpenCVE's notification test POSTs to a URL read from `form.cleaned_data`, and the
|
|
|
|
|
# scan answered the file and found nothing. `cleaned_data` is what a form bound to the request hands back --
|
|
|
|
|
# validated for type, not for trust.
|
|
|
|
|
[[source]]
|
|
|
|
|
id = "django-request"
|
|
|
|
|
patterns = [
|
|
|
|
|
"request.GET",
|
|
|
|
|
"request.POST",
|
|
|
|
|
"request.body",
|
|
|
|
|
"request.COOKIES",
|
|
|
|
|
"request.FILES",
|
|
|
|
|
"request.META",
|
|
|
|
|
"request.query_params",
|
|
|
|
|
"cleaned_data",
|
|
|
|
|
]
|
|
|
|
|
|
2026-09-04 13:57:33 +02:00
|
|
|
[[source]]
|
|
|
|
|
id = "argv"
|
|
|
|
|
patterns = ["sys.argv"]
|
|
|
|
|
|
|
|
|
|
[[source]]
|
|
|
|
|
id = "stdin"
|
|
|
|
|
patterns = ["sys.stdin", "stdin.read"]
|
|
|
|
|
|
|
|
|
|
[[source]]
|
|
|
|
|
id = "input"
|
|
|
|
|
patterns = ["input("]
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "eval"
|
|
|
|
|
cwe = "CWE-95"
|
|
|
|
|
calls = ["eval", "exec"]
|
|
|
|
|
rule_ids = ["python-unsafe-eval"]
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "pickle.loads"
|
|
|
|
|
cwe = "CWE-502"
|
|
|
|
|
calls = ["pickle.loads", "pickle.load", "yaml.load", "marshal.loads", "cPickle.loads"]
|
|
|
|
|
rule_ids = ["python-unsafe-deserialization"]
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "execute"
|
|
|
|
|
cwe = "CWE-89"
|
|
|
|
|
calls = ["execute", "executemany"]
|
|
|
|
|
rule_ids = ["python-sql-injection"]
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "os.system"
|
|
|
|
|
cwe = "CWE-78"
|
|
|
|
|
calls = ["os.system", "os.popen", "subprocess.run", "subprocess.call", "subprocess.Popen", "subprocess.check_output"]
|
|
|
|
|
rule_ids = ["python-os-command", "python-shell-true"]
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "hashlib"
|
|
|
|
|
cwe = "CWE-327"
|
|
|
|
|
calls = ["hashlib.md5", "hashlib.sha1", "hashlib.new"]
|
|
|
|
|
rule_ids = ["python-weak-hash"]
|
|
|
|
|
weak_literals = ["md5", "sha1", "MD5", "SHA1", "sha-1"]
|
|
|
|
|
|
|
|
|
|
[[sanitizer]]
|
|
|
|
|
id = "ast.literal_eval"
|
|
|
|
|
calls = ["ast.literal_eval"]
|
|
|
|
|
|
|
|
|
|
[[sanitizer]]
|
|
|
|
|
id = "yaml.safe_load"
|
|
|
|
|
calls = ["yaml.safe_load", "safe_load"]
|
|
|
|
|
|
|
|
|
|
[[sanitizer]]
|
|
|
|
|
id = "parameterized_execute"
|
|
|
|
|
calls = ["execute", "executemany"]
|
|
|
|
|
parameterized = true
|
2026-09-08 14:45:12 +02:00
|
|
|
|
|
|
|
|
# --- model-grounded-detection task 4.2 -------------------------------------------------------------------
|
|
|
|
|
# Sinks for the four families that previously had no fact routing to them by CWE, plus the ORM raw-SQL forms
|
|
|
|
|
# the Joern go/no-go measured as misses (11 of 23). Every entry below is a documented dangerous API of the
|
|
|
|
|
# named library, not an inferred pattern; the provenance is on each entry so a maintainer can review the data
|
|
|
|
|
# as data. `rule_ids` is left empty where no pattern rule exists: these facts feed the model layer, and a
|
|
|
|
|
# sink fact is not a detection rule.
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
# CWE-89. Django's documented raw-SQL escape hatches (docs: "Performing raw SQL queries") and the
|
|
|
|
|
# SQLAlchemy/peewee text forms. These were the single largest miss class in the go/no-go.
|
|
|
|
|
id = "orm.raw"
|
|
|
|
|
cwe = "CWE-89"
|
|
|
|
|
calls = ["objects.raw", "raw", "RawSQL", "extra", "text", "from_statement"]
|
|
|
|
|
rule_ids = []
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
# CWE-22. Filesystem entry points that accept a caller-supplied path: the stdlib openers plus Flask's
|
|
|
|
|
# send_file/send_from_directory and Django's FileResponse.
|
|
|
|
|
id = "path.open"
|
|
|
|
|
cwe = "CWE-22"
|
|
|
|
|
calls = ["open", "send_file", "send_from_directory", "FileResponse", "os.remove", "os.unlink", "shutil.copy", "shutil.move", "shutil.rmtree"]
|
|
|
|
|
rule_ids = []
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
# CWE-79. The documented "this string is trusted HTML" escapes: Jinja/Flask render_template_string and
|
|
|
|
|
# Markup, Django's mark_safe and format_html. Rendering a template *file* is not a sink; rendering a
|
|
|
|
|
# caller-supplied *string* is.
|
|
|
|
|
id = "html.unescaped"
|
|
|
|
|
cwe = "CWE-79"
|
|
|
|
|
calls = ["render_template_string", "Markup", "mark_safe", "format_html", "HttpResponse"]
|
|
|
|
|
rule_ids = []
|
|
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
# CWE-918 (SSRF) and CWE-601 (open redirect): outbound requests and redirects to a caller-supplied
|
|
|
|
|
# destination. requests/urllib/httpx are the documented HTTP clients; redirect is Flask's and Django's.
|
|
|
|
|
id = "outbound.request"
|
|
|
|
|
cwe = "CWE-918"
|
|
|
|
|
calls = ["requests.get", "requests.post", "requests.request", "urlopen", "urlretrieve", "httpx.get", "httpx.post"]
|
|
|
|
|
rule_ids = []
|
2026-09-25 12:37:22 +02:00
|
|
|
prefix_fixes_origin = true
|
2026-09-08 14:45:12 +02:00
|
|
|
|
|
|
|
|
[[sink]]
|
|
|
|
|
id = "redirect"
|
|
|
|
|
cwe = "CWE-601"
|
|
|
|
|
calls = ["redirect", "HttpResponseRedirect", "url_for"]
|
|
|
|
|
rule_ids = []
|
2026-09-25 12:37:22 +02:00
|
|
|
# Django's `reverse` builds an in-site path from a route NAME; request data can only fill its path segments.
|
|
|
|
|
prefix_fixes_origin = true
|
|
|
|
|
origin_anchors = ["reverse", "reverse_lazy"]
|
2026-09-25 16:56:15 +02:00
|
|
|
|
|
|
|
|
# Django's documented redirect-target check (`is_safe_url` is its pre-3.0 name). wger's fix for its open
|
|
|
|
|
# redirect is `if not url_has_allowed_host_and_scheme(next_url, ...): next_url = reverse(...)`.
|
|
|
|
|
[[sanitizer]]
|
|
|
|
|
id = "url_allowlist"
|
|
|
|
|
calls = ["url_has_allowed_host_and_scheme", "is_safe_url"]
|
|
|
|
|
guard = true
|