PT-2026-141400
CVE-2026-77433
·
Publicado
2026-11-09
·
Atualizado
2026-11-09
Descrição por
CVSS 3.1
6.1
MédiaMétricasAV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N
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//?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
Explorabilidade
Correção
Impactos
Fornecedores
Produtos afetados
Encontrou algum problema na descrição? Tem algo a acrescentar? Fique à vontade para nos escrever 👾