Repository navigation
Conversation
The module guards fetchHTML with is_safe_url, but that guard leaks in two ways. First, the destination is validated once and then requests.get follows redirects on its own, so a public host answering 302 Location: http://127.0.0.1:6379/ makes the module fetch an internal service and hand its response back to the analyst as page content. Second, the hand-rolled BLOCKED_RANGES list only covers 127/8, RFC1918, 169.254/16 and ::1, so http://0.0.0.0/, http://[fe80::1]/, http://[fc00::1]/ and the shared address space 100.64.0.0/10 pass the check outright. Redirects are now followed manually, revalidating every hop and capping the chain, and the range list is replaced by ipaddress' is_global, which covers every range the old list missed (it also rejects multicast and benchmark addresses, which are equally non-public destinations). Rebinding between the check and the connection still needs connection-level IP pinning and is not addressed here. Verified with flake8 on the changed file and the full suite against a local modules server: 161 passed, 4 skipped, 5 subtests passed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018dfYpyaSZd1nxSRLr8suj8
Contributor
Author
|
[pr-sweep] superseded by #908 - the base changed substantially, so this has been rebuilt cleanly on current Upstream f359a02 and 2448f74 already fixed the redirect half of this PR. #908 carries only the part that is still open: the missing |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
BLUF —
fetchHTMLfollows redirects unchecked and misses four blocked ranges, allowing SSRF.fetchHTMLinhtml_to_markdown.pyvalidated a URL once and then letrequestsfollow redirects automatically, so an allowed external URL replying with a 302 to an internal address (Redis, metadata endpoints, admin panels) was fetched transparently, and the hand-rolled blocked-range list missedfe80::/10,fc00::/7,0.0.0.0/8and100.64.0.0/10.urljoinand re-runsis_safe_urlon each of at most five hops, and replaces the range list with theipaddressmodule's built-in address classification.The defect
fetchHTMLvalidated the URL once, then letrequestsfollow redirects automatically:A request to an allowed external URL that responds with a
302tohttp://127.0.0.1:6379/(or any other internal address) is followed transparently — the safety check only ever saw the original, safe URL. Separately, the IP-blocking helper used a hand-rolled list:which omits
fe80::/10,fc00::/7,0.0.0.0/8, and100.64.0.0/10(carrier-grade NAT), so URLs likehttp://[fe80::1]/orhttp://0.0.0.0/passed the check outright.Impact
The
html_to_markdownmodule runs on MISP server infrastructure. An analyst who enriches an attribute containing an attacker-controlled URL can be made to have the MISP server issue requests to internal-only services (metadata endpoints, Redis, internal admin panels, link-local services) via either an open redirect or one of the unblocked address ranges, and have the response content returned into MISP as "markdown" — a classic internal SSRF against the MISP host itself.The fix
fetchHTMLnow disables automatic redirect following (allow_redirects=False) and loops over redirect hops itself, re-resolving relativeLocationheaders withurljoinand re-runningis_safe_urlon every hop, capped at 5 redirects._is_ip_blockednow usesnot ipaddress.ip_address(...).is_globalinstead of the hand-rolled range list, which additionally blocks0.0.0.0/8,100.64.0.0/10,fe80::/10, andfc00::/7.Note: the finding's suggested replacement,
is_private or is_loopback or is_link_local or is_reserved, does not actually block100.64.0.0/10on CPython (verified in the project venv — that range isis_privatebut not flagged by the others either), sois_globalwas used instead since it correctly covers the full non-global address space in one check.Verification
flake8 --extend-exclude=misp_modules/lib/,tests/,website/on the changed file: clean, no output.161 passed, 4 skipped, 5 subtests passed in 58.69s.Found during a review of the repository; other findings are being submitted as separate PRs.
🤖 Generated with Claude Code