flawopen.com/Server-Side Request Forgery/Swift
You give a robot courier an order slip. The courier is instructed to only visit public storefronts. If you don't inspect the address, the courier walks into your private company basement.
Calling reqwest::get(&url).await in Rust connects to any routable address. Attackers supply internal endpoints to probe internal microservices.
Vulnerabilities in modern Rust backend microservices that fetch remote avatars, webhooks, or open-graph cards have led to internal infrastructure port scanning.
CISA Cybersecurity Advisory & MITRE CVE repository.// VULNERABLE: Unchecked reqwest::get
use reqwest;
async fn fetch_remote_resource(user_url: &str) -> Result<Vec<u8>, reqwest::Error> {
// Attacker passes: http://169.254.169.254/latest/meta-data/
let resp = reqwest::get(user_url).await?;
let bytes = resp.bytes().await?;
Ok(bytes.to_vec())
}
// HARDENED: Resolve DNS, assert non-private IP, and disable redirects
use std::net::{IpAddr, ToSocketAddrs};
use reqwest::redirect::Policy;
use url::Url;
fn is_safe_ip(ip: IpAddr) -> bool {
match ip {
IpAddr::V4(ipv4) => {
!ipv4.is_loopback() &&
!ipv4.is_private() &&
!ipv4.is_link_local() &&
!ipv4.is_unspecified()
}
IpAddr::V6(ipv6) => {
!ipv6.is_loopback() &&
!ipv6.is_unspecified()
}
}
}
async fn fetch_remote_resource(user_url: &str) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let parsed_url = Url::parse(user_url)?;
if parsed_url.scheme() != "http" && parsed_url.scheme() != "https" {
return Err("Invalid URL scheme".into());
}
let host = parsed_url.host_str().ok_or("Missing host")?;
let port = parsed_url.port_or_known_default().unwrap_or(80);
// 1. Resolve host and inspect all IP addresses
let socket_addrs: Vec<_> = format!("{}:{}", host, port).to_socket_addrs()?.collect();
for sa in &socket_addrs {
if !is_safe_ip(sa.ip()) {
return Err("Access to private/internal IP rejected".into());
}
}
// 2. Build client with redirects disabled
let client = reqwest::Client::builder()
.redirect(Policy::none())
.timeout(std::time::Duration::from_secs(3))
.build()?;
let resp = client.get(parsed_url).send().await?;
let bytes = resp.bytes().await?;
Ok(bytes.to_vec())
}
The code uses to_socket_addrs() to evaluate all destination IPs. Checking !ipv4.is_private() and !ipv4.is_link_local() blocks RFC 1918 and IMDS endpoints. Policy::none() blocks redirect exploitation.
Ensure IPv6 addresses in fc00::/7 (ULA) are checked and blocked in production.
Type safety prevents memory corruption, but logical network vulnerabilities like SSRF are independent of memory safety.
cargo clippy
cargo audit
Url::parse to restrict schemes to http/https!ip.is_private() && !ip.is_link_local()Policy::none() on reqwest ClientYes, reqwest follows up to 10 redirects by default. You must explicitly configure Policy::none() to prevent SSRF redirect bypasses.