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", ] # 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", ] [[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 # --- 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 = [] prefix_fixes_origin = true [[sink]] id = "redirect" cwe = "CWE-601" calls = ["redirect", "HttpResponseRedirect", "url_for"] rule_ids = [] # 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"] # 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