{"id":"CVE-2026-47076","aliases":["EEF-CVE-2026-47076","EUVD-2026-31689","GHSA-pj7v-xfvx-wmjq"],"status":"published","title":"SSRF allowlist bypass via percent-encoded host in hackney","description":"Interpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney_url{} record. OTP's uri_string:parse/1 and inet:parse_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","severity":{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00229,"percentile":0.13788,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-436","CWE-918"],"published":"2026-05-25T14:00:46Z","modified":"2026-10-28T02:30:49Z","fixes":[{"ecosystem":"Hex","package":"hackney","fixed_in":["4.0.1"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Hex","package":"hackney","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.13.0"},{"fixed":"4.0.1"}]}]},{"source":"osv","ecosystem":"Hex","package":"hackney","ranges":[{"type":"SEMVER","events":[{"introduced":"0.13.0"},{"fixed":"4.0.1"}]}],"versions":["0.13.1","0.14.0","0.14.1","0.14.2","0.14.3","0.15.0","0.15.2","1.0.1","1.0.2","1.0.5","1.0.6","1.1.0","1.10.0","1.10.1","1.11.0","1.12.0","1.12.1","1.13.0","1.14.0","1.14.2","1.14.3","1.15.0","1.15.1","1.15.2","1.16.0","1.17.0","1.17.1","1.17.2","1.17.3","1.17.4","1.18.0","1.18.1","1.18.2","1.19.0","1.19.1","1.2.0","1.20.0","1.20.1","1.21.0","1.22.0","1.23.0","1.24.0","1.24.1","1.25.0","1.3.0","1.3.1","1.3.2","1.4.0","1.4.1","1.4.10","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.7.0","1.7.1","1.8.0","1.8.2","1.8.3","1.8.4","1.8.5","1.8.6","1.9.0","2.0.0","2.0.0-beta.1","2.0.1","3.0.0","3.0.1","3.0.2","3.0.3","3.1.0","3.1.1","3.1.2","3.2.0","3.2.1","4.0.0"]},{"source":"osv","ecosystem":"Hex","package":"hackney","ranges":[{"type":"SEMVER","events":[{"introduced":"0.13.0"},{"fixed":"4.0.1"}]}],"versions":["0.13.1","0.14.0","0.14.1","0.14.2","0.14.3","0.15.0","0.15.2","1.0.1","1.0.2","1.0.5","1.0.6","1.1.0","1.10.0","1.10.1","1.11.0","1.12.0","1.12.1","1.13.0","1.14.0","1.14.2","1.14.3","1.15.0","1.15.1","1.15.2","1.16.0","1.17.0","1.17.1","1.17.2","1.17.3","1.17.4","1.18.0","1.18.1","1.18.2","1.19.0","1.19.1","1.2.0","1.20.0","1.20.1","1.21.0","1.22.0","1.23.0","1.24.0","1.24.1","1.25.0","1.3.0","1.3.1","1.3.2","1.4.0","1.4.1","1.4.10","1.4.2","1.4.3","1.4.4","1.4.5","1.4.6","1.4.7","1.4.8","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.5.6","1.5.7","1.6.0","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.7.0","1.7.1","1.8.0","1.8.2","1.8.3","1.8.4","1.8.5","1.8.6","1.9.0","2.0.0","2.0.0-beta.1","2.0.1","3.0.0","3.0.1","3.0.2","3.0.3","3.1.0","3.1.1","3.1.2","3.2.0","3.2.1","4.0.0"]}],"products":[{"vendor":"benoitc","product":"hackney"},{"vendor":"hackney","product":"hackney"}],"references":[{"url":"https://github.com/benoitc/hackney/security/advisories/GHSA-pj7v-xfvx-wmjq","tags":["vendor-advisory","related","exploit","Exploit","Patch","Vendor Advisory","advisory","web"]},{"url":"https://cna.erlef.org/cves/CVE-2026-47076.html","tags":["related","Patch","Third Party Advisory","web"]},{"url":"https://osv.dev/vulnerability/EEF-CVE-2026-47076","tags":["related","Patch","Third Party Advisory","web"]},{"url":"https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f","tags":["patch","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-47076","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-pj7v-xfvx-wmjq"},{"url":"https://github.com","tags":["web"]},{"url":"https://repo.hex.pm","tags":["web"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/47xxx/CVE-2026-47076.json","tags":["advisory"]},{"url":"https://github.com/benoitc/hackney","tags":["package"]},{"url":"https://hex.pm/packages/hackney","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-47076","title":"SSRF allowlist bypass via percent-encoded host in hackney","description":"Interpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney_url{} record. OTP's uri_string:parse/1 and inet:parse_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","status":"published","published":"2026-05-25T14:00:46.707Z","modified":"2026-05-27T15:41:17.845Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium","assessor":"cna"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T13:36:40.478484Z"}],"euvd":[{"id":"EUVD-2026-31689","description":"Interpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney_url{} record. OTP's uri_string:parse/1 and inet:parse_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","status":"published","published":"2026-05-25T14:00:46Z","modified":"2026-05-27T15:41:17Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"updated_at":"2026-11-02T18:21:03.926243Z"}],"ghsa":[{"id":"GHSA-pj7v-xfvx-wmjq","title":"Hackney has SSRF allowlist bypass in hackney_url:normalize/2 via percent-encoded host","description":"### Summary\n\n`hackney_url:normalize/2` URL-decodes the host component of a parsed URL, but the caller's SSRF allowlist runs before normalization using OTP's `uri_string:parse/1` and `inet:parse_address/1`, neither of which decodes percent-escapes in hostnames. A URL like `http://%31%32%37%2E%30%2E%30%2E%31/` presents an encoded, non-IP-looking host to the validator, which passes the allowlist check; hackney's normalizer then decodes it to `127.0.0.1` and connects to loopback. Because `hackney:request/5` always calls `normalize/2` with no opt-out, every request path that accepts a binary or list URL is affected. This is a parser-differential SSRF in the same class as CVE-2025-1211, but in a different function.\n\n### Details\n\nIn `src/hackney_url.erl` (lines 161–186), `normalize/2` checks whether the parsed host is already a dotted-quad or IPv6 literal via `inet_parse:address/1`. Percent-encoded forms like `%31%32%37%2E%30%2E%30%2E%31` fail that check and fall into the catch-all branch, where `urldecode/1` decodes the host before passing it to IDNA conversion:\n\n```erlang\nHost1 = binary_to_list(\n           urldecode(unicode:characters_to_binary(Host0))\n         ),\n```\n\nThe decoded host (`\"127.0.0.1\"`) replaces the original in the returned `#hackney_url{}` record. `hackney:request/5` at `src/hackney.erl:463` always calls `normalize/2`, so the decoded host is what `do_dispatch/1` and `add_host_header/2` ultimately use. The on-wire `Host:` header and the TCP connect target both reflect the decoded value.\n\nThe same payload pattern reaches the AWS/GCP/Azure IMDS (`169.254.169.254`), RFC1918 ranges, and any `localhost` admin endpoint. The 1.21.0 patch for CVE-2025-1211 fixed a separate differential in `parse_url/1` and did not touch `normalize/2`.\n\n### PoC\n\n1. Validate the URL with the canonical Erlang SSRF allowlist: `uri_string:parse/1` returns host `\u003c\u003c\"%31%32%37%2E%30%2E%30%2E%31\"\u003e\u003e`, `inet:parse_address/1` returns `{error, einval}`, so the allowlist accepts it.\n2. Pass the same URL to `hackney:get/1`.\n3. hackney's `normalize/2` decodes the host to `\"127.0.0.1\"` and connects to `127.0.0.1:80`. The internal service receives the request with `Host: 127.0.0.1`.\n\n### Impact\n\nUnauthenticated SSRF bypassing the canonical Erlang allowlist pattern. Affects hackney 0.13.0 through 4.0.0 for any application that accepts attacker-supplied URLs. Targets include cloud IMDS endpoints, `localhost` admin interfaces, and RFC1918 backends. CVSS v4.0: **6.9 (MEDIUM)**.\n\n## Resources\n\n* Introduction commit: https://github.com/benoitc/hackney/commit/4d725507588942fd00efca15b86da3273656510a\n* Patch commit: https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f","status":"published","published":"2026-06-26T21:54:55Z","modified":"2026-06-26T21:54:57Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T14:40:34.713036Z"}],"nvd":[{"id":"CVE-2026-47076","description":"Interpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney_url{} record. OTP's uri_string:parse/1 and inet:parse_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","status":"published","published":"2026-05-25T15:16:22.69Z","modified":"2026-07-24T10:10:00.197Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"medium","assessor":"secondary"},{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:H/I:N/A:N","level":"medium","assessor":"primary"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T14:12:20.131508Z"}],"osv":[{"id":"CVE-2026-47076","title":"SSRF allowlist bypass via percent-encoded host in hackney","description":"Interpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney_url{} record. OTP's uri_string:parse/1 and inet:parse_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","status":"published","published":"2026-05-25T14:00:46.707Z","modified":"2026-10-28T02:30:49.993589902Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T15:45:06.231145Z"},{"id":"EEF-CVE-2026-47076","title":"SSRF allowlist bypass via percent-encoded host in hackney","description":"## Summary\n\nInterpretation Conflict vulnerability in benoitc hackney allows Server Side Request Forgery. hackney\\_url:normalize/2 URL-decodes the host component after the URL has been parsed into a #hackney\\_url{} record. OTP's uri\\_string:parse/1 and inet:parse\\_address/1 do not decode percent-escapes in the host, so a URL such as http://%31%32%37%2E%30%2E%30%2E%31/ is seen by a caller's allowlist validator with host %31%32%37%2E%30%2E%30%2E%31 (not an IP address), which passes the allowlist check. hackney's normalizer then decodes the host to 127.0.0.1 and opens a TCP connection to loopback. Because hackney:request/5 always calls hackney\\_url:normalize/2 with no opt-out, every request that takes a binary or list URL is affected. The same technique reaches cloud instance metadata services (169.254.169.254), RFC1918 networks, and any admin interface listening on localhost.\n\nThis issue affects hackney: from 0.13.0 before 4.0.1.","status":"published","published":"2026-05-25T14:00:46.707Z","modified":"2026-07-30T18:22:09.935592Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T15:46:33.047032Z"},{"id":"GHSA-pj7v-xfvx-wmjq","title":"Hackney has SSRF allowlist bypass in hackney_url:normalize/2 via percent-encoded host","description":"### Summary\n\n`hackney_url:normalize/2` URL-decodes the host component of a parsed URL, but the caller's SSRF allowlist runs before normalization using OTP's `uri_string:parse/1` and `inet:parse_address/1`, neither of which decodes percent-escapes in hostnames. A URL like `http://%31%32%37%2E%30%2E%30%2E%31/` presents an encoded, non-IP-looking host to the validator, which passes the allowlist check; hackney's normalizer then decodes it to `127.0.0.1` and connects to loopback. Because `hackney:request/5` always calls `normalize/2` with no opt-out, every request path that accepts a binary or list URL is affected. This is a parser-differential SSRF in the same class as CVE-2025-1211, but in a different function.\n\n### Details\n\nIn `src/hackney_url.erl` (lines 161–186), `normalize/2` checks whether the parsed host is already a dotted-quad or IPv6 literal via `inet_parse:address/1`. Percent-encoded forms like `%31%32%37%2E%30%2E%30%2E%31` fail that check and fall into the catch-all branch, where `urldecode/1` decodes the host before passing it to IDNA conversion:\n\n```erlang\nHost1 = binary_to_list(\n           urldecode(unicode:characters_to_binary(Host0))\n         ),\n```\n\nThe decoded host (`\"127.0.0.1\"`) replaces the original in the returned `#hackney_url{}` record. `hackney:request/5` at `src/hackney.erl:463` always calls `normalize/2`, so the decoded host is what `do_dispatch/1` and `add_host_header/2` ultimately use. The on-wire `Host:` header and the TCP connect target both reflect the decoded value.\n\nThe same payload pattern reaches the AWS/GCP/Azure IMDS (`169.254.169.254`), RFC1918 ranges, and any `localhost` admin endpoint. The 1.21.0 patch for CVE-2025-1211 fixed a separate differential in `parse_url/1` and did not touch `normalize/2`.\n\n### PoC\n\n1. Validate the URL with the canonical Erlang SSRF allowlist: `uri_string:parse/1` returns host `\u003c\u003c\"%31%32%37%2E%30%2E%30%2E%31\"\u003e\u003e`, `inet:parse_address/1` returns `{error, einval}`, so the allowlist accepts it.\n2. Pass the same URL to `hackney:get/1`.\n3. hackney's `normalize/2` decodes the host to `\"127.0.0.1\"` and connects to `127.0.0.1:80`. The internal service receives the request with `Host: 127.0.0.1`.\n\n### Impact\n\nUnauthenticated SSRF bypassing the canonical Erlang allowlist pattern. Affects hackney 0.13.0 through 4.0.0 for any application that accepts attacker-supplied URLs. Targets include cloud IMDS endpoints, `localhost` admin interfaces, and RFC1918 backends. CVSS v4.0: **6.9 (MEDIUM)**.\n\n## Resources\n\n* Introduction commit: https://github.com/benoitc/hackney/commit/4d725507588942fd00efca15b86da3273656510a\n* Patch commit: https://github.com/benoitc/hackney/commit/452620a92ec1da2e6b4862a049a2a4f04b42068f","status":"published","published":"2026-06-26T21:54:55Z","modified":"2026-06-26T22:00:10.982884294Z","cvss":[{"version":"4.0","score":6.9,"vector":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N","level":"medium"}],"cwes":["CWE-436","CWE-918"],"updated_at":"2026-11-02T15:46:53.244788Z"}]}}
