sec: harden UtilMethods.getURL — restrict to http(s) and block non-routable hosts - #36969
sec: harden UtilMethods.getURL — restrict to http(s) and block non-routable hosts#36969mbiuki wants to merge 6 commits into
Conversation
…ble hosts UtilMethods.getURL(String) fetched any URI a caller passed and returned the body, with no scheme allowlist or host restriction. It is exposed to the Velocity template context as $UtilMethods.getURL (VelocityUtil), so any design-layer (template/container) user could use it for local file read (file://) and full-read SSRF to loopback / link-local (cloud metadata) / private hosts. The introspector denylist blocks reflection/Runtime/etc but not this method. Restrict getURL to http/https and reject loopback/any-local/link-local/site-local/ multicast targets, returning empty + a SecurityLogger entry otherwise. getURL has no callers in Java or shipped .vtl, so no functional impact; $UtilMethods and its other helpers (isSet, date/HTML utils used by ~24 templates) are untouched. Adds SstiGetUrlReproTest as a regression guard (file:// read blocked, loopback SSRF blocked). Verified: ./mvnw test -pl :dotcms-core -Dtest=SstiGetUrlReproTest -> BUILD SUCCESS. Details: dotCMS/private-issues#668 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @wezell's task in 2m 25s —— View job PR Review — harden
|
Set setInstanceFollowRedirects(false) on the HttpURLConnection so a 3xx response cannot redirect the request to an internal host after the loopback/link-local host check has passed. Closes the redirect-to-internal residual noted in the PR description. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HMzJB7ujVZN6N2nV1xgJng
|
CI results (head
The MainSuite 2a failure is The security change itself is verified: 🤖 Generated with Claude Code |
…host guard Addresses review on #36969 (wezell, rsh1k, automated review): - Fetch now uses the shared CircuitBreakerUrl client (same path as $import): circuit breaker, timeout, IPUtils private-subnet gate, redirects disabled. Being HTTP-only it also removes the file:// read path. Replaces the hand-rolled URLConnection. - Kept a strong pre-connect host guard because IPUtils' default blacklist is weaker (misses 127/8, 0.0.0.0, full 169.254/16 incl. 169.254.170.2, IPv6). Now resolves via getAllByName and rejects if ANY address is non-routable (multi-record bypass), via a new isNonRoutable() helper that also covers IPv6 ULA (fd00::/7) and IPv4 CGNAT (100.64/10). - Fixed cosmetic 'jar://' -> 'jar:' comment. Regression test extended: file:// + loopback + IPv6 ::1 + CGNAT + ULA + 0.0.0.0 + 169.254.170.2 all return empty. ./mvnw test -pl :dotcms-core -Dtest=SstiGetUrlReproTest -> BUILD SUCCESS. Deferred to dotCMS/private-issues#668: TOCTOU/DNS-rebinding (pin connection to the validated IP) — needs client-level IP pinning CircuitBreakerUrl doesn't expose. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Pushed @wezell / @rsh1k — reuse the shared path: Automated review items:
Regression test extended to assert @wezell — ready for another look when you have a moment. 🤖 Generated with Claude Code |
Closes dotCMS/private-issues#668
Proposed Changes
UtilMethods.getURL(String)opened a connection to any URI passed to it and returned the body, with no scheme allowlist and no host restriction. It is exposed to the Velocity template context as$UtilMethods.getURL(VelocityUtil), which makes it reachable by any user with design-layer (template/container) write access — a lower bar than administrator. That allowed local file read (file://) and full-read SSRF to loopback / link-local (cloud-metadata) / private hosts. The Velocity introspector denylist blocks reflection/Runtime/etc. but is class-based and does not cover this method.This change makes
getURLself-defend:http/httpsschemes (blocksfile:,jar:,ftp:,gopher:, …).SecurityLogger.Scope / compatibility:
getURLhas no callers in the Java source or in any shipped.vtl, so there is no functional impact.$UtilMethodsstays in the template context and its other helpers (isSet, date/HTML utilities used by ~24 bundled templates) are unchanged.Regression test
SstiGetUrlReproTestguards the fix — assertsfile://read returns empty and a loopback request is never made. Verified locally:Notes
Sensitive detail, full reproduction, and follow-up hardening (audit other context tools
$import/$filetool/$velutil/$dotcontent; move the introspector to an allowlist; split content-edit from template-edit permissions) are tracked privately in dotCMS/private-issues#668.🤖 Generated with Claude Code