
Python PoC for CVE-2026-93453, a SOGo password reset link poisoning flaw via attacker-controlled Origin header that enables reset token interception and account takeover.
An exploit for a flaw where SOGo assembles the password-reset link it emails to users straight from the client-supplied Origin header. An anonymous attacker who knows a victim's login POSTs a recovery request with a controlled Origin; the link inside the emailed "Password reset" message then points at the attacker's host while the embedded JWT remains a perfectly valid recovery token. One victim click later the token is in the attacker's access log, and POST /SOGo/changePassword swaps it for a new password — full account takeover of mail, calendars and contacts, no authentication at any step. The email branch additionally skips the SOGoPasswordRecoveryEnabled check (that flag is only consulted in the SecretQuestion branch), so the email vector works even when the operator thinks recovery is toggled off.
Record: https://www.cve.org/CVERecord?id=CVE-2026-93453
| Affected | SOGo < 5.12.11 (≤ 5.12.10) |
| Fixed | 5.12.11 (SOGoPasswordRecoveryBaseURLs allowlist) |
| Prerequisites | victim with SecondaryEmail recovery mode; attacker knows the userName; victim clicks the emailed link |
| Impact | account takeover |
// UI/MainUI/SOGoRootPage.m:1374 (tag SOGo-5.12.10) — passwordRecoveryEmailAction
url = [NSString stringWithFormat:@"%@%@?token=%@",
[[request headers] objectForKey:@"origin"], // <-- raw client header
[request uri],
jwtToken]; // <-- valid HS256 JWT, 10 min TTL
The tokenized reset URL is Origin + request URI + JWT, so whoever sets the
header sets the host the victim's secret travels to. This is password reset
poisoning (CWE-640 / CWE-601), not an open redirect: the JWT is accepted by
/SOGo/changePassword no matter which host carried it there.
// 5.12.11 — the fix: origins are validated against a mandatory allowlist
baseUrls = [[SOGoSystemDefaults sharedSystemDefaults] passwordRecoveryBaseURLs];
serverUrl = [[request headers] objectForKey:@"origin"];
if (!(baseUrls && [baseUrls count] > 0)) // unset config -> 403
if (![baseUrls containsObject:serverUrl]) // foreign origin -> 403
On 5.12.11 a foreign Origin gets a 403 ("Password recovery email in
error"); on ≤ 5.12.10 the same request returns 200 and the mail goes out —
that difference is what check keys on.
# non-destructive probe: canary user + foreign Origin, classify the reply
python3 CVE-2026-93453.py check https://sogo.example
# poison: dispatch the reset mail for the victim with your host in the link
python3 CVE-2026-93453.py poison https://sogo.example --user victim --origin https://attacker.example
Standard library only (Python 3.8+). Supports --proxy, --insecure,
--timeout, --no-color, and --prefix for non-standard SOGo mount points.
The PoC needs no attacker infrastructure: poison only makes the server
mail the link, and the poisoned host is whatever you put in --origin.
On a vulnerable instance the mailed link becomes
https://attacker.example/SOGo/passwordRecoveryEmail?token=<valid HS256 JWT>
— the token travels to your host the moment the victim clicks.
check uses a nonexistent canary username so no email is ever delivered to a
real mailbox; 200 = the email branch accepted the foreign Origin (vulnerable
signature), 403 = 5.12.11 allowlist or unset SOGoPasswordRecoveryBaseURLs
(not exploitable). For a definitive self-test, poison an account you own
and read the link host in your own inbox.
passwordRecoveryEmailAction)For authorized testing and research only. Use only against systems you own or have explicit permission to test.