ServerSideRequestForgery
1. SSRF(Server Side Request Forgery) 개요
- 서버 측 요청 위조(Server-Side Request Forgery) 공격은 공격자가 서버의 기능을 악용하여 내부 리소스에 무단 접근하는 보안 취약점입니다.
간단한 비유
회사 직원에게 "저 파일 좀 가져와줘"라고 부탁하는 것과 같습니다. 악의적인 요청자가 "내부 기밀 서류함에서 급여 명세서 가져와줘"라고 요청하면, 검증 없이 실행하는 직원(취약한 서버)이 내부 정보를 유출하게 됩니다.
2. 공격 동작 방식
- 대상 애플리케이션은 URL에서 데이터를 가져오거나, URL로 데이터를 발행하거나, 조작 가능한 URL에서 데이터를 읽는 기능이 있을 수 있습니다.
- 이 경 우 공격자는 다음과 같은 방법으로 해당 기능을 악용합니다.
2.1 URL 조작
- 공격자가 서버에서 실행되는 코드가 읽어올 URL을 제공하거나 수정
- 완전히 다른 URL 제공하거나 URL 구조 조작 (경로 탐색 등)
2.2 내부 서비스 접근
- 조작된 요청을 통해 다음과 같은 내부 자원에 접근:
- 클라우드 메타데이터: AWS의
http://169.254.169.254/에서 설정 정보 및 인증 키 추출 - 데이터베이스 HTTP 인터페이스: MongoDB 같은 NoSQL DB의 REST API 악용
- 내부 REST API: 외부에 노출되지 않은 내부 서비스 접근
- 로컬 파일:
file://URI를 통한 시스템 파일 읽기
3. 공격 예시
3.1 정상적인 사용
POST /api/fetch-image
Content-Type: application/json
{
"url": "https://example.com/public-image.jpg"
}
3.2 SSRF 공격
POST /api/fetch-image
Content-Type: application/json
{
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}
- 위 공격이 성공하면 AWS 인스턴스의 IAM 자격 증명을 탈취할 수 있어 클라우드 인프라 전체가 위험에 노출됩니다.
4. 주요 위험성
- 내부 네트워크 침투: 방화벽으로 보호된 내부 서비스 접근
- 클라우드 메타데이터 탈취: 인증 토큰 및 설정 정보 유출
- 데이터베이스 직접 접근: 인증 우회를 통한 민감 정보 탈취
- 입력 검증 우회: 신뢰할 수 없는 데이터를 신뢰하는 코드로 전달
5. 방어 방법
- SSRF 공격에 대한 방어는 애플리케이션 계층과 네트워크 계층에서 다층 방어 원칙을 적용해야 합니다.
5.1 애플리케이션 계층 방어
5.1.1 입력 검증 및 화이트리스트
신뢰할 수 있는 도메인만 허용:
const allowedDomains = ['api.trusted-service.com', 'cdn.example.com'];
const urlObj = new URL(userInput);
if (!allowedDomains.includes(urlObj.hostname)) {
throw new Error('허용되지 않은 도메인입니다');
}
IP 주소 검증 (Java 예시):
import org.apache.commons.validator.routines.InetAddressValidator;
public boolean isValidPublicIP(String ip) {
InetAddressValidator validator = InetAddressValidator.getInstance();
if (!validator.isValid(ip)) {
return false;
}
// 내부 IP 대역 차단
String[] privateCIDRs = {
"10.0.0.0/8",
"172.16.0.0/12",
"192.168.0.0/16",
"127.0.0.0/8",
"169.254.0.0/16" // AWS 메타데이터
};
return !isInPrivateRange(ip, privateCIDRs);
}
5.1.2 도메인 이름 검증
import validators
import socket
import ipaddress
def validate_domain(domain):
# 1. 도메인 형식 검증
if not validators.domain(domain):
return False
# 2. DNS 해석하여 IP 확인
try:
ip_addresses = socket.gethostbyname_ex(domain)[2]
for ip in ip_addresses:
ip_obj = ipaddress.ip_address(ip)
# 내부 IP인 지 확인
if not ip_obj.is_global:
return False
return True
except socket.gaierror:
return False
5.1.3 프로토콜 제한
const allowedProtocols = ['http:', 'https:'];
const urlObj = new URL(userInput);
if (!allowedProtocols.includes(urlObj.protocol)) {
throw new Error('허용되지 않은 프로토콜입니다');
}
// 리다이렉션 비활성화
const options = {
redirect: 'error', // 리다이렉션 차단
timeout: 5000 // 타임아웃 설정
};
5.1.4 요청 정당성 검증
외부 서비스와 통신 시 토큰 기반 검증:
// 대상 애플리케이션에서 생성한 랜덤 토큰 사용
const validationToken = generateRandomToken(20); // 20자 영숫자
const requestBody = {
authToken: validationToken,
businessData: validatedData
};
// POST 요청만 허용
const response = await fetch(validatedURL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(requestBody),
redirect: 'error'
});
5.2 네트워크 계층 방어
5.2.1 방화벽 규칙
# iptables 예시 - 내부 네트워크 접근 차단
iptables -A OUTPUT -d 10.0.0.0/8 -j REJECT
iptables -A OUTPUT -d 172.16.0.0/12 -j REJECT
iptables -A OUTPUT -d 192.168.0.0/16 -j REJECT
iptables -A OUTPUT -d 127.0.0.0/8 -j REJECT
iptables -A OUTPUT -d 169.254.169.254 -j REJECT
5.2.2 네트워크 분리
graph TB
A["웹 애플리케이션"] --> B["DMZ 네트워크"]
B --> C["방화벽"]
C --> D["외부 인터넷"]
C -.-> E["내부 네트워크 (차단)"]
style E fill:#ffcccc
style C fill:#ccffcc
5.2.3 DNS 정책
# DNS 서버 설정 예시
# 내부 DNS는 내부 도메인만 해석
internal_dns:
- domain: "*.company.local"
server: "10.0.0.53"
# 외부 DNS는 퍼블릭 도메인만 해석
external_dns:
- server: "8.8.8.8"
- server: "1.1.1.1"
5.3 클라우드 환경 보안
5.3.1 AWS IMDSv2 적용
클라우드 메타데이터 보호
클라우드 환경에서는 메타데이터 서비스 보호가 필수입니다. AWS의 경우 IMDSv2를 반 드시 적용하세요.
# IMDSv2 강제 설정
aws ec2 modify-instance-metadata-options \
--instance-id i-1234567890abcdef0 \
--http-tokens required \
--http-put-response-hop-limit 1
5.3.2 IAM 역할 최소 권한
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::specific-bucket/*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}