이미지 프록시를 공개했다가 내부 네트워크까지 열어버린 이유
외부 이미지 URL을 받아 서버가 대신 내려받아 주는 프록시는 편리합니다. 브라우저의 CORS 제약을 피할 수 있고, 이미지 형식을 통일하거나 변환 결과를 캐시하기도 쉽습니다.
하지만 이 기능에는 중요한 전제가 있습니다. 사용자가 입력한 주소로 서버가 직접 요청을 보낸다는 점입니다. 접근 통제가 빠지거나 목적지 검증이 약하면 단순한 이미지 도구가 서버 측 요청 위조, 즉 SSRF(Server-Side Request Forgery) 통로가 될 수 있습니다.
이번 글에서는 공개 파일 서비스를 점검하다 발견한 프록시 문제와, 사용처가 없는 기능을 보완하는 대신 제거한 이유를 정리해보겠습니다.
문제는 이미지가 아니라 서버의 네트워크 권한이었습니다
프록시의 기본 흐름은 단순했습니다.
사용자 입력 URL
↓
서버가 외부 주소로 요청
↓
응답을 파일로 저장하거나 사용자에게 전달브라우저가 직접 접근할 수 없는 주소라도 서버에서는 접근할 수 있습니다. 서버가 데이터베이스, 관리 도구, 메타데이터 서비스, 다른 내부 애플리케이션과 같은 네트워크에 연결되어 있다면 프록시 요청도 그 접근 권한을 함께 사용하게 됩니다.
따라서 공격자는 프록시를 통해 외부 인터넷이 아니라 서버에서만 보이는 주소를 요청할 수 있습니다. 응답 본문이 그대로 노출되지 않더라도 상태 코드, 응답 시간, 오류 차이를 비교해 내부 서비스의 존재 여부를 추정할 수 있습니다.
OWASP도 SSRF를 방어할 때 애플리케이션 계층의 입력 검증과 네트워크 계층의 접근 통제를 함께 적용하도록 권고합니다. 특히 신뢰할 수 있는 목적지가 정해져 있다면 허용 목록을 사용하는 방식이 우선입니다. OWASP SSRF 방어 가이드
인증 경계에서 프록시만 빠져 있었습니다
파일 서비스에는 관리자 화면과 업로드 API를 보호하는 인증 규칙이 있었습니다. 그러나 외부 URL을 가져오는 프록시는 그 규칙의 대상에 포함되지 않았습니다.
인증 규칙이 다음처럼 일부 경로만 나열되는 구조라면 새 API를 추가할 때 누락되기 쉽습니다.
보호 대상
- 관리자 화면
- 이미지 업로드 API
누락
- 외부 URL 가져오기 API프록시 내부에서도 http와 https 프로토콜인지 확인하고 있었지만, 이것만으로는 충분하지 않았습니다. 사설 주소, 루프백 주소, 링크 로컬 주소도 모두 정상적인 HTTP 목적지가 될 수 있기 때문입니다.
실제 점검에서는 인증 없이 서버 내부에서만 접근 가능한 자원에 요청이 도달하는 것을 확인했습니다. 이 시점에서 문제를 단순한 인증 누락이 아니라 SSRF 취약점으로 판단했습니다.
디스크 캐시도 또 다른 공격 표면이었습니다
프록시는 같은 이미지를 반복해서 가져오지 않도록 응답을 디스크에 저장하고 있었습니다. 기능만 보면 합리적이지만 다음 제한이 없으면 공격자가 저장 공간을 소모시킬 수 있습니다.
- 응답 크기 상한
- 다운로드 시간 제한
- 사용자별 또는 전체 요청 횟수 제한
- 캐시 총용량 제한
- 오래된 항목을 지우는 퇴거 정책
- 허용할 콘텐츠 유형
서로 다른 URL을 계속 요청하면 캐시 키도 계속 늘어납니다. 서버가 응답 전체를 메모리에 받은 뒤 파일로 저장한다면 큰 응답으로 메모리와 디스크를 동시에 압박할 수도 있습니다.
SSRF는 목적지 접근 문제로 시작하지만, 프록시가 수행하는 다운로드와 저장 작업까지 함께 살펴봐야 합니다.
사용처를 찾은 뒤 기능 자체를 제거했습니다
처음에는 인증과 주소 검증을 추가해 프록시를 보완하는 방안을 검토했습니다. 그러나 호출 경로를 검색해보니 현재 애플리케이션 어디에서도 이 기능을 사용하지 않았습니다. 남아 있던 캐시 항목도 검증 과정에서 생성된 것뿐이었습니다.
사용하지 않는 네트워크 프록시는 유지할 이유보다 위험이 더 큽니다.
- 외부 입력을 받아 서버 권한으로 네트워크 요청을 보냅니다.
- 잘못된 검증 하나가 내부 접근으로 이어질 수 있습니다.
- 다운로드와 캐시 때문에 CPU, 메모리, 네트워크, 디스크를 사용합니다.
- 실제 사용자가 없어도 보안 패치와 모니터링 비용은 계속 발생합니다.
그래서 이번에는 방어 코드를 덧붙이는 대신 프록시 라우트와 관련 캐시 로직을 제거했습니다. 기능이 필요하지 않다면 공격 표면을 없애는 것이 가장 단순하고 확실한 대응입니다.
제거하기 전에는 코드 검색만으로 결론을 내리지 않고, 호출부와 생성된 URL, 배포 환경의 접근 로그, 캐시 파일을 함께 확인하는 것이 좋습니다. 동적 호출이나 외부 클라이언트가 있을 수 있기 때문입니다.
프록시가 꼭 필요하다면 무엇을 확인해야 할까요?
외부 자원을 가져오는 기능이 제품 요구사항이라면 인증 하나만 추가해서는 부족합니다. 다음 계층을 함께 설계해야 합니다.
1. 목적지를 허용 목록으로 제한합니다
가능하다면 사용 가능한 도메인과 포트를 명시적으로 제한합니다. 문자열에 특정 단어가 포함되지 않는지 확인하는 차단 목록보다, 허용한 목적지만 통과시키는 방식이 안전합니다.
URL 파싱 결과의 스킴, 호스트, 포트를 각각 검증하고 사용자 정보 영역이나 모호한 주소 표기처럼 파서 차이를 유발할 수 있는 입력도 거부해야 합니다.
2. DNS 결과와 모든 리디렉션을 다시 검사합니다
도메인 이름이 정상처럼 보여도 실제 해석 결과가 사설 주소나 루프백 주소일 수 있습니다. 최초 DNS 해석 결과를 확인하고, 연결 직전의 목적지도 검증해야 합니다.
리디렉션을 자동으로 따라간다면 이동할 때마다 새 목적지를 같은 규칙으로 검사해야 합니다. 최초 URL만 확인하면 공개 주소가 내부 주소로 리디렉션되는 우회가 생길 수 있습니다.
IPv4뿐 아니라 IPv6의 루프백, 링크 로컬, 사설 범위도 포함해야 합니다. 숫자형·축약형처럼 다양한 IP 표현이 동일한 주소로 해석될 수 있다는 점도 고려해야 합니다.
3. 네트워크 계층에서 외부 통신 범위를 줄입니다
애플리케이션 검증은 실수할 수 있습니다. 프록시 프로세스나 컨테이너가 내부 관리망과 메타데이터 주소에 연결하지 못하도록 방화벽과 egress 정책을 함께 적용하면 피해 범위를 줄일 수 있습니다.
애플리케이션 허용 목록과 네트워크 접근 통제를 함께 두면 한쪽 검증이 우회되더라도 다른 계층이 요청을 차단할 수 있습니다.
4. 다운로드 비용에 상한을 둡니다
- 연결과 전체 요청에 짧은 타임아웃을 둡니다.
- 리디렉션 횟수를 제한합니다.
- 응답 헤더와 본문 크기를 제한합니다.
- 전체 응답을 메모리에 올리기 전에 스트리밍 중단 조건을 적용합니다.
- 허용한 콘텐츠 유형과 실제 파일 서명이 일치하는지 확인합니다.
- 사용자별·IP별 요청 제한과 전체 할당량을 둡니다.
- 캐시의 총용량, 보존 기간, 퇴거 정책을 정합니다.
5. 쓰기 기능과 공개 읽기를 분리합니다
CDN이나 파일 서비스는 공개 읽기와 관리자 쓰기의 요구사항이 다릅니다. 파일 조회 URL은 공개할 수 있어도 업로드, 원격 가져오기, 변환 생성 같은 비용이 큰 작업은 인증과 요청 제한이 필요합니다.
특히 임의 크기의 이미지 변환을 허용하면 공격자가 서로 다른 조합을 계속 만들어 CPU와 캐시를 소모시킬 수 있습니다. 폭과 포맷을 미리 정한 허용 목록으로 제한하면 캐시 키의 수와 연산량을 예측하기 쉬워집니다.
파일 서비스에서 함께 점검할 항목
프록시를 제거했다고 파일 서비스 점검이 끝나는 것은 아닙니다. 다음 항목도 서비스 요구사항에 따라 확인하는 것이 좋습니다.
- 업로드 시 클라이언트가 보낸 MIME 타입만 신뢰하지 않는지 확인합니다.
- 브라우저가 콘텐츠를 임의로 해석하지 않도록
X-Content-Type-Options: nosniff를 적용합니다. - HTML이나 SVG처럼 실행 가능성이 있는 형식은 기본 다운로드 방식과 별도 도메인을 검토합니다.
- 큰 파일이나 영상을 제공한다면
Range요청과 이어받기를 지원할지 결정합니다. - 업로드와 변환 API에 인증, 요청 제한, 용량 제한을 적용합니다.
- 공개에서 비공개로 바뀌는 파일은 CDN 캐시 삭제까지 하나의 작업으로 처리합니다.
이 목록은 특정 제품의 미완료 항목을 뜻하기보다, 파일 제공 기능을 설계할 때 반복해서 확인해야 하는 일반적인 점검 기준입니다.
정리
이번 문제의 핵심은 공개 API 하나가 빠졌다는 사실보다, 서버가 가진 네트워크 권한을 외부 입력에 연결했다는 점이었습니다.
- 사용자가 지정한 URL을 서버가 요청하면 SSRF 가능성을 먼저 검토합니다.
- 인증 경로는 허용 목록과 테스트로 관리해 새 라우트가 빠지지 않게 합니다.
- 목적지 검증은 URL 문자열이 아니라 DNS 결과, IP 범위, 리디렉션까지 포함합니다.
- 애플리케이션 검증과 네트워크 egress 통제를 함께 적용합니다.
- 응답 크기, 시간, 요청 횟수, 캐시 용량에 상한을 둡니다.
- 사용하지 않는 프록시는 보완보다 제거를 우선 검토합니다.
편의를 위해 만든 작은 프록시도 서버의 신뢰 경계를 바꿀 수 있습니다. 외부 URL을 받는 기능을 추가할 때는 이미지 처리 기능이 아니라 제한된 네트워크 클라이언트를 설계한다는 관점으로 접근하는 것이 안전합니다.
함께 읽기
- Base64는 암호화가 아니다: 브라우저 Vault의 v2 봉투 암호화모바일 서명 파일이나 환경설정을 백업하다 보면 값 대부분이 Base64처럼 보일 때가 있다. 이때 가장 먼저 구분해야 할 사실이 있다.
- 개발 키가 많아질수록: Bitwarden으로 비밀번호와 시크릿을 나누는 법서비스를 운영하다 보면 GitHub 토큰, 데이터베이스 비밀번호, API 키, 배포 인증서, 백업 암호화 키가 빠르게 늘어납니다. 처음에는 비밀번호 관리 앱의 메모나 여러 .env 파일에 저장해도 괜찮아 보이지만, 키가 많아질수록 “무엇이 최신인지”, “개발용인지 운영용인지”, “어디에서 사용 중인지”를 판단하기 어려워…
- YouTube Data API OAuth 인증 설정: 로컬 자동 업로더 연결하기YouTube 영상을 코드로 업로드하려면 API 키만으로는 부족합니다. 영상 업로드, 썸네일 지정, 자막 등록처럼 채널 데이터를 바꾸는 작업은 채널 소유자의 동의를 받은 OAuth 2.0 인증이 필요합니다.
- HMAC 입문: 서버는 값이 바뀌었다는 사실을 어떻게 알아낼까?웹 서비스를 만들다 보면 이런 값들을 자주 다룹니다.
- 웹사이트 공개 전 무료 점검 도구: 속도·보안·접근성·SEO 한 번에 확인하기웹사이트는 화면과 기능이 완성됐다고 바로 공개할 수 있는 상태가 되는 것은 아닙니다. 실제 사용자가 느끼는 속도, HTTP 보안 헤더, HTTPS 설정, 키보드 접근성, 구조화 데이터, DNS 설정은 서로 다른 문제입니다.