정보보안핵심 · 13분 읽기

파일 업로드와 경로 처리의 보안

업로드는 파일을 저장하는 기능이면서 비신뢰 데이터를 처리하는 경계다. 형식 검사·저장 위치·실행 권한·다운로드 인가를 함께 설계한다.

개념부터 찾아보기

정의·역할·동작 원리를 독립 페이지에서 확인하세요. 본문의 밑줄 용어에도 설명이 있습니다.

이 노트에서 설명할 수 있게 되는 것

  1. 업로드의 임시 저장·검사·승인·제공 상태를 구분한다.
  2. 확장자·MIME·실제 형식과 저장 경로의 검사를 나눈다.
  3. 후처리·압축 해제·다운로드에서 생기는 추가 경계를 설명한다.

01개념과 구성 요소

  • 파일 업로드는 바이트 수신, , 저장, 검사, 후속 파싱·실행으로 나뉜다.
  • filename·Content-Type·파일 내용은 모두 검증 대상이며 하나만으로 안전한 형식을 증명하지 못한다.
  • 저장 위치·다운로드 소유권·실행 권한·변환기 권한을 각각 제한한다.

02업로드 파일을 검사 후 제공하는 순서

  1. 인증·와 요청·파일 크기 한도를 검사하고 초과 시 임시 자원을 정리한다.
  2. 서버가 만든 ID를 저장 키로 사용하고 원래 파일명은 별도 메타데이터로 취급한다.
  3. 파일을 검사 대기 상태로 저장한다. 웹 실행 경로와 일반 다운로드 경로에서 즉시 접근하도록 두지 않는다.
  4. 허용 형식·시그니처·실제 파서 결과와 크기·확장 자원 한도를 평가한다. 시간 초과를 승인으로 처리하지 않는다.
  5. 검사 결과에 따라 approved 또는 rejected로 바꾸고 실패 자료·캐시를 정책에 맞게 처리한다.
  6. 요청자의 소유권과 승인 상태를 다시 확인하고 적절한 Content-Type·으로 제공한다.

03파일 업로드가 실행 취약점으로 이어지는 경계

  • 파일을 받는 기능은 바이트를 저장하는 단계와 나중에 해석하는 단계로 나뉜다. 업로드한 파일이 웹 루트에 저장되고 서버가 그 확장자를 같은 실행 핸들러에 연결하면, 단순 자료 업로드가 서버 코드 실행으로 바뀐다.
  • 반대로 웹 루트 밖에 저장한 파일도 이미지 변환기·문서 미리보기 파서의 취약점을 통과하면 위험해진다. 파일 저장만 성공했는지와 파일이 어떤 권한으로 어떤 처리기에 전달되는지는 별개이다.
  • 파일 업로드와 파일 포함이 연결되면 저장된 바이트가 나중에 실행 입력이 될 수 있다. 업로드 경로를 웹에서 실행하지 않더라도 별도의 include 기능이 같은 파일을 코드로 읽으면 실행 경계가 다시 열린다.
  • 저장소 분리, 실행 금지, 포함 대상 제한을 서로 다른 통제로 이해해야 한다.

04multipart 요청에서 신뢰할 수 없는 세 가지 값

  • 브라우저가 만드는 /form-data에는 boundary로 구분된 각 부분의 이름, filename, Content-Type과 실제 파일 바이트가 담긴다. filename은 저장 경로가 아니라 클라이언트가 제공한 표시 정보이며, Content-Type 역시 클라이언트 주장이다.
  • 이미지라는 헤더와 .jpg라는 이름이 있어도 본문이 이미지라는 증거가 되지 않는다.
  • 서버가 임의 식별자로 저장 이름을 만들고 원래 이름은 메타데이터로 관리하면 충돌·경로 조작을 줄일 수 있다. 허용 확장자와 파일 시그니처, 실제 디코더의 해석 결과를 함께 사용할 때도 다중 형식 파일과 복잡한 파서의 위험은 남는다.
  • 따라서 검사 통과가 곧 실행 권한 부여를 뜻해서는 안 된다.
text
POST /training/avatar HTTP/1.1
Content-Type: multipart/form-data; boundary=lesson

--lesson
Content-Disposition: form-data; name="avatar"; filename="portrait.png"
Content-Type: image/png

[PNG 바이트]
--lesson--

원리를 설명하는 가상 학습 예시입니다.

