CVE-2026-77433GHSA-gcwf-c25f-p9rv
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:
AI analysis
Not analysed yet.
Claude analyses new vulnerabilities after each morning's collection, most important first, up to a daily limit. The verified data on this page is complete without it.
| Product | Affected versions | Fixed in | Source |
|---|---|---|---|
| kiwitcmsPyPI | <= 12.4 | None listed | GitHub |
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
Not scored yet. FIRST publishes EPSS daily for CVEs.
Published
First publication at the source
Last modified
Latest change at the source
Collected
First seen by Baba Yaga Security