mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-18 04:29:27 +00:00
feat(rules): Django request sources; a destination with a fixed origin is not untrusted
python.toml's sources were Flask-only, so no flow could start anywhere on a Django
project. OpenCVE's SSRF reads its URL from `form.cleaned_data`, and the scan answered
the sink file and found nothing. Added: request.GET/POST/body/COOKIES/FILES/META,
DRF's query_params, and cleaned_data.
With those sources known, OpenCVE reported three redirects that cannot leave the
site: `reverse("cves") + "?" + request.GET.urlencode()` (twice) and
`reverse(route, kwargs=...)`. Destination sink facts can now set
`prefix_fixes_origin` and `origin_anchors`. A path is cut when it enters a
concatenation, f-string or `+=` through anything other than its leftmost operand,
and that operand fixes the origin. Origin-fixing operands:
- a literal `/x`, `?` or `#`;
- a literal of the form scheme://host/;
- an anchor call;
- a variable whose every assignment fixes the origin.
A path is also cut when it passes through an anchor. `"/" + x` (//evil) and
`"https://" + host` stay reported. A Joern-gated test covers all seven shapes.
Exploratory (retune after protocol-v1): OpenCVE vulnerable and fixed pins are both
0 findings, down from 2 and 3. The SSRF itself is still missed, because the flow
crosses a registry dispatch (`REGISTRY.get(t)(extras).run()`) that the engine does
not follow.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ec3d1457fb
commit
562b670a43
@@ -16,6 +16,23 @@ patterns = [
|
||||
"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"]
|
||||
@@ -111,9 +128,13 @@ 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"]
|
||||
|
||||
Reference in New Issue
Block a user