한양과학기술고등학교

에듀테크 프로젝트 대시보드

SSRF (서버 측 요청 위조)

SSRF (서버 측 요청 위조)

Server-Side Request Forgery. 사용자가 직접 내부망에 접근하는 게 아니라, "URL을 알려주면 서버가 대신 그 주소로 요청을 보내주는 기능"을 악용해 서버 자신을 대리인으로 삼아 방화벽 뒤 내부망·클라우드 메타데이터 서버를 두드리는 공격. XSS·CSRF·클릭재킹이 "브라우저"를 노렸다면, SSRF는 서버 자체의 네트워크 위치를 노린다.

취약한 기능: URL 미리보기 API POST /api/preview
게시판에 링크를 붙이면 썸네일을 보여주는 "URL 미리보기" 기능이 있다고 하자. 서버 코드는 사용자가 준 URL로 그대로 requests.get()을 날린다.
정상 이미지 ☁️ AWS 메타데이터 🔒 내부 Redis:6379 🗄️ 내부 관리자 API 📄 file:// 로컬 파일
방어(허용목록 + 내부 IP 차단) OFF
응답
// 아직 요청하지 않음
0
내부 정보 유출
0
차단됨
요청 경로 시각화
공격자 브라우저
외부
웹 서버
방화벽 안쪽
내부 자원
Redis·메타데이터·관리 API
핵심 원인: 방화벽은 "누가 요청했나"가 아니라 "요청이 어디서 왔나"만 본다. 웹 서버가 보낸 요청은 내부망 소속이라 자동으로 신뢰받는다. 공격자는 브라우저에서 내부 IP에 직접 접근할 수 없지만, 서버에게 "대신 가줘"라고 URL만 던지면 된다.
서버 요청 로그
[--:--:--]서버 대기 중...

실제로 왜 위험한가

  • 클라우드 메타데이터 탈취 — AWS/GCP/Azure 인스턴스는 169.254.169.254에 자격증명을 노출한다. 2019년 Capital One 유출 사고가 EC2 메타데이터 SSRF로 시작됐다.
  • 포트 스캐닝 — 응답 시간·에러 메시지 차이로 내부망의 어떤 포트가 열려 있는지 하나씩 알아낼 수 있다.
  • 방화벽 우회 — 외부에서 막힌 관리자 페이지·DB·캐시 서버를 서버를 거쳐 간접 접근한다.
  • file:// / gopher:// 같은 다른 스킴을 지원하는 라이브러리라면 로컬 파일 읽기나 임의 프로토콜 요청까지 가능하다.
# 취약한 FastAPI 코드 @app.post("/api/preview") async def preview(url: str): # ❌ 사용자가 준 URL을 검증 없이 그대로 요청 resp = requests.get(url, timeout=3) return {"content": resp.text[:500]}

방어 방법

  • 허용 목록(allowlist) — 미리 정해둔 도메인/스킴만 요청 허용. 사설 IP·loopback·link-local 대역은 차단.
  • DNS 재바인딩 주의 — 도메인 검증 후 실제 요청 시점에 IP가 바뀔 수 있으므로, 연결 직전에도 다시 확인.
  • 리다이렉트 금지 또는 재검증 — 정상 URL이 302로 내부 IP로 튕기는 우회를 막는다.
  • 네트워크 분리 — 이 기능을 처리하는 서버는 애초에 내부망 접근 권한이 없는 별도 네트워크에 둔다(egress 방화벽).
# 방어: 스킴 + 사설 IP 차단 import ipaddress, socket from urllib.parse import urlparse def is_safe_url(url: str) -> bool: p = urlparse(url) if p.scheme not in ("http", "https"): return False # file:// gopher:// 등 차단 ip = ipaddress.ip_address(socket.gethostbyname(p.hostname)) if ip.is_private or ip.is_loopback or ip.is_link_local: return False # 169.254.x.x, 127.x, 10.x 등 return True
토글을 켜고 다시 "내부망" 프리셋들을 눌러보면, 서버가 요청을 보내기 전에 차단하는 걸 확인할 수 있다.