Skip to content

fix: model Django's CSRF token and reversed URLs as values attackers cannot shape - #133

Merged
SizzleUnrlsd merged 4 commits into
mainfrom
fix/django-token-and-reverse
Sep 24, 2026
Merged

SizzleUnrlsd merged 4 commits into
mainfrom
fix/django-token-and-reverse

Conversation

@SizzleUnrlsd

@SizzleUnrlsd SizzleUnrlsd commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Closes #132. This is the third model fix that comes before the stricter guard rule from #98, covering the healthchecks false positives that rule would otherwise raise to HIGH. It is independent of #129 and #131, apart from the shared CHANGELOG spot.

Change

  • django.middleware.csrf.get_token becomes a sanitizer for every kind. It returns _mask_cipher_secret(...), built from CSRF_ALLOWED_CHARS (ASCII letters and digits), and an incoming cookie of another shape is rejected by _check_token_format first (Django 5.2 middleware/csrf.py).
  • django.urls.reverse and reverse_lazy (and their django.urls.base spellings) become sanitizers for REDIRECT only. The result is a path of the project's URL configuration: arguments are quoted, a query goes after ?, and escape_leading_slashes rules out //host (Django 5.2 urls/resolvers.py, urls/base.py). ' is left unquoted, since it is an RFC 3986 sub-delimiter, so other kinds still flow. A test keeps a SQL injection through reverse reported.

Snapshot changes, reviewed (healthchecks only)

Finding Why it goes
hc/accounts/views.py:214 xss (signup_csrf) HttpResponse(csrf.get_token(request)): an alphanumeric token.
hc/accounts/views.py:142 open-redirect (_check_2fa) redirect(reverse(route, query=query)): next only ends up URL-encoded in the query string.
hc/front/views.py:553, 574, 670 open-redirect url = reverse("hc-checks", args=[...]); url += _get_referer_qs(request); redirect(url). The taint came through args, from the view's parameters. The target starts with a local path, so no suffix can change the host.
hc/front/views.py:1115 open-redirect (copy) redirect(reverse("hc-details", args=[copied.code], query={"copied": 1})).

A correction to what I said in #98: healthchecks lines 170 and 281 do not reach the redirect guarded by _allow_redirect (line 115). They reach this reverse redirect at line 142. _allow_redirect only concerns line 115, a hotspot that stays one; declaring it as a validator is tracked separately.

Not fixed here: the findings at lines 170 and 281. They are reported at the callers, through _check_2fa, and a sanitizer inside a project function does not protect its callers' flows. Function summaries record which parameters reach which external calls, not the models applied along the way. A helper doing HttpResponse(escape(value)) has the same limit. Tracked in #134.

Verification

  • pytest: 1005 passed. ruff check src tests and mypy are clean.
  • pytest -m regression under Python 3.12: 19 passed. Only healthchecks.json changed, by the six findings above.
  • The two new tests were red before the models (XSS at 8 and 11, open-redirect at 9 and 12). The two guard tests, SQL through reverse and a redirect to raw input, were already green.

@SizzleUnrlsd SizzleUnrlsd self-assigned this Sep 24, 2026
@SizzleUnrlsd
SizzleUnrlsd merged commit da442a7 into main Sep 24, 2026
10 checks passed
@SizzleUnrlsd
SizzleUnrlsd deleted the fix/django-token-and-reverse branch September 24, 2026 22:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Precision: Django's CSRF token and reversed URLs carry no attacker choice

1 participant