05저장 이름·경로·권한을 분리하는 설계

  • 업로드 서비스는 사용자의 파일명으로 경로를 직접 연결하지 않고 서버가 만든 ID를 사용한다. 데이터베이스에는 소유자, 저장 키, 크기, 판별된 형식, 검사 상태를 저장한다.
  • 다운로드 요청의 ID는 이 레코드를 조회한 뒤 소유권 검사와 실제 저장 키 선택으로 이어진다. 경로를 URL에서 직접 받는 설계보다 저장 위치와 접근 권한을 명확히 분리한다.
  • 저장소를 웹 루트 밖 또는 별도 객체 저장소에 두면 웹 서버의 스크립트 핸들러에 우연히 연결되는 위험이 줄어든다. 제공할 때도 적절한 Content-Type과 을 결정한다.
  • HTML·SVG처럼 능동 콘텐츠가 가능한 형식은 표시 기능의 요구와 격리 출처를 따로 설계해야 하며, 이미지 재인코딩은 지원 형식의 불필요한 부가 데이터를 줄이는 방법이지 모든 형식의 만능 검사기는 아니다.

06크기 제한과 비동기 검사 상태

  • HTTP 요청 크기, 업로드 파일 크기, 사용자 저장 할당량, 디코딩된 이미지의 픽셀 수는 서로 다른 자원 제한이다. 압축 파일은 작은 입력이 큰 결과로 풀릴 수 있어 압축 전 크기만 제한해도 충분하지 않다.
  • 한도 초과 때 임시 파일을 정리하지 않으면 실패한 요청이 디스크를 차지한다.
  • 비동기 검사에서는 received → quarantined → approved 또는 rejected 같은 상태를 둔다. 검사가 완료되기 전에 일반 다운로드 경로가 파일을 제공하면 검사 대기 시간 동안 우회가 생긴다.
  • 검사 실패·시간 초과를 승인으로 처리하는 fail-open도 같은 문제다. 승인된 콘텐츠에 대해 이후 재검사 결과가 바뀌었을 때 제공 상태와 캐시를 함께 갱신해야 한다.
원리 심화

07업로드 크기·디코딩 비용·검사 사본의 동일성

  • 전송 바이트·압축 해제 크기·이미지 픽셀 수·디코더 메모리는 다른 자원이다. 파일 크기 제한 하나로 모두 제한되지 않는다.
  • 임시 저장 객체의 ID·버전·해시를 검사 결과와 묶는다. 검사한 파일과 공개하는 파일이 중간에 바뀌면 정상 결과를 다른 바이트에 적용하게 된다.
  • RECEIVED→SCANNING→APPROVED 또는 REJECTED로 상태를 나누고, 승인 전에는 외부 다운로드·변환 작업을 제한한다.
  • 확장자·MIME·매직 바이트는 함께 보되 실제 안전성을 전부 보장하지 않는다. 지원 형식의 파서가 전체 구조와 자원 한도를 검사해야 한다.
  • 변환·미리보기 작업도 입력 처리 프로그램이다. 격리된 권한·시간·메모리·출력 크기 한도와 네트워크 정책을 적용한다.
예제로 추적

08가상 이미지의 압축 크기와 메모리 계산

  1. 압축 파일은 3MB지만 헤더가 가로 40,000·세로 40,000픽셀을 나타낸다고 가정한다.
  2. 픽셀 수는 40,000×40,000=1,600,000,000개다. 3MB라는 전송 크기와는 다른 값이다.
  3. 한 픽셀을 RGBA 4바이트로 펼치면 기본 픽셀 버퍼만 6,400,000,000바이트가 필요하다. 디코더의 추가 메모리는 제외한 값이다.
  4. 픽셀 수 또는 예상 메모리 한도를 초과하면 전체 디코딩 전에 거부한다. 헤더 검사도 파서 오류와 잘못된 수치에 대비해야 한다.
  5. 작은 정상 이미지도 같은 승인 경로를 통과시켜 저장 ID·미리보기·다운로드가 일치하는지 본다. 거부 기능만 성공해서는 정상 기능을 검증하지 못한다.
검증·예외

09검사 성공 뒤 제공 단계에서 생기는 실패

  • 클라이언트 파일명을 서버 경로·명령 인수로 재사용하는지 본다. 표시용 이름과 실제 저장 키를 분리한다.
  • 동일 이름 덮어쓰기나 비동기 변환 때문에 승인된 해시와 제공된 해시가 달라졌는지 확인한다.
  • 안전한 저장소라도 브라우저가 실행 가능한 Content-Type으로 제공하면 새로운 해석 경계가 생긴다. 다운로드 헤더와 Origin을 함께 설계한다.
  • 스캐너 timeout·파서 오류를 승인으로 바꾸지 않는다. 재시도 대기와 최종 거부를 구분하고 사용자가 미완료 파일을 열지 못하게 한다.

