Skip to content

07. 파일 업로드·다운로드·경로 탐색

파일 기능은 입력 검증, 저장소 설계, 브라우저 응답 헤더, 접근 통제가 한 번에 섞이는 영역이다. 그래서 취약점도 한 가지가 아니라 여러 층에서 함께 생긴다.

학습 목표

  1. 파일 업로드와 다운로드에서 왜 취약점이 자주 생기는지 설명할 수 있다.
  2. 안전한 저장 위치, 파일명 생성, 형식 검증, 응답 헤더 설정 원칙을 이해한다.
  3. 경로 탐색(Path Traversal)과 안전한 다운로드 설계 차이를 설명할 수 있다.
  4. 오래된 확장자 우회 사례보다 현재 실무에서 더 중요한 방어 포인트를 파악할 수 있다.

1. 파일 기능의 위험 표면

파일 기능에서 동시에 봐야 할 것은 다음과 같다.

  • 업로드된 콘텐츠 자체
  • 사용자가 보낸 파일명과 경로 정보
  • 파일을 저장하는 위치
  • 파일을 다시 내려줄 때의 응답 헤더
  • 그 파일을 볼 권한이 있는지 여부

2. 안전한 업로드의 기본 원칙

2.1 서버가 파일명을 만든다

사용자가 보낸 원본 파일명은 표시용 메타데이터로만 저장하고, 실제 저장 파일명은 서버가 생성하는 편이 안전하다.

  • 랜덤 ID
  • UUID
  • 시간 + 난수 조합

이렇게 해야 이름 충돌과 경로 조작 위험을 줄일 수 있다.

2.2 저장 위치를 격리한다

가장 중요한 원칙 중 하나다.

  • 웹 루트 밖에 저장
  • 실행 가능한 확장자가 동작하지 않는 저장소 사용
  • 가능하면 별도 정적 도메인이나 오브젝트 스토리지를 사용

원문처럼 "업로드 경로를 잘 검증하자"도 중요하지만, 더 근본적인 방어는 실행되지 않는 위치에 저장하는 것이다.

가능하다면 메인 서비스 도메인과 업로드 서빙 도메인의 쿠키 범위도 분리하는 편이 좋다.

2.3 형식 검증은 한 겹으로 끝내지 않는다

검증 항목이유
허용 확장자가장 기본적인 정책
MIME 타입브라우저와 서버가 보는 타입 확인
매직 바이트파일 내부 시그니처 확인
크기 제한자원 고갈 방지
이미지 재인코딩메타데이터와 숨은 페이로드 제거 도움

확장자만 보는 방식은 충분하지 않다.


3. 특히 주의할 파일 유형

  • SVG: 이미지처럼 보여도 스크립트와 외부 참조가 들어갈 수 있다
  • HTML: 다운로드가 아니라 렌더링되면 바로 XSS 면이 된다
  • PDF, Office 문서: 별도 악성 콘텐츠 검토가 필요할 수 있다
  • 압축 파일: 압축 폭탄, 경로 탈출, 내부 스크립트 포함 가능성 고려

즉, "이미지 업로드"라면 가능한 한 실제 비트맵 이미지 포맷만 허용하는 편이 안전하다.

정말 SVG가 필요하다면 Sanitizer 정책과 별도 서빙 정책을 함께 검토해야 한다.


4. 다운로드 기능은 경로가 아니라 식별자로 설계한다

안전한 다운로드는 보통 이렇게 설계한다.

  1. 사용자는 파일 ID를 요청한다
  2. 서버는 DB에서 파일 메타데이터를 조회한다
  3. 현재 사용자가 그 파일에 접근할 권한이 있는지 확인한다
  4. 서버가 내부 저장 경로를 결정해서 응답한다

이 방식이 중요한 이유는 사용자 입력을 파일 경로로 직접 해석하지 않기 때문이다.

반대로 위험한 방식은 다음과 같다.

  • ?path=/uploads/...
  • ?filename=../../...
  • 사용자 입력을 그대로 storagePath + filename으로 연결

5. 경로 탐색을 줄이는 응답 전략

사용자 제공 파일을 내려줄 때는 브라우저가 실행하지 않게 하는 응답 헤더도 중요하다.

http
Content-Disposition: attachment
Content-Type: application/octet-stream
X-Content-Type-Options: nosniff

이 조합은 특히 사용자 업로드 파일을 렌더링하지 말아야 할 때 유용하다.

  • attachment로 강제 다운로드
  • octet-stream으로 일반 바이너리 취급
  • nosniff로 브라우저 추측 실행 방지

6. basename()은 도움이 되지만 만능은 아니다

일부 프레임워크 가이드에서는 basename()처럼 파일명에서 경로 정보를 제거하는 방식을 보여 준다.
이 방법은 분명 도움은 되지만, 이것만으로 전체 설계를 대신할 수는 없다.

더 좋은 기준은 다음과 같다.

  • 애초에 사용자 입력 파일명을 경로 결정에 사용하지 않기
  • 내부 저장소 키는 서버가 결정하기
  • 다운로드는 식별자 기반으로 매핑하기

즉, 문자열 정리 함수는 보조 수단이고, 구조적으로 경로를 노출하지 않는 설계가 더 중요하다.


7. 원문을 현재 기준으로 보완하면

원문 포인트현재 기준 보강
확장자 화이트리스트MIME, 매직 바이트, 재인코딩까지 확장
실행 가능 경로 차단웹 루트 밖 또는 별도 저장소 분리까지 포함
파일 다운로드 경로 문자 차단식별자 기반 조회와 권한 검증을 우선
오래된 서버별 우회 사례현재는 SVG, HTML, MIME sniffing, 정적 도메인 분리가 더 중요

8. PR 리뷰 체크리스트

  • 실제 저장 파일명을 서버가 생성하는가
  • 업로드 파일이 실행되지 않는 위치에 저장되는가
  • 허용 확장자, MIME, 매직 바이트 검증이 있는가
  • 이미지 업로드는 재인코딩이나 메타데이터 제거를 검토했는가
  • 다운로드는 파일 ID 기반이며 권한 검증을 거치는가
  • 사용자 업로드 응답에 Content-Disposition: attachmentnosniff를 검토했는가

핵심 정리

  • 파일 기능은 입력 검증만이 아니라 저장 구조와 응답 헤더까지 함께 봐야 한다
  • 실제 저장 파일명은 서버가 만들고, 파일은 실행되지 않는 위치에 보관하는 것이 기본이다
  • 다운로드는 경로 입력이 아니라 식별자와 권한 검증 중심으로 설계해야 한다
  • 현재 실무에서는 오래된 우회 기법 암기보다 격리 저장, 안전한 헤더, 형식 3중 검증이 더 중요하다