flawopen.com/ssrf/Php

● CWE-918 · 높음
보안 연구 · FlawOpen

취약점 분석: Server-Side Request Forgery in PHP

How curl_exec in PHP exposes internal networks, and how to filter IPs using FILTER_VALIDATE_IP and CURLOPT_FOLLOWLOCATION.

💡 알기 쉬운 설명 (ELI5)

사무실 배달원에게 일반 상점에서 소포를 찾아오라고 하면서, 사장실 안의 잠긴 금고 주소를 적어주는 상황을 상상해 보세요. 배달원은 사내 출입증을 소지하고 있으므로 경비원을 통과해 금고를 열고 회사 기밀을 넘겨주게 됩니다.

핵심 개념 및 용어

Web Application Security
보안 개념 (Web Application Security): Core architecture component affected by CWE-918.
CWE-918
보안 개념 (CWE-918): Standard Common Weakness Enumeration classification for ssrf-php.
Defense-in-Depth
보안 개념 (Defense-in-Depth): Multi-layered engineering verification and runtime boundary isolation.

단계별 공격 실행 흐름

Step 1

신뢰할 수 없는 원격 URL 수신

애플리케이션이 웹훅 전송, 이미지 미리보기 등을 위해 사용자가 제출한 URL을 수신합니다.

Step 2

내부 네트워크 및 클라우드 메타데이터 주소 지정

공격자가 루프백 주소나 클라우드 메타데이터 URL(예: http://169.254.169.254/latest/meta-data/)을 지정합니다.

Step 3

제한 없는 내부 소켓 발신

서버의 HTTP 클라이언트가 사설 VPC 내부에서 IP 범위 검증 없이 요청을 전송합니다.

Step 4

클라우드 자격 증명 및 토큰 유출

내부 메타데이터 서비스가 요청을 신뢰하여 임시 IAM 보안 자격 증명 및 관리자 데이터를 반환합니다.

소스 코드 비교: 취약한 구현 vs 보안 패치

✕ 취약한 구현
<?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;
?>

엔지니어링 및 시스템 보안 강화 체크리스트

References