Answer in brief
CVE-2026-77433 records a Medium severity (CVSS 6.1) vulnerability in Kiwi TCMS account confirmation open redirect through protocol-relative URLs. The current sources do not mark it as known exploited. The current feed maps kiwitcms (pip), kiwitcms (pypi). Check affected ranges and fixed versions before updating.
Analysis pending evidence review
HOL Guard separates source facts from reviewed analysis. See the methodology.
CVSS is 6.1. The current sources do not mark it as known exploited. Treat this as a source-backed prioritization signal, not a statement about your environment.
Analysis status
Analysis pending evidence review
Factual feed record only; HOL analysis is not approved for indexing. Read the methodology.
The current feed maps kiwitcms (pip), kiwitcms (pypi). Check affected ranges and fixed versions before updating.
| Package | Affected range | Fixed version |
|---|---|---|
| kiwitcmspip | <=12.4 | Not reported |
| kiwitcmspypi | >=0 | Not reported |
Published upstream
Nov 9, 2026
Evidence: source:ghsa:source_dates:source-dates:recordSource modified
Nov 9, 2026
Evidence: source:ghsa:source_dates:source-dates:recordFirst seen by HOL
Nov 9, 2026
## 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
Quoted source text, attributed separately from HOL analysis.