mirror of
https://github.com/reconurge/flowsint.git
synced 2026-06-10 00:30:17 -05:00
[PR #164] fix(ssrf): block numeric-encoded IPv4 loopback/private addresses #2633
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/reconurge/flowsint/pull/164
Author: @Mubashirrrr
Created: 6/3/2026
Status: 🔄 Open
Base:
main← Head:fix/ssrf-numeric-ip-bypass📝 Commits (1)
1e426cffix(ssrf): block numeric-encoded IPv4 loopback/private addresses📊 Changes
2 files changed (+57 additions, -7 deletions)
View changed files
📝
flowsint-core/src/flowsint_core/templates/loader/yaml_loader.py(+27 -7)📝
flowsint-core/tests/templates/test_loader.py(+30 -0)📄 Description
Bug (security — SSRF bypass)
flowsint-core/src/flowsint_core/templates/loader/yaml_loader.pyprotects outbound enricher requests against SSRF viavalidate_url_safe→is_ip_blocked.is_ip_blockedcanonicalizes the host withipaddress.ip_address, which accepts only the dotted-quad IPv4 form.However,
httpx(used byTemplateEnricher) and the OS resolver also accept legacy numeric IPv4 encodings that all resolve to127.0.0.1:21307064330x7f000001017700000001127.1ipaddress.ip_addressrejects all of these, sois_ip_blockedreturnedFalseand the URL was reported ALLOWED — bypassing the loopback/private/metadata blocklist. A template URL likehttp://2130706433/therefore reaches internal services.validate_url_safeis reached on the real request path:template_enricher.pyrenders a template URL and callsvalidate_url_safe(url)immediately before thehttpxrequest.Reproduction
End-to-end confirmation against a loopback listener:
httpx.get("http://2130706433:<port>/")andhttpx.get("http://0x7f000001:<port>/")both connect to127.0.0.1and return the internal response, whilevalidate_url_safe(pre-fix) raised nothing.Fix
Normalize the host literal through
socket.inet_aton(which mirrors the C resolver's acceptance of decimal/hex/octal/short IPv4 forms) before the blocklist check. All numeric encodings of a blocked address are now caught; numeric encodings of public addresses (e.g.0x08080808→8.8.8.8) remain allowed. Change is confined tois_ip_blockedplus a small_normalize_iphelper.Regression tests
Added to the existing
TestSSRFProtectionclass intests/templates/test_loader.py:test_is_ip_blocked_numeric_loopback_encodingsandtest_validate_url_safe_numeric_ip_bypass— fail before this change (numeric forms reported as not-blocked / ALLOWED), pass after.test_is_ip_blocked_numeric_public_still_allowed— guards against false positives on a public numeric address.tests/templates/test_loader.py: 43 passed on Python 3.12. (Pre-existing, unrelated failures/errors intests/templates/test_template_enricher.pyexist onmainindependently of this change and are untouched by it.)🤖 Generated with Claude Code
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.