웹 양식 파일 검증 구현 체크리스트
카테고리: 보안·구현
파일 업로드 기능의 구현에는 보안상 주의할 점이 많고 놓치기 쉬운 함정도 많습니다. 본 문서에서는 프로덕션 환경에서 안전하게 작동하는 파일 업로드를 구현하기 위한 체크리스트를 설명합니다.
체크리스트 개요
이 체크리스트는 백엔드(서버 측) 검증을 중심으로 정리되어 있습니다. 프론트엔드 검증은 보조적인 UX 개선으로 구현되지만 보안 보장이 되지 않습니다.
1. 파일 크기 검증
- 업로드 상한을 바이트 단위로 정의하고 있습니다(MB와 MiB의 혼동 없음).
- PHP의 경우,
upload_max_filesize와post_max_size둘 다 설정하고 있습니다. - Nginx의 경우,
client_max_body_size에 multipart 오버헤드분을 추가하고 있습니다. -
$_FILES['file']['error']가 UPLOAD_ERR_INI_SIZE / UPLOAD_ERR_FORM_SIZE인 경우의 에러 처리가 있습니다. - 최소 파일 크기 검사가 있습니다(0바이트 파일 제외).
$maxBytes) {
throw new \RuntimeException(sprintf(
'ファイルサイズ(%s)が上限(%s)を超えています',
number_format($file['size']),
number_format($maxBytes)
));
}
}
2. 파일 형식 검증(MIME 타입)
- 클라이언트에서 보낸
Content-Type($_FILES['file']['type'])을 신뢰하지 않습니다. -
finfo/mime_content_type()으로 서버 측 MIME 타입 검증을 수행하고 있습니다. - 허용하는 MIME 타입의 화이트리스트를 정의하고 있습니다.
file($file['tmp_name']);
if (!in_array($mimeType, $allowed, true)) {
throw new \RuntimeException('許可されていないファイル形式です: ' . $mimeType);
}
3. 파일 확장자 검증
- 확장자의 화이트리스트를 정의하고 있습니다(블랙리스트가 아닌 화이트리스트).
- 이중 확장자(
shell.php.jpg)를 감지하고 거부하고 있습니다. - 대소문자를 정규화하여 검증하고 있습니다(
.JPG와.jpg를 동일시).
2) {
throw new \RuntimeException('不正なファイル名です');
}
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowed, true)) {
throw new \RuntimeException('許可されていない拡張子です: ' . $ext);
}
4. 매직 바이트(파일 서명) 검증
- 중요한 파일(실행 파일 제외 등)에서 매직 바이트를 검증하고 있습니다.
5. 저장 위치 및 파일 이름의 안전한 처리
- 저장 디렉토리는 웹 루트 외부입니다(또는 XSendFile/X-Accel-Redirect로 제어).
- 저장 파일명은 UUID 등으로 무작위 생성하고 원래 파일명을 사용하지 않습니다.
- 경로 순회(
../../../etc/passwd)를 검증으로 배제하고 있습니다. - 저장 디렉토리에 PHP 실행 권한이 없습니다(
.htaccess또는 Nginx 설정으로 PHP 처리 비활성화).
6. 에러 핸들링 및 레스폰스
- 업로드 성공 시 적절한 HTTP 상태(200/201)를 반환하고 있습니다.
- 크기 초과 시 413 Payload Too Large를 반환하고 있습니다.
- 잘못된 파일 형식 시 422 Unprocessable Entity를 반환하고 있습니다.
- 에러 메시지에 서버의 내부 정보(경로, 버전 등)가 포함되어 있지 않습니다.
7. 테스트 케이스
구현 후 다음 테스트 케이스를 실행하여 동작을 확인하세요. DevLab의 테스트 파일을 활용할 수 있습니다.
| 테스트 케이스 | 예상 결과 | 사용할 파일 |
|---|---|---|
| 정확히 한계 크기의 파일 | 성공 | 임계값 파일 |
| 한계를 1바이트 초과하는 파일 | 413 에러 | 임계값 파일 |
| 0바이트의 빈 파일 | 검증 오류 | 수동 생성 |
확장자를 위장한 파일 (PHP→.jpg) | MIME 오류 | 손상된 파일 |
| 헤더 손상 파일 | 검증 오류 | 손상된 파일 |
요약
안전한 파일 업로드 구현에는 여러 계층에서의 검증이 필수적입니다. 특히 다음 3가지는 반드시 구현해야 합니다.
- 서버 측 MIME 타입 검증(
finfo사용) — 클라이언트 신고를 신뢰하지 않기 - 임의의 파일명으로 저장 — 원본 파일명 사용 금지
- 업로드 디렉토리에서 PHP 실행 비활성화——업로드 디렉토리에서 스크립트가 실행되지 않도록 방지
이 기사에서 사용할 수 있는 테스트 파일
관련 기사
❓ 자주 묻는 질문
파일 업로드 검증에서 최소한 구현해야 할 것은?
최소 세 가지가 필요합니다. 첫째 finfo를 이용한 서버 측 MIME 타입 검증(클라이언트의 신고를 신뢰하지 않기), 둘째 무작위 파일명으로 저장(원래 파일명을 쓰지 않기), 셋째 저장 디렉터리에서 PHP 실행 비활성화(업로드 디렉터리에서 스크립트가 실행되지 않도록 하기)입니다.
파일 확장자 검사만으로는 불충분한 이유는?
확장자는 쉽게 위장할 수 있어 shell.php.jpg 같은 이중 확장자의 악성 파일을 잡아내지 못합니다. 확장자 화이트리스트에 더해 finfo에 의한 MIME 타입 검증과 매직 바이트(파일 헤더) 검증을 조합해 안전성을 높여야 합니다.
파일 업로드 시 적절한 HTTP 상태 코드는?
업로드 성공 시에는 200 또는 201, 파일 크기 초과 시에는 413 Payload Too Large, 잘못된 파일 형식일 때는 422 Unprocessable Entity를 반환하는 것이 적절합니다. 에러 메시지에 서버 내부 정보(경로나 버전 등)를 포함하지 않도록 주의하세요.