이미지 업로드 기능, 실제로는 아무 파일이나 올라가고 있을 수 있습니다
"이미지 업로드 버튼인데 설마 다른 파일이 올라가겠어?"라고 생각했다면, 한 번 코드를 확인해보세요. 실제로 많은 서비스에서 클라이언트 검증이 빠져 있습니다. 그리고 S3에 HTML 파일이 올라가는 순간, 피싱 페이지 하나가 생기는 거예요. 이 글에서는 파일 업로드 허점이 어떤 경로로 보안 위협이 되는지, 그리고 어떻게 막는지 실제 코드 기준으로 설명합니다.
이미지 업로드 기능, 실제로는 아무 파일이나 올라가고 있을 수 있습니다
"이미지 업로드 버튼인데 설마 다른 파일이 올라가겠어?"라고 생각했다면, 한 번 코드를 확인해보세요. 실제로 많은 서비스에서 클라이언트 검증이 빠져 있습니다. 그리고 S3에 HTML 파일이 올라가는 순간, 피싱 페이지 하나가 생기는 거예요. 이 글에서는 파일 업로드 허점이 어떤 경로로 보안 위협이 되는지, 그리고 어떻게 막는지 실제 코드 기준으로 설명합니다.
목차
- 이미지 업로드인데 JSON이 올라가는 이유
- S3에 아무 파일이 올라가면 실제로 어떤 일이 생기나
- AWS abuse 감지 — 감지되면 어떻게 되나
- 클라이언트 / 서버 각각 막는 방법
- FAQ
- 지금 바로 할 액션
이미지 업로드 버튼인데 왜 JSON이 올라가지?
Ant Design의 <Upload> 컴포넌트는 accept 속성을 지정하지 않으면 파일 선택 다이얼로그에서 모든 파일 형식을 허용합니다. 브라우저 기본 동작이 그렇게 생겼어요.
// ❌ 이렇게 쓰면 모든 파일 선택 가능
<Upload beforeUpload={beforeUpload} showUploadList={false}>
// ✅ 이미지만 허용
<Upload beforeUpload={beforeUpload} showUploadList={false} accept="image/*">
beforeUpload도 문제입니다. 많은 코드에서 아래처럼 타입 검사 없이 그냥 return false만 해요.
const beforeUpload = (file: File) => {
setImgFile(file); // 타입 체크 없음
return false;
};
서버에 isImage: true 파라미터를 보내더라도, 백엔드가 실제로 MIME 타입을 검증하지 않으면 아무 파일이나 저장됩니다.
클라이언트 검증과 서버 검증은 별개입니다. 하나만 믿으면 안 됩니다.
실행 인사이트: 지금 운영 중인 서비스의 <Upload> 컴포넌트에 accept 속성이 없다면 당장 추가하세요. 10초 작업입니다.
S3에 HTML 파일이 올라가면 실제로 무슨 일이 생기나
S3는 파일을 실행하지 않습니다. 그래서 "괜찮지 않냐"고 생각할 수 있는데, 문제는 S3가 파일을 그대로 서빙한다는 점입니다.
공격 시나리오는 이렇습니다.
- 로그인한 공격자가 이미지 업로드 기능으로 HTML 파일 업로드
- S3 공개 URL 확보 (
isPublic: true설정인 경우) - 해당 URL을 피싱 메일/문자에 삽입
- 피해자는 정상 CDN 도메인처럼 보이는 URL을 신뢰하고 클릭
비용 피해는 작습니다. HTML 파일은 수 KB라 수만 명이 접속해도 S3 아웃바운드 비용은 몇 달러 수준이에요. 진짜 피해는 브랜드 신뢰도와 인프라입니다.
자사 CDN 도메인에서 피싱 페이지가 서빙되는 상황은, 비용보다 훨씬 큰 문제입니다.
실행 인사이트: S3 버킷 정책에서 허용하는 Content-Type을 이미지로 제한하는 것도 방어 레이어 중 하나입니다. 버킷 레벨 정책은 코드와 무관하게 작동합니다.
AWS abuse 감지 — 계정이 정지될 수 있습니다
AWS는 자사 인프라가 피싱·악성코드 유포에 악용되면 계정에 제재를 가합니다.
감지 경로:
- 피해자 또는 제3자가 AWS abuse팀에 직접 신고
- 구글 Safe Browsing, Microsoft SmartScreen 등 브라우저 블랙리스트에 등재 → AWS에 통보
- AWS 내부 자동 스캔
제재 단계:
abuse@[계정 이메일]로 경고 메일 발송- 응답 없거나 반복 시 — 해당 버킷 또는 객체 접근 차단
- 심각한 경우 — AWS 계정 전체 정지
계정 정지까지 가는 경우는 드물지만, 버킷 하나만 차단돼도 그 버킷에 저장된 모든 상품 이미지, 콘텐츠 이미지가 404가 됩니다. 서비스 전체가 이미지 없이 돌아가는 상황이 생길 수 있어요.
AWS abuse 제재는 코드 문제가 아니라 계정 문제입니다. 복구가 느리고 불투명합니다.
실행 인사이트: AWS CloudTrail로 /files 엔드포인트의 업로드 이력을 모니터링하고, 비정상적인 파일 타입 업로드 패턴을 알림으로 받도록 설정하세요.
클라이언트 / 서버 각각 막는 방법
두 레이어 모두 막아야 합니다. 하나만으론 부족해요.
클라이언트 (즉시 적용 가능)
// 1. accept 속성으로 파일 선택 자체를 제한
<Upload accept="image/*" beforeUpload={beforeUpload} showUploadList={false}>
// 2. beforeUpload에서 MIME 타입 이중 검증
const beforeUpload = (file: File) => {
const isImage = file.type.startsWith('image/');
if (!isImage) {
message.error('이미지 파일만 업로드 가능합니다.');
return false;
}
setImgFile(file);
return false;
};
서버 (백엔드 담당자 확인 필요)
isImage: true파라미터를 받았을 때 실제로 MIME 타입 검증하는지 확인- multipart 파싱 후
content-type헤더 또는 파일 매직 바이트(file signature)로 검증 - 허용 외 타입은 400 또는 415 응답
클라이언트 검증은 UX용, 서버 검증이 실제 보안입니다.
FAQ
Q. accept="image/*"를 우회하는 게 가능한가요?
네, 가능합니다. 브라우저 devtools에서 accept 속성을 제거하거나, curl로 직접 요청하면 우회됩니다. 그래서 서버 검증이 반드시 필요합니다.
Q. S3 버킷이 private인데도 위험한가요?
업로드 시 isPublic: true로 저장되는 경우라면 동일하게 위험합니다. 버킷 자체가 private이어도 객체별 ACL이 public이면 URL로 직접 접근 가능합니다.
Q. 비용 피해는 없나요?
피싱 페이지 자체는 파일이 작아 비용 피해가 미미합니다. 다만 공격자가 대용량 파일을 대량 업로드하는 스토리지 남용 시나리오에서는 비용 피해가 생길 수 있습니다.
지금 바로 할 액션
파일 업로드 보안은 "나중에 하면 되는 일"이 아닙니다. 이미 배포된 기능에 구멍이 있다면, 지금 이 순간에도 누군가 시도하고 있을 수 있어요. 아래 세 가지를 오늘 안에 확인하세요.
<Upload>컴포넌트 전수조사 —accept속성 없는 곳 모두 추가beforeUpload에 MIME 타입 검증 로직 추가- 백엔드 담당자에게
/files엔드포인트의 서버 사이드 MIME 검증 여부 확인 요청
클라이언트 검증 하나로 안심하고 있었다면, 지금 바로 서버 검증 상태를 확인해보세요. aws 보안 사고는 코드 한 줄 차이로 막을 수 있는 경우가 생각보다 많습니다.
메타설명: 이미지 업로드 버튼으로 HTML 파일이 올라갈 수 있습니다. S3 피싱 공격 원리와 MIME 타입 검증으로 파일 업로드 보안을 지키는 방법을 코드와 함께 설명합니다.