사용자가 준 URL로 이미지를 받아왔더니 내부 메타데이터가 새어나간 이유
문제 발생
프로필 이미지를 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을 정하는 경우에는 허용 목록이 비현실적이라 차단 목록에 의존하게 되고 우회에 계속 취약합니다.
해결 방안
-
가능하면 허용 목록으로 좁힙니다. 특정 이미지 CDN만 허용하는 식으로 요구를 줄일 수 있으면 그게 가장 확실합니다.
-
도메인이 아니라 해석된 IP를 검증합니다. OWASP가 DNS 리바인딩 대응으로 명시하는 절차입니다 — 도메인 이름을 해석한 뒤 그 결과 IP가 사설/로컬 주소가 아닌지 검증합니다. 문자열 검사만으로는
http://내도메인/이127.0.0.1로 해석되는 경우를 막지 못합니다. -
리다이렉트를 끕니다. 문서가 드는 방어 그대로 — 검증 우회를 막기 위해 HTTP 리다이렉트를 비활성화합니다. 허용된 주소로 시작해 내부 주소로 튀는 것이 전형적인 우회입니다.
await fetch(url, { redirect: "manual" });-
원격 응답을 그대로 돌려주지 않습니다. 문서가 경고하듯 원격 서버의 raw 응답을 클라이언트에 그대로 보내면 민감한 데이터가 새거나 추가 공격을 가능하게 합니다. 이미지라면 디코딩·재인코딩해서 저장합니다.
-
네트워크 계층에서도 막습니다. 애플리케이션 검증만 믿지 않습니다 — 아웃바운드 방화벽으로 내부 대역과 메타데이터 엔드포인트를 차단하면, 코드에 구멍이 나도 피해가 제한됩니다.
-
URL 전체를 받지 않는 설계도 검토합니다. OWASP의 제안대로 IP 주소나 도메인 이름을 따로 받아 서버가 URL을 조립하면 파싱 차이로 인한 우회 표면이 줄어듭니다. 업로드로 대체할 수 있으면 그게 더 낫습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.