728x90
— URL 파싱 우회·메타데이터 노출·내부서비스 접근을 막기 위한 설계·운영·탐지 가이드
개요 · 배경
- 정의: SSRF(Server-Side Request Forgery)는 애플리케이션이 사용자 입력 URL을 서버 측에서 요청하도록 허용할 때, 공격자가 이를 악용해 내부망·관리용 엔드포인트·링크로컬 메타데이터 등에 접근하게 되는 취약점입니다.
- 주요 목표
- 클라우드/호스트 메타데이터 노출(예: 링크로컬
169.254.169.254
,fe80::/10
대역 등) - 내부 관리용 서비스 스캔 및 상호작용(예: 오케스트레이션/런타임/키-밸류 스토어/모니터링 에이전트/DB/메일·큐·캐시 등)
- 파일/토큰/자격증명 노출(
file://
등 비HTTP 스킴 허용 시) - 프로토콜 스머글링(예:
gopher://
,dict://
, FastCGI 등)로 RCE/상태 변조로 확장
- 클라우드/호스트 메타데이터 노출(예: 링크로컬
공격면(Attack Surface) · 빈출 시나리오
- URL 입력/프록시형 기능: 이미지 프록시, 웹훅 테스트, 미리보기(Preview), RSS/웹캠/건강체크, 서버-사이드 렌더링 등
- 메타데이터 엔드포인트: 링크로컬(예:
169.254.169.254
) 또는 내부 도메인으로 노출된 인스턴스/작업/자격증명/구성 정보 - 내부 관리용 API: 오케스트레이션(제어 포트), 런타임 데몬(관리 소켓/포트), 키-밸류 스토어(기본 포트), 모니터링 에이전트(명령 실행 옵션), 메일/큐/캐시/검색 등
- URL 파서 불일치: 언어/라이브러리별
@
,#
,\
처리 차이, 인증정보/프래그먼트/역슬래시 혼동 - 스킴 남용:
file://
,ftp://
,ldap://
,dict://
,tftp://
,gopher://
,jar:
,netdoc://
등 런타임이 지원 시 악용
필터 우회 기법(요약 · 방어 관점에서 이해해야 할 포인트)
아래는 “이런 기법이 존재한다”는 수준의 인지용이며, 방어 설계 시 정규화·재검증이 필요한 이유를 설명합니다. (악용 목적의 상세 페이로드는 제외)
- 대상 표기 변조
localhost/127.0.0.1/::1/0.0.0.0
변형,127.0.0.0/8
내 임의 IP, 축약형(http://0/
,127.1
)- 십진(도트·무도트), 8진, 16진, 혼합 인코딩, 이중/삼중 URL 인코딩
- 리다이렉트 체인 악용
- Whitelist 도메인 → 30x → 내부/메타데이터로 전환
- 307/308로 메서드/바디 유지 리다이렉트
- DNS 기법
- 동적 해석/재바인딩(한 번은 외부, 다음은 내부로 해석)
- 서브도메인 패턴으로 내부 IP를 가리키는 서비스(예:
<IP>.domain
) 악용
- 파서 불일치·희귀 문자
\
,@
이중 사용, 프래그먼트#
혼동, 유니코드 숫자/동그라미 문자 등
- 비HTTP 스킴
- 런타임/프레임워크가 지원할 경우 파일 읽기/로컬 소켓/저수준 프로토콜 상호작용 발생
방어 설계(핵심 원칙 5가지)
- 아키텍처 우선: 서버가 임의 URL을 대신 호출하지 않게 설계
- 가능하면 ID(식별자)만 입력받고, 서버 내부 매핑으로 실제 대상 결정
- 강력한 허용목록(Allowlist)
- 스킴:
http/https
만. 나머지 전부 불허 - 호스트: 필요한 정확 일치 FQDN만 허용(와일드카드 지양)
- 포트: 80/443 등 필요한 범위만
- 스킴:
- 정규화 후 검증(Normalize-then-Validate)
- IDNA/Punycode, IPv6·포트 기본값,
@/#/\
제거, 이중 인코딩/유니코드 숫자 해제 후 검사 - 리다이렉트 매 hop 최종 목적지까지 동일 정책 재검증
- IDNA/Punycode, IPv6·포트 기본값,
- 요청 제한
- GET만(필요 시), 임의 헤더/바디 전송 금지, 타임아웃·재시도 제한
- 응답 크기/타입 제한, 캐시/리소스 폭주 방지
- 네트워크 차단
- 애플리케이션에서 내부망·메타데이터 대역으로 직접 이그레스 불가
- Egress 전용 게이트웨이/프록시만 통과 → 게이트웨이에서 최종 도메인 재검증
300x250
안전한 구현 예시(요지) — 정규화·허용목록·리다이렉트 재검증
아래 코드는 개념 예시입니다. (사내 표준 라이브러리/헬퍼로 공통화 권장)
1. Python (요지)
import ipaddress, socket, urllib.parse, requests
ALLOWED_SCHEMES = {"http", "https"}
ALLOWED_HOSTS = {"api.example.corp", "assets.example.corp"}
ALLOWED_PORTS = {80, 443}
BLOCKED_NETS = [
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.0.0/16"), # 링크로컬(메타데이터)
ipaddress.ip_network("::1/128"),
ipaddress.ip_network("fc00::/7"), # ULA
ipaddress.ip_network("fe80::/10"), # 링크로컬(IPv6)
]
def is_private(host):
addrs = socket.getaddrinfo(host.encode("idna").decode(), None)
for _, _, _, _, sa in addrs:
ip = ipaddress.ip_address(sa[0])
if any(ip in n for n in BLOCKED_NETS):
return True
return False
def safe_fetch(input_url, max_redirects=3):
p = urllib.parse.urlsplit(input_url)
if p.scheme.lower() not in ALLOWED_SCHEMES: raise ValueError("scheme")
host = (p.hostname or "").lower()
if host not in ALLOWED_HOSTS: raise ValueError("host")
port = p.port or (443 if p.scheme=="https" else 80)
if port not in ALLOWED_PORTS: raise ValueError("port")
if is_private(host): raise ValueError("private")
url = urllib.parse.urlunsplit((p.scheme, f"{host}:{port}", p.path or "/", "", ""))
s = requests.Session()
for _ in range(max_redirects+1):
r = s.get(url, allow_redirects=False, timeout=5)
if 300 <= r.status_code < 400 and "Location" in r.headers:
loc = urllib.parse.urljoin(url, r.headers["Location"])
q = urllib.parse.urlsplit(loc)
h = (q.hostname or "").lower()
prt = q.port or (443 if q.scheme=="https" else 80)
if q.scheme not in ALLOWED_SCHEMES: raise ValueError("redir-scheme")
if h not in ALLOWED_HOSTS: raise ValueError("redir-host")
if prt not in ALLOWED_PORTS: raise ValueError("redir-port")
if is_private(h): raise ValueError("redir-private")
url = urllib.parse.urlunsplit((q.scheme, f"{h}:{prt}", q.path or "/", "", ""))
continue
return r
raise ValueError("too-many-redirects")
2. 게이트웨이/리버스 프록시(아이디어)
- 스킴 차단:
file|ftp|ldap|dict|tftp|gopher|jar|netdoc
- 내부 IP/링크로컬 패턴 차단:
127.*
,10.*
,172.16-31.*
,192.168.*
,169.254.*
,::1
,fc00::/7
,fe80::/10
- 인코딩/역슬래시/이중
%25
탐지 시 고강도 검사 또는 차단 - 리다이렉트 체인 최종 목적지 재검증(게이트웨이 레벨에서 강제)
네트워크·플랫폼 레벨 차단
1. 이그레스 기본 DENY(호스트 방화벽 개념 예시)
# 링크로컬(메타데이터)·루프백·사설 대역으로의 아웃바운드 차단
iptables -A OUTPUT -d 169.254.169.254 -j DROP
iptables -A OUTPUT -d 127.0.0.0/8 -j DROP
iptables -A OUTPUT -d 10.0.0.0/8 -j DROP
iptables -A OUTPUT -d 172.16.0.0/12 -j DROP
iptables -A OUTPUT -d 192.168.0.0/16 -j DROP
2. Egress 게이트웨이/프록시 경유 강제
- 애플리케이션은 직접 외부로 나가지 못하고, 지정 게이트웨이만 통과
- 게이트웨이에서 허용 도메인 집합·최종 목적지 재검증·로깅 수행
3. 메타데이터 접근 보호(일반 원칙)
- 가능하면 비활성화 또는 토큰·헤더 기반 방어·홉 제한 등 강제 정책
- 앱 레벨에서 임의 헤더 주입 금지(메타데이터 보호 헤더 우회 차단)
- 네트워크에서 링크로컬 경로 차단 및 필요한 워크로드만 프록시 경유
내부 서비스 악용(고급 기법) 대비
특정 제품명이 아닌 유형·행동 중심으로 방어 포인트만 제시합니다.
- 키-밸류/캐시/검색: 기본 포트로 무인증 쓰기/명령이 가능한 경우 존재 → 네트워크 접근 차단, 바인드 주소 제한, 인증/ACL 필수
- 메일/큐/전송 프로토콜: 저수준 프로토콜을
gopher://
등으로 직접 구사 가능 → 스킴 차단 + 게이트웨이 레벨 프로토콜 필터링 - 실행 게이트웨이/런타임: FastCGI/WSGI와 같은 로컬 실행 인터페이스 노출 시 RCE 전개 → 내부망 바인드 + 소켓 권한 + 외부 경로 차단
- DNS: 내부 DNS로 존 전송 유도(AXFR) 가능성 → 권한 제한·전송 ACL, 로깅 및 비정상 트랜잭션 탐지
탐지·모니터링(벤더 중립)
1. SIEM/로그 룰 아이디어(개념 표현)
- 목적지 IP/대역
127.0.0.0/8
,169.254.0.0/16
, 사설 대역10/8
,172.16/12
,192.168/16
이그레스 시 경고
- 도메인 패턴
- 내부 IP를 지시하는 동적 서브도메인 패턴 또는 테스트 도메인 악용 징후
- 포트/서비스
- 관리용/제어용 포트(예: 오케스트레이션, 런타임, K/V 스토어, 모니터링 에이전트 등) 접근 탐지
- 문자열 패턴
- 무도트 십진/8진/16진 IP,
\
,@
,#
, 과도한%25
(이중 인코딩), 유니코드 숫자
- 무도트 십진/8진/16진 IP,
- 행위 패턴
- 짧은 시간 내 30x 연속 리다이렉트 후 차단/실패 반복
- 동일 파라미터에서 인코딩 변종 다수 시도
2. 엔드포인트/에이전트·프록시 로그
- 링크로컬/사설 대역으로의 아웃바운드를 DROP하는 방화벽/프록시 로그를 상승도(중/높음)로 티켓화
- 게이트웨이에서 허용목록 불일치/리다이렉트 최종지 차단 발생 시 알림
안전한 검증 절차
- 샌드박스 네트워크에서만 수행(격리·가짜 메타데이터·더미 내부서비스)
- 우회기법 카탈로그(표기/인코딩/리다이렉트/파서 불일치/비HTTP 스킴)를 전수 테스트
- 성공 기준
- 비HTTP·내부·메타데이터 모두 차단
- 리다이렉트 최종지 재검증 통과 실패
- 게이트웨이 로그에 정상 차단·알림 생성
운영 체크리스트(역할별)
- 개발자
- 외부 입력 URL 대신 리소스 ID 매핑
- http/https·허용 FQDN·허용 포트만
- 정규화 후 검증, 리다이렉트 매 hop 재검증
- 임의 헤더·바디 금지, 타임아웃/응답 크기 제한
- 에러/차단 사유 명확 로깅
- 플랫폼/인프라
- Egress 기본 DENY, 게이트웨이 강제 경유
- 링크로컬/사설 대역 라우팅·방화벽 차단
- 메타데이터 접근 비활성화/제한/토큰·헤더 강제
- 내부 관리용 서비스 로컬 바인드·인증·TLS/ACL
- 보안팀
- SIEM 룰: 내부 대역 이그레스·리다이렉트 이상·인코딩 변종 탐지
- 분기별 회귀 테스트: 런타임/라이브러리 업데이트 시 URL 파서 불일치 점검
- 보안 게이트: 코드리뷰/아키텍처 리뷰에 SSRF 방어 항목 포함
예시(방어 검증용 · 안전한 방향의 샘플)
- 입력 정규화: IDNA, 디코딩 재귀 1회, 역슬래시 → 슬래시 치환 후 재파싱
- 목적지 검증: 스킴·호스트·포트 허용목록, DNS 해석된 모든 A/AAAA가 사설/링크로컬인지 검사
- 리다이렉트: 자동따라가기 금지 → 매 hop 수동 검증 후 허용 시에만 진행
- 네트워크: 애플리케이션 서브넷에서 링크로컬·사설 경로 불가, 게이트웨이만 통과
- 로그: 차단 사유(사설대역, 비허용 스킴, 리다이렉트 내부유도 등)와 파라미터 해시값만 기록(민감정보 최소화)
즉시 적용 스피드런
- 앱에 정규화→허용목록→리다이렉트 재검증 패턴 삽입
- 호스트/프록시에서 링크로컬·사설 대역 이그레스 DROP
- 메타데이터 접근 비활성/제한 + 헤더/토큰 강제
- 탐지 룰 배포(내부 대역 이그레스·인코딩 변종·리다이렉트 이상)
- 샌드박스 전수 테스트로 우회 기법 차단 확인
728x90
그리드형(광고전용)
댓글