본문으로 건너뛰기
개발 머꼬
개발 노트보안
hohyeon.dev26

사용자가 준 URL로 이미지를 받아왔더니 내부 메타데이터가 새어나간 이유

  • #Engineering Note
  • #보안

문제 발생

프로필 이미지를 URL로도 등록할 수 있게 만들었습니다.

const res = await fetch(userProvidedUrl);
await saveAvatar(await res.arrayBuffer());

http://169.254.169.254/...이나 http://localhost:9200/... 같은 값이 들어오면, 외부에서 접근할 수 없는 내부 서비스에 우리 서버가 대신 요청을 보냈습니다.

원인 분석

서버가 공격자의 대리인이 됩니다. OWASP는 SSRF를 애플리케이션을 악용해 내부/외부 네트워크나 머신 자체와 상호작용하게 만드는 공격 벡터로 정의하고, 외부 이미지 다운로드나 사용자 제공 웹훅 URL 처리 같은 기능이 그 통로가 된다고 설명합니다.

우리 서버는 방화벽 안쪽에 있습니다. 브라우저가 못 가는 곳(클라우드 메타데이터 엔드포인트, 내부 관리 API, DB 관리 화면)에 우리 서버는 갈 수 있습니다.

방어가 어려운 이유도 문서에 있습니다. OWASP는 두 경우를 나눕니다 — 신뢰하는 내부 서비스와만 통신하는 경우에는 대상이 미리 정해져 있으니 허용 목록이 잘 동작하지만, 사용자가 외부 리소스 URL을 정하는 경우에는 허용 목록이 비현실적이라 차단 목록에 의존하게 되고 우회에 계속 취약합니다.

해결 방안

  1. 가능하면 허용 목록으로 좁힙니다. 특정 이미지 CDN만 허용하는 식으로 요구를 줄일 수 있으면 그게 가장 확실합니다.

  2. 도메인이 아니라 해석된 IP를 검증합니다. OWASP가 DNS 리바인딩 대응으로 명시하는 절차입니다 — 도메인 이름을 해석한 뒤 그 결과 IP가 사설/로컬 주소가 아닌지 검증합니다. 문자열 검사만으로는 http://내도메인/127.0.0.1로 해석되는 경우를 막지 못합니다.

  3. 리다이렉트를 끕니다. 문서가 드는 방어 그대로 — 검증 우회를 막기 위해 HTTP 리다이렉트를 비활성화합니다. 허용된 주소로 시작해 내부 주소로 튀는 것이 전형적인 우회입니다.

await fetch(url, { redirect: "manual" });
  1. 원격 응답을 그대로 돌려주지 않습니다. 문서가 경고하듯 원격 서버의 raw 응답을 클라이언트에 그대로 보내면 민감한 데이터가 새거나 추가 공격을 가능하게 합니다. 이미지라면 디코딩·재인코딩해서 저장합니다.

  2. 네트워크 계층에서도 막습니다. 애플리케이션 검증만 믿지 않습니다 — 아웃바운드 방화벽으로 내부 대역과 메타데이터 엔드포인트를 차단하면, 코드에 구멍이 나도 피해가 제한됩니다.

  3. URL 전체를 받지 않는 설계도 검토합니다. OWASP의 제안대로 IP 주소나 도메인 이름을 따로 받아 서버가 URL을 조립하면 파싱 차이로 인한 우회 표면이 줄어듭니다. 업로드로 대체할 수 있으면 그게 더 낫습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.