Hack&Dev/Web
SSRF(Server-Side Request Forgery)와 우회 기법 (Dreamhack SSRF 문제 curling 기준)
스우스우03
2025. 2. 23. 21:24
1. SSRF 개요
- SSRF(Server-Side Request Forgery)는 공격자가 서버 측에서 요청을 보내도록 유도하여 내부 네트워크 자원에 접근하거나 외부 요청을 조작하는 취약점이다.
- 일반적으로 방화벽 뒤에 있는 내부 시스템을 공격하는 데 사용되며, 종종 클라우드 인프라의 메타데이터 서비스 (AWS EC2, Google Cloud 등)에 접근하는 데 악용된다.
- 본 문서는 Dreamhack의 "curling" 문제를 기반으로 SSRF 취약점과 그 우회 기법, 방어 기법을 설명한다.
2. 특정 도메인 필터링을 우회하는 기법
- 개발자는 /api/v1/test/curl 엔드포인트를 구현할 때 특정 도메인(example.com, tools.example.com)만 요청을 보낼 수 있도록 필터링을 적용하였다.
- 그러나 URL 구조를 악용하면 필터링을 우회할 수 있다.
2.1 @ 기호를 활용한 호스트 우회
- 웹 표준에 따르면, @ 기호를 포함한 URL은 user:password@host 형식으로 해석된다.
- 즉, http://example.com@192.168.1.100:8000/api/v1/test/internal와 같은 URL이 있을 경우:
- 필터링 로직에서는 http://example.com을 포함하고 있으므로 허용됨.
- 실제 요청은 192.168.1.100:8000으로 전달됨.
- 즉, http://example.com@192.168.1.100:8000/api/v1/test/internal와 같은 URL이 있을 경우:
- 이는 SSRF 공격에서 자주 이용되는 기법으로, requests, curl과 같은 여러 라이브러리에서 정상적으로 처리되는 경우가 많다.
- 그러나 일부 최신 라이브러리나 특정 환경에서는 @ 기호가 포함된 URL을 엄격하게 파싱하여 예외 처리할 수도 있으므로, 환경에 따라 동작 여부가 달라질 수 있다.
curl "<http://example.com@192.168.1.100:8000/api/v1/test/internal>"
- 위 요청은 필터링을 우회하면서 내부 서버의 엔드포인트에 직접 접근하는 효과를 가진다.
3. 엔드포인트 차단 우회 기법
- 개발자는 내부 엔드포인트(/api/v1/test/internal)에 대한 접근을 차단하기 위해 다음과 같은 조건을 사용하였다.
if url.endswith('/test/internal'):
return "Access Denied"
- 그러나 이 방식은 단순한 문자열 일치 검사를 수행하므로, 다양한 방법으로 우회할 수 있다.
3.1 Query String을 이용한 우회
- endswith('/test/internal')은 정확한 경로와 일치하는지 확인하지만, ?foo=bar와 같은 쿼리 문자열이 추가되면 조건이 일치하지 않게 된다.
curl "<http://example.com@192.168.1.100:8000/api/v1/test/internal?foo=bar>"
- 필터링 로직에서는 /test/internal?foo=bar는 /test/internal과 다르므로 접근 차단이 우회됨.
- 내부 서버는 정상적으로 해당 요청을 처리하고 응답을 반환할 가능성이 높음.
3.2 URL 인코딩 및 변조 기법
- /test/internal%2F : %2F(슬래시 인코딩) 추가
- /test/internal/. : .(현재 디렉토리) 추가
- /test/internal;/ : ;(세미콜론) 추가
이러한 기법들은 단순한 문자열 비교를 우회하는 데 사용될 수 있다.
4. SSRF 방어 기법
4.1 내부 IP 차단 범위 확장과 DNS 재바인딩 방어
- 단순히 127.0.0.1, 192.168.1.100, 169.254.169.254만 차단하는 방식은 우회 가능성이 크다.
- 따라서 전체 사설 IP 대역을 차단하는 것이 일반적이다.
4.1.1 사설 IP 대역 차단
- 다음과 같은 사설 IP 대역을 포함하여 차단해야 한다.
- 127.0.0.1/8 (루프백 주소)
- 10.0.0.0/8 (사설 IP)
- 172.16.0.0/12 (사설 IP)
- 192.168.0.0/16 (사설 IP)
- 169.254.169.254/32 (클라우드 메타데이터 서버)
import ipaddress
def is_internal_ip(ip):
private_networks = [
ipaddress.ip_network("127.0.0.0/8"),
ipaddress.ip_network("10.0.0.0/8"),
ipaddress.ip_network("172.16.0.0/12"),
ipaddress.ip_network("192.168.0.0/16"),
ipaddress.ip_network("169.254.169.254/32")
]
ip_addr = ipaddress.ip_address(ip)
return any(ip_addr in network for network in private_networks)
4.1.2 DNS 재바인딩 방어
- DNS 재바인딩 공격에서는 처음에는 외부 IP를 반환하다가, 요청 시점에서 내부 IP로 변경할 수 있다.
- 이를 방지하기 위해 DNS 조회 후 실제 IP가 내부 IP인지 확인하는 과정이 필요하다.
import socket
def is_allowed_domain(url):
parsed_url = urlparse(url)
hostname = parsed_url.hostname
try:
ip = socket.gethostbyname(hostname)
return not is_internal_ip(ip)
except socket.gaierror:
return False
- 이렇게 하면 도메인이 정상적으로 보이더라도 내부 IP로 변환되는 것을 방지할 수 있다.
4.2 URL 필터링 로직 강화
- 단순한 endswith() 방식이 아닌, 정규 표현식을 사용하여 보다 엄격한 검사를 수행해야 한다.
import re
def is_blocked_endpoint(url):
return bool(re.match(r'^https?://[^/]+/api/v1/test/internal$', url))
- 이 방식은 정확히 /api/v1/test/internal 경로에 해당하는 요청만 차단하므로, ?foo=bar와 같은 우회 기법을 막을 수 있다.
4.3 Outbound 요청 제한(방화벽 및 프록시 활용)
- 서버에서 외부로 나가는 요청을 엄격히 제한하고, 필요한 도메인만 허용하는 방식을 적용할 수 있다.
- 화이트리스트 방식 적용
- 특정 서비스(example.com, tools.example.com)에 대한 통신만 허용하고 나머지는 차단.
- 방화벽 설정
- Egress filtering을 적용하여 내부 요청을 차단.
- 클라우드 환경 보호
- AWS, GCP, Azure 등의 클라우드 환경에서는 Security Group, VPC Firewall, Service Control Policy 등을 통해 내부 메타데이터 서버로의 직접 요청을 차단.
- 화이트리스트 방식 적용
- 예를 들어, AWS 환경에서는 다음과 같이 메타데이터 서버에 대한 접근을 방지할 수 있다.
iptables -A OUTPUT -d 169.254.169.254 -j DROP