flawopen.com/ssrf/Rust
How reqwest allows internal network queries in Rust, and how to validate resolved socket addresses before connecting.
사무실 배달원에게 일반 상점에서 소포를 찾아오라고 하면서, 사장실 안의 잠긴 금고 주소를 적어주는 상황을 상상해 보세요. 배달원은 사내 출입증을 소지하고 있으므로 경비원을 통과해 금고를 열고 회사 기밀을 넘겨주게 됩니다.
Web Application SecurityCWE-918.CWE-918CWE-918): Standard Common Weakness Enumeration classification for ssrf-rust.Defense-in-Depth애플리케이션이 웹훅 전송, 이미지 미리보기 등을 위해 사용자가 제출한 URL을 수신합니다.
공격자가 루프백 주소나 클라우드 메타데이터 URL(예: http://169.254.169.254/latest/meta-data/)을 지정합니다.
서버의 HTTP 클라이언트가 사설 VPC 내부에서 IP 범위 검증 없이 요청을 전송합니다.
내부 메타데이터 서비스가 요청을 신뢰하여 임시 IAM 보안 자격 증명 및 관리자 데이터를 반환합니다.
// 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())
}
ip.is_private() && !ip.is_link_local().none() on reqwest Client을(를) 올바르게 구성하십시오.