CVE-2026-77433 json
Kiwi TCMS account confirmation open redirect through protocol-relative URLs
published 2h ago modified 2h ago also GHSA-gcwf-c25f-p9rv
- cvss
- 6.1
- version 3.1, GitHub
- cisa kev
- Not listed
- not in CISA's catalog
- epss
- —
- no EPSS score yet
- public exploits
- 0
- none known
Summary
## Summary
In Kiwi TCMS version 16.1, the fix for CVE-2026-54724 (open redirect via unvalidated `next` parameter) in the account confirmation endpoint is incomplete. The validation uses `startswith("/")` to check if the redirect URL is local, but this is bypassed by protocol-relative URLs like `//evil.com` which start with `/` but redirect the browser to an external attacker-controlled domain. 16.1’s incomplete mitigation still allows protocol-relative destinations and earlier releases lack the mitigation.
## Vulnerable Code
In `tcms/kiwi_auth/views.py`, the `Confirm` view (line ~113):
def get_redirect_url(self, *args, **kwargs): self.url = self.request.GET.get("next", reverse("core-views-index")) if not self.url.startswith("/"): self.url = reverse("core-views-index")
The check `startswith("/")` passes for `//evil.com` because it begins with `/`, but browsers interpret `//evil.com` as a protocol-relative URL that redirects to `https://evil.com` (or `http://evil.com` depending on the current page's protocol).
## Proof of Concept
GET /confirm/<activation_key>/?next=//evil.com/phishing HTTP/1.1 Host: kiwi.example.com
Response:
HTTP/1.1 302 Found Location: //evil.com/phishing
The browser follows the redirect to `https://evil.com/phishing`.
Verification:
>>> url = "//evil.com/phishing" >>> url.startswith("/") True # passes the check!
## Impact
An attacker can craft a confirmation link that, after the user confirms their account, redirects them to a phishing page. This is useful for:
- Credential phishing (fake login page on attacker domain) - Malware delivery after trusted action - Social engineering (user trusts the link because it's from the legitimate Kiwi TCMS instance)
The attack requires sending a crafted activation link to a user who is registering, or intercepting/predicting the activation key.
## Suggested Fix
Use Django's `url_has_allowed_host_and_scheme()` utility instead of a simple `startswith` check:
from django.utils.http import url_has_allowed_host_and_scheme
def get_redirect_url(self, *args, **kwargs): self.url = self.request.GET.get("next", reverse("core-views-index")) if not url_has_allowed_host_and_scheme( self.url, allowed_hosts={self.request.get_host()}, require_https=self.request.is_secure(), ): self.url = reverse("core-views-index")
This properly rejects protocol-relative URLs, absolute URLs to other hosts, and other bypass patterns.
Fixed in Version 16.2
Patch / fix
- github.com/kiwitcms/Kiwi@65d8f7c commit
- github.com/kiwitcms/Kiwi#4458 pull request
- github.com/kiwitcms/Kiwi/releases/tag/v16.2 release
Is CVE-2026-77433 in your dependencies? Fencer shows which of your repositories are affected as soon as an advisory lands, and opens the fix as a pull request.
Check with FencerCVSS scores
| assigned by | version | score | vector |
|---|---|---|---|
| GitHub | 3.1 | 6.1 | CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N |
Affected packages
| ecosystem | package | affected | fixed in |
|---|---|---|---|
| pypi | kiwitcms | <=12.4 |
no fix listed |
Classification
- Weakness
-
- CWE-601 URL Redirection to Untrusted Site
References
-
github.com
/kiwitcms/Kiwi/pull/4458
web
-
github.com
/kiwitcms/Kiwi/commit/65d8f7c2743d7f06a4fca77c060b9d561f02f948
web
-
github.com
/kiwitcms/Kiwi/security/advisories/GHSA-gcwf-c25f-p9rv
web
-
github.com
/kiwitcms/Kiwi/security/advisories/GHSA-hmj5-jm8h-h9fh
web
-
github.com
/kiwitcms/Kiwi
package
-
github.com
/kiwitcms/Kiwi/releases/tag/v16.2
web
Sources
- GitHub Advisory GHSA-gcwf-c25f-p9rv
Fencer Intel keeps one entry per vulnerability: the CVE id wins, then GHSA, and records that alias each other are merged, except that two CVEs always keep separate entries. The same data is available as JSON.