flawopen.com/ssrf/Php
How curl_exec in PHP exposes internal networks, and how to filter IPs using FILTER_VALIDATE_IP and CURLOPT_FOLLOWLOCATION.
オフィスの宅配便受取係に小包を取りに行くよう依頼する際、外部の店舗ではなく役員室の金庫の部屋番号を指定する場面を想像してください。受取係は社内通行バッジを持っているため、疑われることなく金庫室に入り機密書類を持ち出してしまいます。
Web Application SecurityCWE-918.CWE-918CWE-918):Standard Common Weakness Enumeration classification for ssrf-php.Defense-in-Depthアプリケーションがユーザーから指定されたURLを受け取り、画像の取得やWebhook配信を実行します。
攻撃者がクラウドメタデータサービス(例: http://169.254.169.254/latest/meta-data/)や内部ループバックURLを指定します。
サーバーのHTTPクライアントがプライベートVPC内からリクエストを発行し、内部IPチェックを行いません。
メタデータサービスがリクエストを信頼し、一時的なIAM認証情報や内部APIデータを攻撃者に返却します。
<?php
// VULNERABLE: file_get_contents on user-controlled URL
$url = $_GET['url'];
// Attacker input: http://169.254.169.254/latest/meta-data/
$content = file_get_contents($url);
echo $content;
?>
<?php
// HARDENED: Parse URL, resolve DNS, filter private IPs, and disable redirects
$url = $_GET['url'] ?? '';
$parsed = parse_url($url);
if (!$parsed || !in_array(strtolower($parsed['scheme'] ?? ''), ['http', 'https'], true)) {
http_response_code(400);
exit('Invalid URL scheme');
}
$host = $parsed['host'] ?? '';
$ips = gethostbynamel($host);
if (!$ips) {
http_response_code(400);
exit('Unable to resolve host');
}
// Check every resolved IP address
foreach ($ips as $ip) {
if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
http_response_code(403);
exit('Access to private/internal address denied');
}
}
// Fetch using cURL with redirects disabled
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false, // Prevent redirect bypasses
CURLOPT_TIMEOUT => 3,
CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
]);
$response = curl_exec($ch);
curl_close($ch);
echo $response;
?>