fix(net): block :: and fec0::/10, and start the parity audit (0.7.1) - #28
Merged
Conversation
The first JVM <-> JS parity audit (P3-1). It found four divergences —
two on each side — which is the answer to whether the audit was worth
running.
This side was looser in two IPv6 cases:
- `::`, the unspecified address, was classified as public. Connecting
to it reaches the local host; it is the IPv6 counterpart of 0.0.0.0,
which was already blocked.
- `fec0::/10`, IPv6 site-local. Deprecated by RFC 3879 and therefore
easy to leave out, but still routed on networks that predate the
deprecation — which is exactly where an internal service lives.
The Java sibling catches both through InetAddress.isAnyLocalAddress()
and isSiteLocalAddress(), so neither was a design choice here; they were
simply absent. A hostname resolving to either passed blockPrivateNetworks
and safeFetch's DNS checks. URL-time checks were unaffected: an
IP-literal URL like http://[::]/ was already rejected.
Measured, not inferred. The address list was run through the built
package and compared against what the JVM helpers return, because
reading two implementations side by side is exactly how the 0.1.2 bug
survived review. The fe80::/10 and fec0::/10 masks differ in two bits,
so tests also pin fe00:: and fe40:: as public — a wrong mask would
swallow the whole fe00::/8 block silently.
The Java side was looser in two others, both in the tool-input scanner
and both the same shape as the 0.1.2 bypass — a collection-stage filter
letting URLs skip validation rather than be rejected by it. Fixed in
ssrf-guard#20 against the unreleased 3.2.0.
Also adds docs/parity.md and its Korean twin: the checklist for the four
areas that must agree, this audit's findings in both directions, the one
divergence deliberately left open (the JVM re-validates only scheme and
host per redirect hop, spread across six adapters), and the list of
differences that are intended rather than drift.
Verified: typecheck, 223 tests in 15 files (was 219), build, and the
runtime check green on Node.
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.
로드맵 P3-1(JVM ↔ JS 정합성 정기 점검) 첫 회차. 양쪽에서 두 건씩, 총 4건이 나왔습니다 — 감사를 돌릴 값어치가 있었는지에 대한 답입니다.
이쪽(JS)이 느슨했던 것
::(unspecified)isAnyLocalAddress()→ 차단fec0::/10(site-local)isSiteLocalAddress()→ 차단::로 연결하면 로컬 호스트에 닿습니다 — 이미 차단하던0.0.0.0의 IPv6 짝입니다.fec0::/10은 RFC 3879로 폐지돼서 빼먹기 쉽지만, 폐지 이전에 구축된 망에서는 여전히 라우팅되고 내부 서비스가 사는 곳이 바로 거깁니다.둘 다 설계상의 선택이 아니라 그냥 없었습니다. 이 주소로 해석되는 호스트명이
blockPrivateNetworks와safeFetch의 DNS 검사를 통과했습니다. URL 시점 검사는 무관합니다(http://[::]/는 이미rejectIpLiteralHosts가 거부).추론이 아니라 실측입니다. 주소 목록을 빌드된 패키지에 넣어 돌리고 JVM 헬퍼의 답과 대조했습니다 — 두 구현을 나란히 읽는 건 애초에 0.1.2 버그가 리뷰를 통과한 방식이라서요.
fe80::/10과fec0::/10은 2비트 차이라, 마스크가 틀리면fe00::/8전체를 조용히 삼킵니다. 그래서fe00::·fe40::이 공인으로 남는지도 테스트로 못 박았습니다.자바가 느슨했던 것
툴 입력 스캐너가
http(s)://만 수집하고 나머지 스킴은 정책이 보기 전에 버렸습니다 —file://·gopher://·ftp://가 조용히 통과. 프로토콜 상대//host는 수집조차 안 됐습니다.0.1.2 우회와 정확히 같은 형태입니다 — 수집 단계 필터가 URL을 검증에서 거부되게 하는 대신 빠져나가게 함. ssrf-guard#20에서 미출시 3.2.0에 수정했습니다(
./gradlew buildSUCCESSFUL, 231 테스트 전부 통과).docs/parity.md(+ 한국어 짝)이게 P3-1의 진짜 산출물입니다:
그리고 원칙 하나를 못 박았습니다: 소스가 아니라 동작을 대조할 것.
일부러 미해결로 남긴 것
자바의 리다이렉트 홉이 스킴 + 호스트/DNS만 재검증합니다. 포트·userinfo·IP-리터럴을 다시 안 봐서, 허용 호스트에서
https://allowed.example:9999/로 가는 리다이렉트를 따라갑니다(JS는 막음).JS는 공유 루프 하나에서 홉마다
validateUrl전체를 돌리지만, 자바는 리다이렉트 처리가 어댑터 6종에 흩어져 각자 자기 클라이언트 전략에 위임합니다. 패치가 아니라 전 어댑터 설계 변경이라 이번 범위 밖으로 두고 문서에 근거와 함께 기록했습니다.검증
typecheck · 223 테스트/15파일(기존 219) · build · 런타임 체크 Node 그린.