1. 결함의 본질

서버가 외부 입력을 받아 다른 곳으로 HTTP 요청을 보낼 때, 요청의 목적지/내용을 공격자가 조작할 수 있는 결함. 요청을 보내는 주체가 서버라서, 서버 권한으로 외부에서 못 닿는 자원(내부망, localhost, 클라우드 메타데이터 169.254.169.254)에 접근 가능

  • CSRF와의 차이: 피해자 브라우저가 아니라 서버가, 서버 권한으로 요청을 전송

2. 공격 대상

서버 내부망과 그 서버가 신뢰하는 자원. 외부에서 직접 못 닿지만 서버는 닿을 수 있는 것들(내부 API, DB, 클라우드 메타데이터 엔드포인트, 관리자 패널 등) 서버를 대리인 삼아 내부를 공격

3. 전제 조건

  • 서버가 외부 입력을 받아 다른 곳으로 요청을 보냄 (프록시·중계·URL fetch·웹훅·이미지 미리보기 등)
  • 그 요청의 목적지(URL) 또는 내용(바디·파라미터)에 입력이 반영
  • 목적지/내용에 대한 검증이 없거나 부실

4. 공격 경로

입력이 요청의 어디에 반영되느냐로 결정

  • 목적지(URL) 조작: 서버가 fetch(입력_URL) 하면, http://169.254.169.254/, http://localhost:관리포트 를 넣어 내부 자원 응답을 받아내는 전형적 SSRF
  • 내용(바디) 조작: 목적지는 고정 + 바디에 입력 → 서버가 고정했다고 믿는 값을 덮어쓸 수 있음

HPP 예시: 서버가 data = f"title={title}&body={body}&user=guest" 로 조립해 내부에 전송
body에 &user=admin 을 끼우면 → …&body=내용&user=admin&user=guest → user 파라미터 중복
Flask는 중복 시 첫 번째를 채택 → guest로 고정한 권한이 admin으로 상승

5. 판별점

  • 서버가 입력을 받아 또 다른 요청을 보내는가?
  • 그 요청의 목적지나 바디를 입력이 건드리는가?
  • 목적지형이면 → 내부 주소(localhost, 169.254.169.254, 사설 IP)로 돌릴 수 있는가?
  • 바디형이면 → 서버가 고정한 값을 파라미터 중복/인젝션으로 덮을 수 있는가?

6. 방어

  • 목적지 검증: 허용 목록(allowlist) → 입력 URL을 사용자가 못 정하고 정해진 목적지만 접근
  • 입력 분리/인코딩: 바디 조립 시 입력을 URL 인코딩해 &·= 같은 구분자 주입 불가
    → body를 인코딩만 했어도 &user=admin 무력화
  • 내부망 분리: 내부 API가 외부 요청을 그대로 신뢰하지 않게, 인증/네트워크 분리
  • 고정값을 입력과 안 섞기: user처럼 서버가 정하는 값은 입력과 같은 문자열로 조립하지 말 것