{"id":"GHSA-f3g8-9xv5-77gv","summary":"Saltcorn: Open Redirect in `POST /auth/login` due to incomplete `is_relative_url` validation (backslash bypass)","details":"### Summary\nSaltcorn validates the post-login `dest` parameter with a string check that only blocks `:/` and `//`. Because all WHATWG-compliant browsers normalise backslashes (`\\`) to forward slashes (`/`) for special schemes, a payload such as `/\\evil.com/path` slips through `is_relative_url()`, is emitted unchanged in the HTTP `Location` header, and causes the browser to navigate cross-origin to an attacker-controlled domain. The bug is reachable on a default install and only requires a victim who can be tricked into logging in via a crafted Saltcorn URL.\n\n### Details\nVulnerable function: `packages/server/routes/utils.js:393-395`\n\n```js\nconst is_relative_url = (url) =\u003e {\n  return typeof url === \"string\" && !url.includes(\":/\") && !url.includes(\"//\");\n};\n```\n\nThe function's intent is to allow only same-origin redirects, but the allow-list only checks for two literal substrings. It does not handle:\n- backslash characters, which WHATWG URL parsing (used by every modern browser) treats as forward slashes for the special schemes `http`, `https`, `ftp`, `ws`, `wss`. A URL parser fed `/\\evil.com/path` with a base of `http://victim/` resolves to `http://evil.com/path`.\n- non-`http(s):` schemes that do not contain `:/`. The strings `javascript:alert(1)`, `data:text/html,...`, `vbscript:...` all pass.\n\nVulnerable callsite: `packages/server/auth/routes.js:1371-1376`\n\n```js\n} else if (\n  (req.body || {}).dest &&\n  is_relative_url(decodeURIComponent((req.body || {}).dest))\n) {\n  res.redirect(decodeURIComponent((req.body || {}).dest));\n} else res.redirect(\"/\");\n```\n\nThe body's `dest` is URL-decoded twice (once by body-parser, once by the explicit `decodeURIComponent`) and the same value is passed to `res.redirect`. Express 5's `res.redirect` runs the value through `encodeurl@2.0.0`, whose whitelist character class `[^\\x21\\x23-\\x3B\\x3D\\x3F-\\x5F\\x61-\\x7A\\x7C\\x7E]` includes `\\x5C` (backslash). The backslash is therefore not percent-encoded and ends up verbatim in the `Location` response header.\n\n### PoC\n[poc.zip](https://github.com/user-attachments/files/26678853/poc.zip)\n\nPlease extract the uploaded compressed file before proceeding\n1. ./setup.sh\n2. ./poc.sh\n\n\u003cimg width=\"419\" height=\"71\" alt=\"스크린샷 2026-04-13 오후 11 44 36\" src=\"https://github.com/user-attachments/assets/9c919ed4-167b-47e3-9873-733f97b44bf0\" /\u003e\n\n### Impact\nAny user who can be lured into clicking a Saltcorn login URL crafted by the attacker will, after submitting their valid credentials, be redirected to an attacker-controlled origin. The redirect happens under the trusted Saltcorn domain, so the user has no visual cue that they are about to leave the site. Realistic abuse patterns:\n\n- Credential phishing — the attacker's site renders a forged \"session expired, please log in again\" prompt to capture the password the user just typed.","aliases":["CVE-2026-42259"],"modified":"2026-05-11T13:53:18.140132Z","published":"2026-04-16T23:00:45Z","database_specific":{"github_reviewed":true,"github_reviewed_at":"2026-04-16T23:00:45Z","nvd_published_at":"2026-05-07T20:16:44Z","cwe_ids":["CWE-601"],"severity":"MODERATE"},"references":[{"type":"WEB","url":"https://github.com/saltcorn/saltcorn/security/advisories/GHSA-f3g8-9xv5-77gv"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42259"},{"type":"PACKAGE","url":"https://github.com/saltcorn/saltcorn"}],"affected":[{"package":{"name":"@saltcorn/server","ecosystem":"npm","purl":"pkg:npm/%40saltcorn/server"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.4.6"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-f3g8-9xv5-77gv/GHSA-f3g8-9xv5-77gv.json"}},{"package":{"name":"@saltcorn/server","ecosystem":"npm","purl":"pkg:npm/%40saltcorn/server"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.5.0-beta.0"},{"fixed":"1.5.6"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-f3g8-9xv5-77gv/GHSA-f3g8-9xv5-77gv.json"}},{"package":{"name":"@saltcorn/server","ecosystem":"npm","purl":"pkg:npm/%40saltcorn/server"},"ranges":[{"type":"SEMVER","events":[{"introduced":"1.6.0-alpha.0"},{"fixed":"1.6.0-beta.5"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/04/GHSA-f3g8-9xv5-77gv/GHSA-f3g8-9xv5-77gv.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:A/VC:N/VI:N/VA:N/SC:L/SI:L/SA:N"}]}