10Path Traversal은 기준 디렉터리를 벗어나는 문제

  • 경로 순회는 사용자 입력이 파일 경로에 연결되고 상위 디렉터리 이동·절대 경로·인코딩 변환 등으로 허용 루트 밖의 파일을 가리킬 때 발생한다. 파일 포함과 달리 단순 읽기 API에서도 발생한다.
  • 문자열에서 ../만 지우는 방식은 운영체제의 경로 규칙, 중복 디코딩, 심볼릭 링크 해석을 충분히 반영하지 못한다.
  • 가장 단순한 대책은 외부 ID를 서버가 정한 파일에 매핑하여 사용자가 경로를 표현할 필요를 없애는 것이다. 경로 입력이 꼭 필요하다면 최종 정규화 경로가 허용 루트 안에 있는지 경로 구성요소 단위로 검사한다.
  • /data/files와 /data/files-old가 같은 문자열 접두사를 공유하므로 단순 startsWith 비교만으로 경계를 정하면 안 된다. 검사 후 링크가 바뀌는 경쟁 조건과 파일 접근 권한도 별도로 남는다.

11LFI·RFI와 다운로드의 차이

  • 는 로컬 파일을 include/require/템플릿 와 같은 해석 경로에 포함하는 문제이고, 는 지원·허용된 환경에서 원격 자원을 포함하는 문제이다. readfile처럼 데이터를 응답하는 동작과 include처럼 언어 처리기에 전달하는 동작을 구분한다.
  • 취약점 영향은 포함 API, 파일 내용, 런타임 설정과 서비스 계정 권한에 의해 결정된다.
  • 로 특정 파일을 읽을 수 있다는 사실만으로 임의 코드 실행이 증명되지는 않는다. 공격자가 제어한 바이트가 파일에 기록되고, 그 파일이 코드로 해석되는 추가 조건이 있어야 실행으로 연결되는 경우가 있다.
  • chroot는 파일 시스템 이름 공간을 제한한다. 애플리케이션의 파일 선택 검증·실행 권한을 대신하는 완전한 격리 장치는 아니다.

12정상 파일과 경계 입력의 처리 결과 읽기

  • 학습용 정상 PNG를 올려 저장 ID가 반환되는지, 승인 전 다운로드가 거부되는지, 승인 뒤 정확한 형식으로 제공되는지를 연속해서 본다. 허용되지 않은 형식·너무 큰 파일·중복 이름·소유자가 다른 ID는 각각 다른 방어 층을 시험한다.
  • 반환 코드뿐 아니라 임시 파일 정리, 저장 위치, 처리기 선택, 승인 상태 변화가 동작의 근거다.
  • 업로드·파일 포함·경로 순회는 서로 다른 처리 경계이므로 확장자 필터 하나로 해결되지 않는다. 업로드는 바이트를 들이는 경계, 경로 순회는 파일을 선택하는 경계, 포함은 바이트를 코드로 해석하는 경계이며, 한 기능이 다른 기능에 값을 넘길 때도 이 구분을 유지한다.

설명과 함께 보는 보조 도식

처리 과정

모식도 · 업로드 파일의 상태 전이

검사 전 파일과 제공 가능한 파일의 저장·접근 상태를 분리한다.

전체 단계를 한 번에 읽기
  1. 01

    수신

    서버가 정한 식별자로 임시 저장한다.

    상태
    검사 대기
  2. 02

    검사

    허용 조건을 만족하지 않으면 제공 경로로 보내지 않는다.

  3. 03

    후처리

    처리 실패와 시간 초과도 비승인 상태로 남긴다.

  4. 04

    승인

    검사 결과와 소유권을 함께 저장한다.

    상태
    제공 가능
  5. 05

    권한 확인

    요청마다 소유권·공유 정책을 평가한다.

구성요소와 데이터 이동을 단순화한 학습용 모식도입니다. 실제 제품의 모든 내부 모듈을 나타내지는 않습니다.
LEARN THROUGH PRACTICE

업로드 성공과 서버 실행 가능성을 두 단계로 분리하기

파일이 저장되는 경계와 웹 서버가 그 파일을 실행하는 경계를 나누어 분석한다.

