🔄 Topic

An independent security review of yesterday’s SSRF fix — Chromium IP-pinning meant to close DNS-rebinding — found two further bypasses, both confirmed live against a real browser and a real local server, before either was patched.


🎯 Goal

Prove or disprove the IP-pinning fix under real adversarial conditions instead of trusting that “the tests pass” means “the hole is closed.”


🛠 What I Did

I ran an independent acceptance review against my own fix and refused to accept it on faith.

Main areas covered:

  • Defect 2: the pinning fix trusted the original requested hostname’s address without re-validating it, reasoning an earlier gate had already checked it — but that gate checked the hostname string, not what a DNS lookup milliseconds later would resolve to. A rebinding attacker could pass the first check and pin a forbidden address for the second. Confirmed live: Chromium launched with the forbidden address pinned, no alarm raised, just a quiet navigation timeout.
  • Defect 3: redirect-following trusted the very first hop by hostname, assuming the DNS-safety wrapper always ran — except Node’s HTTP client never invokes a custom DNS lookup at all when the URL is already a literal IP address. Confirmed live: fetching http://127.0.0.1:<port> returned a real local server’s full response with zero SSRF checks.
  • fixed both by validating every hop identically, including hop zero and including the original hostname’s fresh resolution — no more trusted exceptions
  • extended the test fixtures with a reusable loopback network-guard mock so the regression tests exercise real HTTP/redirect/timeout mechanics without depending on the now-closed bypass
  • verified IPv6 pinning against a real production IPv6-only site end-to-end, closing a limitation that had only ever been unit-tested
  • confirmed TLS certificate validation stayed intact throughout — expired, wrong-host, and self-signed certificates were all still correctly rejected

🔗 Key Cybersecurity Connections

This is the difference between a fix and a proof. DNS rebinding exists precisely to exploit the gap between “we checked this hostname” and “this is what the hostname resolves to right now” — and my own fix reintroduced that exact gap in a new place while closing it in the old one. Independent review with live reproduction, not test-suite trust, is what caught it.

The IP-literal gap is the other classic SSRF lesson: safety code that only runs during DNS resolution does nothing for a URL that never needed to resolve anything.


🔍 Investigation Questions

  • Does every hostname get revalidated at the moment of connection, including the first one?
  • What happens when the target URL is already a literal IP address?
  • Can a “trusted because checked earlier” shortcut hide a time-of-check/time-of-use gap?
  • Are all private ranges covered, including the ones people forget — like the cloud metadata range?
  • Did the fix get proven against a live target, or only against a mock?

🚨 Detection Opportunities

Checks for outbound-fetch hardening:

  • any code path with a “trust this hostname, it was checked earlier” comment
  • DNS-safety wrapper skipped because the URL was already an IP literal
  • redirect hop zero treated differently from discovered hops
  • private ranges list missing 169.254.0.0/16 or other commonly forgotten space
  • a security fix merged without a live, adversarial reproduction attempt

Example:

project=browser-research-adapter
signal=trusted_hostname_shortcut_in_ssrf_guard
risk_area=time_of_check_time_of_use_gap
triage=reproduce_live_before_and_after_the_fix

🧭 MITRE ATT&CK Techniques

Possible mappings for the vulnerability class:

  • T1090 — Proxy
  • T1557 — Adversary-in-the-Middle (DNS rebinding is a variant of this pattern)

🗺 Visual Investigation Diagram

"Fixed" IP-pinning shipped
    ↓
Independent review: assume it's wrong, try to break it
    ↓
Defect 2: original hostname trusted, not re-validated
Defect 3: IP-literal URLs skip DNS-safety entirely
    ↓
Both reproduced live before any patch
    ↓
Every hop validated identically, no exceptions
    ↓
IPv6 + TLS verified live, not just unit-tested

⚠ Challenges

The hardest part was psychological: yesterday’s fix felt done. Finding two more holes in it the next day is uncomfortable, and it is also exactly what an independent review is for. A review that only checks “did the tests pass” would have missed both.


📚 What I Learned

I learned that SSRF fixes have to be re-attacked, not just re-tested. Both defects survived a full unit-test suite because the tests encoded the same trust assumption the code did. Only live reproduction against a real server exposed the gap.


➡ Next Steps

  • Treat every “trusted because checked earlier” comment as a review target
  • Keep the loopback-fixture pattern for future network-guard changes
  • Re-attack this guard again after any redirect or DNS-handling change
  • Apply the same live-reproduction discipline to the next security fix

🧠 Reflection

Two bypasses found the day after “fixed” is not a failure of yesterday’s work — it is what happens when review is adversarial instead of confirmatory. I would rather find these than have someone else find them first.


🧩 Lessons Learned

What worked

Independent review with a genuine attempt to break the fix, reproduced live before patching.

What broke

A trusted-hostname shortcut and a DNS-lookup assumption that IP-literal URLs violated.

Why it broke

Both defects hid inside reasonable-sounding assumptions that nobody had tested against a real adversary.

Fix / takeaway

Validate every hop identically, with no trusted exceptions, and prove fixes live — a passing test suite is not the same as a closed hole.


📈 Skill Progression Context

This supports my cybersecurity progression because DNS rebinding, time-of-check/time-of-use gaps, and adversarial self-review are exactly the reasoning real appsec and red-team work depends on.


😄 TL;DR

Reviewed my own SSRF fix like an attacker and found two more ways through it — both closed, both proven live.