fix: reject private IPs after DNS resolution (SSRF/DNS-rebinding)
The SSRF guard only checked the host string, so an attacker-owned domain resolving to 169.254.169.254 / 10.x (DNS rebinding, TOCTOU) bypassed it. Add a reqwest dns::Resolve (SafeResolver) that drops resolved addresses in private ranges, wired into all four crawler/analysis clients — it fires per connection so it also covers redirect hops. Also close the is_private_ip IPv6-embedding gaps (IPv4-compatible ::/96, NAT64 64:ff9b::/96, 6to4 2002::/16 all now unwrap to the embedded IPv4). Bump to 0.124.10. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -476,6 +476,10 @@ async fn spawn_analysis_daemon(
|
||||
// endpoint is a single admin-configured URL), so the policy
|
||||
// enforces scheme + private-IP only.
|
||||
.redirect(crate::crawler::safety::public_redirect_policy())
|
||||
// Filter resolved IPs: a hostname that resolves to an internal
|
||||
// address (DNS rebinding) is refused at connect time, not just
|
||||
// by the string check above.
|
||||
.dns_resolver(crate::crawler::safety::safe_dns_resolver())
|
||||
.build()
|
||||
.context("build analysis http client")?;
|
||||
let vision = crate::analysis::vision::VisionClient::new(http, cfg);
|
||||
@@ -502,6 +506,7 @@ async fn spawn_analysis_daemon(
|
||||
// uptime + vision health.
|
||||
.no_proxy()
|
||||
.redirect(crate::crawler::safety::public_redirect_policy())
|
||||
.dns_resolver(crate::crawler::safety::safe_dns_resolver())
|
||||
.build()
|
||||
.context("build vision readiness http client")?;
|
||||
Some(Arc::new(crate::analysis::daemon::HttpVisionReadiness {
|
||||
@@ -586,6 +591,10 @@ async fn spawn_crawler_daemon(
|
||||
.redirect(crate::crawler::safety::safe_redirect_policy(
|
||||
cfg.download_allowlist.clone(),
|
||||
))
|
||||
// Reject any host that resolves to a private/internal IP (DNS
|
||||
// rebinding), complementing the string-level allowlist/private-IP
|
||||
// check which can't see post-resolution addresses.
|
||||
.dns_resolver(crate::crawler::safety::safe_dns_resolver())
|
||||
.cookie_provider(Arc::clone(&cookie_jar));
|
||||
if let Some(ua) = &cfg.user_agent {
|
||||
http_builder = http_builder.user_agent(ua);
|
||||
|
||||
Reference in New Issue
Block a user