실습 구성과 전제

  • 교육용 의 격리 스냅샷에서 화면을 읽는다.
  • 직접 재현은 무해한 텍스트·이미지 파일로 한정하고 운영 서버나 실제 웹셸은 사용하지 않는다.
  1. 업로드 응답이 보장하는 범위를 읽는다

    • 성공 메시지는 서버가 선택한 경로에 파일을 저장했다는 단서다. 안전 검사 통과나 비실행 저장소 분리까지 증명하지 않는다.
    • 의 파일명·Content-Type은 클라이언트가 보낸 입력이다. 서버에서 확장자·형식·크기·저장 이름을 독립적으로 결정해야 한다.
    파일 포함·업로드와 실행 경계 · file upload원본 크기로 보기 ↗
    • File Upload 화면에 PHP 확장자의 파일이 업로드됐다는 메시지가 표시된다.
    • 반환 경로가 웹 애플리케이션 아래 uploads 위치를 가리킨다. 제공 경로와 실행 정책을 이어서 확인해야 한다.
    확인 기준

    업로드 성공과 파일 제공 정책을 별도 항목으로 적는다.

  2. 제공 경로가 실행 경로가 되는 조건을 찾는다

    • 다른 화면은 업로드 파일에 요청을 보내 서버 측 작업 디렉터리가 응답된 교육 사례다. 실행 권한이 없는 파일 저장소라면 같은 방식으로 프로그램이 실행되지 않아야 한다.
    • 웹 루트 밖 저장, 서버가 만든 식별자, 정적 다운로드 처리, 실행 엔진과의 분리가 서로 다른 통제다.
    파일 포함·업로드와 실행 경계 · file upload원본 크기로 보기 ↗
    • 주소 경로에 업로드된 PHP 파일이 있고 화면에 서버 디렉터리 결과가 나타난다.
    • 업로드 메시지와 달리 이 화면은 저장 파일이 실행 경로에 놓였음을 보여 주는 별도 증거다.
    확인 기준

    공격 입력을 반복하기보다 저장소·확장자 처리·서버 핸들러 설정을 조사한다.

  3. 무해한 파일로 수정 경계를 확인한다

    • 허용 이미지, 잘못된 확장자, 제한 크기 초과를 각각 제출한다. 저장 후 이름이 서버에서 생성되는지와 다운로드 응답의 MIME·Disposition을 확인한다.
    • 검사 중 파일은 제공하지 않고, 검사한 바이트와 제공하는 바이트가 같은지 확인한다.
    확인 기준

    거부된 파일이 디스크나 공개 경로에 남지 않는지도 점검한다.

실습에서 확인한 동작 원리

  • 업로드 성공은 안전 판정이 아니다.
  • 저장 정책과 웹 실행 정책을 함께 분리해야 한다.

설정·출력 추가로 확인하기 8장

아래 화면을 펼쳐 위 실습의 설정값, 명령과 확인 기준을 비교해 보세요. 각 묶음은 별도 실습 기록입니다.

실습 화면 묶음 01 3장
7/29 · 실습·개념 참고 화면 8원본 크기로 보기 ↗
  • 「DVWA file inclusion」 문맥에 삽입된 원본 실습·개념 참고 화면이다. 파일 업로드와 경로 처리의 보안 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
7/29 · 실습·개념 참고 화면 9원본 크기로 보기 ↗
  • 「DVWA file inclusion」 문맥에 삽입된 원본 실습·개념 참고 화면이다. 파일 업로드와 경로 처리의 보안 학습 내용과 함께 당시 구성·설정·관찰 결과를 비교할 수 있다.
파일 포함 실습 · 요청 경로와 노출된 시스템 파일원본 크기로 보기 ↗
  • DVWA의 파일 포함 실습에서 경로 입력 뒤 시스템 계정 목록이 HTTP 응답 본문에 나타난 화면이다. 웹 서버 기본 페이지와 구분하여 입력 경로의 허용 범위와 응답 내용 노출을 분석한다.
실습 화면 묶음 02 1장
파일 포함·업로드와 실행 경계 · File Inclusion원본 크기로 보기 ↗
  • 학습 원본 · 파일 포함·업로드와 실행 경계의 File Inclusion에 수록된 화면·도식이다.
실습 화면 묶음 03 1장
파일 포함·업로드와 실행 경계 · File Inclusion원본 크기로 보기 ↗
  • 학습 원본 · 파일 포함·업로드와 실행 경계의 File Inclusion에 수록된 화면·도식이다.
실습 화면 묶음 04 1장
파일 포함·업로드와 실행 경계 · File Inclusion원본 크기로 보기 ↗
  • 학습 원본 · 파일 포함·업로드와 실행 경계의 File Inclusion에 수록된 화면·도식이다.
실습 화면 묶음 05 1장
파일 포함·업로드와 실행 경계 · Remote File Inclusion원본 크기로 보기 ↗
  • 학습 원본 · 파일 포함·업로드와 실행 경계의 Remote File Inclusion에 수록된 화면·도식이다.
실습 화면 묶음 06 1장
파일 포함·업로드와 실행 경계 · file upload원본 크기로 보기 ↗
  • 학습 원본 · 파일 포함·업로드와 실행 경계의 file upload에 수록된 화면·도식이다.
동작·명령 참고 문서
학습 확인

이해했는지 확인하기

  • 파일명·MIME 주장과 실제 형식을 구분해 검사한다.
  • 최종 저장 경로와 실행 권한·크기 제한을 확인한다.
  • 다운로드 요청에도 사용자별 권한 검사가 있는지 확인한다.

함께 확인할 공식 자료