안전한 파일 업로드: 권한, 제한, 서명된 URL, 스캔
웹 앱의 안전한 파일 업로드는 엄격한 권한 관리, 용량 제한, 서명된 URL, 그리고 간단한 악성코드 검사 패턴이 필요합니다.

파일 업로드가 위험한 이유 (알기 쉬운 말로)
파일 업로드는 겉보기엔 무해합니다: 프로필 사진, PDF, 스프레드시트 같은 것들. 하지만 낯선 사람이 시스템에 ‘미지의 상자’를 보낼 수 있다는 점에서 보안 사고의 출발점이 되기 쉽습니다. 당신이 그 파일을 받아서 저장하고 다른 사람에게 보여주면, 앱에 대한 새로운 공격 경로가 생기는 셈입니다.
위험은 단지 “누군가 바이러스를 업로드한다”는 것만이 아닙니다. 나쁜 업로드는 개인 파일을 유출시키거나 스토리지 비용을 폭발시킬 수 있고, 사용자를 속여 접근 권한을 빼앗게 만들 수도 있습니다. “invoice.pdf”라는 이름의 파일이 실제로 PDF가 아닐 수 있습니다. 실제 PDF나 이미지라도 메타데이터를 믿거나, 자동으로 미리보기를 생성하거나, 잘못된 규칙으로 제공하면 문제가 될 수 있습니다.
실제 실패 사례는 보통 이런 모습입니다:
- 누군가 파일 URL을 추측해 다른 사용자의 문서를 내려받음.
- 업로드된 HTML 파일이 웹페이지처럼 제공되어 로그인 정보를 훔치는 프롬프트를 표시함.
- 공격자가 반복해서 거대한 파일을 업로드해 앱을 느려지거나 크래시시킴.
- “안전한” 파일 형식으로 보이게 위장한 파일을 내부 직원이 열어버림.
많은 사고는 한 가지 차이에서 옵니다: 파일을 저장하는 것과 파일을 제공하는 것은 다르다는 점입니다. 저장은 바이트를 보관하는 곳이고, 제공은 그 바이트를 브라우저와 앱에 전달하는 방식입니다. 앱이 사용자 업로드를 메인 웹사이트와 동일한 신뢰 수준으로 서빙하면 문제가 생깁니다. 브라우저가 그 업로드를 ‘신뢰된 것’으로 처리해버릴 수 있기 때문입니다.
작은 서비스나 성장 중인 앱에서 ‘충분히 안전’하다는 건 보통 네 가지 질문에 말장난 없이 답할 수 있다는 뜻입니다: 누가 업로드하나, 무엇을 허용하나, 크기와 빈도는 얼마인가, 그리고 누가 나중에 읽을 수 있나. 빠르게 빌드하더라도(생성된 코드나 채팅 기반 플랫폼을 써도) 이런 안전 장치는 여전히 중요합니다.
업로드에 대한 간단한 위협 모델
모든 업로드를 신뢰하지 않는 입력으로 다루세요. 업로드를 안전하게 유지하는 현실적인 방법은 누가 이를 악용할 수 있는지, 그들의 ‘성공’이 무엇인지 상상하는 것입니다.
대부분의 공격자는 약한 업로드 폼을 스캔하는 봇이거나, 무료 저장소를 얻거나 데이터 스크랩, 서비스를 괴롭히려는 실제 사용자입니다. 때로는 누수를 찾거나 중단을 유발하려는 경쟁자가 있을 수도 있습니다.
그들이 원하는 것은 보통 다음 중 하나입니다:
- 실행 가능한 코드를 서버에서 돌리기 위해 실행되는 무언가를 업로드
- 다운로드 URL을 추측·재사용·공유해 개인정보를 훔치기
- 업로드를 폭주시켜 가용성을 해치기
- 스토리지 증가나 대역폭 많은 다운로드로 비용을 부풀리기
그다음 약점을 맵으로 그려보세요. 업로드 엔드포인트는 정문(과다 크기, 이상한 포맷, 높은 요청률), 스토리지는 뒤편 방(공개 버킷, 잘못된 권한, 공유 폴더), 다운로드 URL은 출구(예측 가능하거나 수명이 긴 URL, 사용자와 연결되지 않은 경우)입니다.
예시: ‘이력서 업로드’ 기능. 봇이 수천 개의 큰 PDF를 올려 비용을 늘리는 동안 악의적 사용자는 HTML 파일을 업로드해 이를 ‘문서’로 공유하며 다른 사람을 속일 수 있습니다.
통제 장치를 추가하기 전에 앱에 가장 중요한 것이 무엇인지 결정하세요: 프라이버시(누가 읽을 수 있는지), 가용성(계속 제공할 수 있는지), 비용(스토리지와 대역폭), 규정 준수(데이터가 어디에 저장되는지, 얼마나 오래 보관하는지). 우선순위 목록이 있으면 결정이 일관됩니다.
실제로 견고한 권한 및 접근 제어
대부분의 업로드 사고는 화려한 해킹이 아닙니다. 단순히 “다른 사람의 파일을 볼 수 있다”는 버그일 뿐입니다. 권한을 나중에 덧붙이는 기능이 아니라 업로드의 일부로 취급하세요.
한 가지 규칙으로 시작하세요: 기본은 거부(default deny). 모든 업로드 대상 오브젝트를 명시적으로 허용하기 전까지는 비공개로 가정하세요. 인보이스, 의료 문서, 계정 서류 등 사용자에 연관된 항목은 기본 비공개가 강력한 기준입니다. 사용자가 명확히 공개를 기대하는 경우(예: 공개 아바타)에만 파일을 공개로 만들고, 그 경우에도 시간 제한 접근을 고려하세요.
실제 업무에 맞는 역할 분리
역할은 단순하고 분리하세요. 흔한 분리는 다음과 같습니다:
- 업로더: 자신의 계정에 업로드 생성 가능
- 뷰어: 허용된 파일 다운로드 가능
- 서포트: 감시 가능한 임시 권한으로만 파일 접근 가능
- 관리자: 정책 관리 가능, 모든 파일을 자동으로 읽어선 안 됨
/user-uploads/ 같은 폴더 규칙에만 의존하지 마세요. 읽기 시점마다 소유권이나 테넌트 접근을 확인하세요. 누군가 팀을 옮기거나 조직을 떠나거나 파일이 재할당될 때를 대비한 방어입니다.
좋은 서포트 패턴은 좁고 일시적입니다: 특정 파일에 대한 접근을 부여하고, 이를 기록하며 자동으로 만료시키세요.
신뢰에 의존하지 않는 파일 형식 검증
대부분의 업로드 공격은 간단한 속임수에서 시작합니다: 이름이나 브라우저 헤더 때문에 안전해 보이지만 실제로는 다른 파일입니다. 클라이언트가 보내는 모든 것을 신뢰하지 마세요.
허용목록으로 시작하세요: 정확히 어떤 형식을 허용할지 결정하고(예: .jpg, .png, .pdf) 나머지는 거부하세요. ‘모든 이미지’ 또는 ‘모든 문서’ 같은 넓은 범위는 진짜 필요할 때만 허용하세요.
파일 확장자나 클라이언트의 Content-Type 헤더는 신뢰하지 마세요. 둘 다 쉽게 위조할 수 있습니다. invoice.pdf라는 이름이 실행 파일일 수 있고, Content-Type: image/png는 거짓일 수 있습니다.
더 강력한 방법은 파일의 첫 바이트(일명 매직 바이트 또는 파일 시그니처)를 검사하는 것입니다. 많은 일반 포맷은 일관된 헤더를 가집니다(PNG, JPEG 등). 헤더가 허용 목록과 일치하지 않으면 거부하세요.
실무적인 검증 구성:
- 서버 측 허용목록으로 확장자 관리
- 클라이언트 헤더가 아닌 서버에서 MIME 타입 감지
- 지원하는 형식에 대해 매직 바이트 검사
- 무작위 저장 이름 생성, 원본 이름은 메타데이터로 보관
- 필요하지 않은 위험한 형식(특히 HTML, SVG, 스크립트 유사 콘텐츠)은 차단
이름 변경(renaming)은 생각보다 중요합니다. 사용자 제공 이름을 그대로 저장하면 경로 조작, 특수 문자, 덮어쓰기 문제가 생깁니다. 저장용 ID는 무작위로 생성하고 원본 파일명은 표시용으로만 보관하세요.
프로필 사진은 JPEG/PNG만 허용하고 헤더를 확인하고 가능하면 메타데이터를 제거하세요. 문서는 PDF로 제한하고 활성 콘텐츠가 있는 것은 거부하는 것을 고려하세요. SVG나 HTML이 필요하다면 실행 가능성 높음을 전제로 격리 처리하세요.
실효성 있는 용량 제한, 비율 제한, DoS 기본 대책
대부분의 업로드 장애는 화려한 공격이 아니라 대용량 파일, 과도한 요청, 느린 연결 때문입니다. 모든 바이트를 비용으로 생각하세요.
실제로 작동하는 크기 제한 설정
기능별로 최대 크기를 정하세요. 아바타는 세금 문서나 짧은 동영상과 같은 제한이 필요하지 않습니다. 정상 범위에서 가장 작은 한도를 정하고, 정말 필요할 때만 별도의 ‘대용량 업로드’ 경로(예: 서명된 URL을 이용한 직접 스토리지 업로드)를 만드세요.
제한은 여러 지점에서 적용하세요. 클라이언트는 거짓말을 할 수 있으므로 앱 로직, 웹 서버/리버스 프록시, 업로드 타임아웃과 선언된 크기가 너무 클 때의 조기 거부 등으로 중복 적용하세요.
구체적인 예: 아바타는 2 MB, PDF는 20 MB로 제한하고 더 큰 파일은 별도 흐름으로 처리하세요(직접 객체 스토리지 업로드 등).
비율 제한과 남용 제어
작은 파일도 반복 업로드되면 DoS가 될 수 있습니다. 업로드 엔드포인트에 대해 사용자별·IP별 비율 제한을 추가하세요. 익명 트래픽에 대해 더 엄격한 제한을 고려하세요.
재개 가능한 업로드(resumable uploads)는 네트워크 상태가 안 좋은 실제 사용자에게 도움되지만, 세션 토큰은 짧은 만료시간, 사용자에 묶임, 특정 파일 크기와 목적지에 바인딩되어야 합니다. 그렇지 않으면 ‘재개’ 엔드포인트가 스토리지로의 무제한 관문이 될 수 있습니다.
업로드를 차단할 때는 사용자에게 명확한 오류를 반환하되(파일이 너무 큼, 요청이 너무 많음), 내부 정보(스택 트레이스, 버킷 이름, 벤더 정보)는 노출하지 마세요.
안전한 저장 및 전달 선택지
안전한 업로드는 무엇을 허용하느냐뿐 아니라 파일이 어디로 가고 나중에 어떻게 제공되는지도 중요합니다.
바이트 자체를 메인 데이터베이스에 보관하지 마세요. 대부분의 앱은 메타데이터만 DB에 두면 됩니다(소유자 사용자 ID, 원본 파일명, 감지된 타입, 크기, 체크섬, 저장 키, 생성 시간). 바이트는 객체 스토리지나 큰 바이너리에 적합한 파일 서비스에 보관하세요.
공개 파일과 비공개 파일을 스토리지 레벨에서 분리하세요. 다른 버킷이나 컨테이너를 사용해 규칙을 나누세요. 공개 파일(공개 아바타 등)은 로그인 없이 읽을 수 있게 하고, 비공개 파일(계약서, 인보이스, 의료 문서)은 URL을 추측해도 절대 공개되지 않도록 하세요.
가능하면 사용자 파일을 메인 도메인과 같은 도메인에서 제공하지 마세요. 위험한 파일(HTML, 스크립트가 포함된 SVG, 브라우저의 MIME 스니핑 문제)이 통과되면 메인 도메인에서 제공되는 것이 계정 탈취로 이어질 수 있습니다. 전용 다운로드 도메인(또는 스토리지 도메인)을 사용하면 영향 범위를 줄일 수 있습니다.
다운로드 시에는 안전한 헤더를 강제하세요. 허용한 포맷을 기반으로 예측 가능한 Content-Type을 설정하고, 사용자가 주장하는 타입이 아닌 서버 기준으로 지정하세요. 브라우저에서 해석될 수 있는 모든 파일은 가능한 다운로드로 제공하세요.
예상치 못한 일을 막는 몇 가지 기본값:
- 문서에는
Content-Disposition: attachment사용 - 안전한
Content-Type사용(또는application/octet-stream) - 불투명한 오브젝트 키로 저장 및 제공(사용자 파일명 사용 금지)
- 비공개 파일의 다운로드는 로깅
보존 정책도 보안입니다. 방치된 업로드를 삭제하고, 교체 후 오래된 버전을 제거하며, 임시 파일에 시간 제한을 두세요. 저장된 데이터가 적을수록 유출 위험이 줄어듭니다.
서명된 URL: 언제 사용하고 어떻게 묶어야 할까
서명된 URL(프리사인드 URL)은 스토리지 버킷을 공개하지 않고도 사용자가 파일을 업로드/다운로드하게 하거나 API를 통해 모든 바이트를 전달하지 않게 할 때 일반적으로 사용됩니다. URL이 임시 권한을 담고 있으며 만료됩니다.
두 가지 흔한 흐름:
- 직접 스토리지 업로드: 앱이 짧은 기간 유효한 서명된 URL을 발급하고 브라우저가 바로 객체 스토리지에 업로드함.
- 서버 경유 업로드: 파일이 먼저 API로 들어오고 서버가 이를 저장함.
직접 스토리지 업로드는 API 부하를 줄이지만, 스토리지 규칙과 URL 제약이 더 중요해집니다.
서명된 URL을 촘촘하게 묶는 방법
서명된 URL을 일회용 키처럼 다루세요. 구체적이고 짧게 만드세요.
- 쓰기 URL은 빠르게 만료시키세요(보통 1–5분). 읽기 URL도 분 단위가 안전합니다, 며칠 단위는 권장되지 않습니다.
- URL을 예상 오브젝트 키에 바인딩하세요(하나의 오브젝트, 폴더 단위 아님).
- 지원된다면 제약 추가: 예상되는 콘텐츠 타입, 최대 크기, 체크섬 등.
- 권한 검사 후에만 URL을 발급하세요.
- 누가 URL을 요청했는지 기록하세요(사용자 ID, 오브젝트 키, 목적, IP/유저 에이전트).
실무 패턴: 먼저 업로드 레코드(status: pending)를 생성한 다음 서명된 URL을 발급하세요. 업로드 후 오브젝트가 존재하고 예상 크기 및 타입과 일치하는지 확인한 다음에야 ready로 표시합니다.
단계별: 구현 가능한 안전한 업로드 흐름
안전한 업로드 흐름은 대부분 명확한 규칙과 상태입니다. 모든 업로드는 검사 완료 전까지는 신뢰하지 않는 것으로 취급하세요.
각 기능이 허용하는 것을 문서화하세요. 프로필 사진과 세금 문서는 같은 파일 타입, 크기 제한, 가시성을 공유하면 안 됩니다.
실무 흐름(상태 포함)
-
허용 형식과 기능별 최대 크기 정의(예: 사진 최대 5 MB, PDF 최대 20 MB). 백엔드에서도 동일 규칙 강제.
-
바이트가 도착하기 전에 ‘업로드 레코드’를 생성. 저장할 항목: 소유자(사용자 또는 조직), 목적(avatar, invoice, attachment 등), 원본 파일명, 예상 최대 크기,
pending같은 상태. -
비공개 위치로 업로드. 클라이언트가 최종 경로를 선택하게 하지 말 것.
-
서버 측에서 다시 검증: 크기, 매직 바이트/타입, 허용목록. 통과하면 상태를
uploaded로 변경. -
악성코드 검사 후 상태를
clean또는quarantined로 업데이트. 스캔이 비동기라면 결과가 나올 때까지 접근을 잠가 두세요. -
상태가
clean일 때만 다운로드, 미리보기, 처리 허용.
작은 예: 프로필 사진의 경우 사용자와 목적 avatar에 묶인 레코드를 생성하고 비공개로 저장한 뒤 실제로 JPEG/PNG인지 확인(단순 파일명만 믿지 않음), 스캔하고 미리보기 URL을 생성하세요.
과잉 약속하지 않는 기본 악성코드 검사 패턴
악성코드 스캔은 안전망이지 약속이 아닙니다. 알려진 악성 파일과 명백한 속임수를 잡아내지만 모든 것을 탐지하진 못합니다. 목표는 위험을 줄이고 미확인 파일은 기본적으로 무해하게 만드는 것입니다.
신뢰할 수 있는 패턴은 먼저 격리입니다. 새로운 업로드는 모두 비공개 격리 위치에 저장하고 pending으로 표시하세요. 검사 통과 후에야 ‘클린’ 위치로 옮기거나 사용 가능으로 표시하세요.
동기식 스캔은 작은 파일과 저트래픽에서만 적합합니다. 대부분의 앱은 비동기 스캔을 사용합니다: 업로드를 수락하고 ‘처리 중’ 상태를 반환한 뒤 백그라운드에서 스캔합니다.
‘기본 스캔’에 포함되는 것
기본 스캔은 보통 안티바이러스 엔진(또는 서비스)과 몇 가지 보호장치를 포함합니다: AV 스캔, 파일 형식 검사(매직 바이트), 아카이브 한도(zip bomb, 중첩된 zip, 큰 압축 해제 크기), 필요 없는 형식 차단 등.
스캐너가 실패하거나 타임아웃되거나 ‘알 수 없음’ 결과를 내면 파일을 의심스러운 것으로 간주하세요. 격리 상태로 두고 다운로드 링크를 제공하지 마세요. 팀이 실수하는 지점은 ‘스캔 실패’가 ‘그냥 허용’으로 이어지는 경우입니다.
파일을 차단할 때는 메시지를 중립적으로 유지하세요: “이 파일은 허용되지 않습니다. 다른 파일을 사용하거나 지원팀에 문의하세요.” 확신이 없는 상태에서 ‘악성코드가 감지됨’이라고 주장하지 마세요.
예시: 일반 앱에서의 프로필 사진과 문서 업로드
프로필 사진(공개 표시되는 항목)과 PDF 영수증(비공개, 결제/지원 용도)은 모두 업로드 문제지만 서로 다른 규칙을 가져야 합니다.
프로필 사진은 엄격하게: JPEG/PNG만 허용, 크기 제한(예: 2–5 MB), 서버 측에서 재인코딩해 원본 바이트를 그대로 제공하지 않기. 검사 후에 공개 스토리지에 저장.
PDF 영수증은 더 큰 크기 허용(예: 최대 20 MB), 기본 비공개 유지, 메인 앱 도메인에서 인라인으로 렌더링하지 않기.
단순 상태 모델로 사용자를 정보 제공하면서 내부를 노출하지 않는 것이 좋습니다:
- pending: 사용자가 파일을 선택했지만 업로드가 시작되지 않음
- uploaded: 스토리지가 바이트를 받음
- scanning: 백그라운드 작업으로 검사 중
- clean (또는 rejected): 파일 사용 가능(또는 차단됨)
서명된 URL은 여기에 잘 맞습니다: 업로드용 짧은 수명 쓰기 전용 URL(하나의 오브젝트 키)을 사용하고, 상태가 clean일 때만 읽기용 짧은 수명 URL을 발급하세요.
조사에 필요한 항목만 로깅하세요: 사용자 ID, 파일 ID, 타입 추정, 크기, 스토리지 키, 타임스탬프, 스캔 결과, 요청 ID. 원본 내용이나 문서 내 민감한 데이터는 로깅하지 마세요.
흔한 실수와 쉬운 함정
대부분의 업로드 버그는 ‘임시’로 남겨둔 작은 편법이 영구화되면서 발생합니다. 모든 파일을 신뢰하지 말고, 모든 URL이 공유될 수 있다고 가정하며, ‘나중에 고치겠다’는 설정은 잊히기 쉽다는 점을 명심하세요.
자주 나타나는 함정:
- 클라이언트 측 검사만 믿는 것(브라우저는 몇 초 만에 우회 가능)
- 사용자가 경로, 파일명, 오브젝트 키에 영향을 주도록 허용하는 것
- 업로드를 ‘잠시 공개’로 만드는 것
- 너무 오래 사는 또는 여러 사용자에 대해 작동하는 서명된 URL 사용
- 잘못된
Content-Type으로 파일을 제공해 브라우저가 위험한 콘텐츠로 해석하게 하는 것
모니터링은 팀들이 보통 건너뛰는 부분인데 스토리지 비용이 급증할 때까지 눈치채지 못합니다. 업로드량, 평균 파일 크기, 상위 업로더, 오류율을 추적하세요. 하나의 탈취된 계정이 하룻밤 사이에 수천 개의 큰 파일을 업로드할 수 있습니다.
예시: 팀이 사용자 제공 파일명(예: “avatar.png”)으로 아바타를 공유 폴더에 저장했습니다. 한 사용자가 다른 사람의 이미지를 덮어썼습니다. 해결책은 평범하지만 효과적입니다: 서버 측에서 오브젝트 키를 생성하고 업로드를 기본 비공개로 유지하며 제어된 응답을 통해 리사이즈된 이미지를 제공하세요.
빠른 체크리스트와 다음 단계
출시 전 최종 점검으로 이 항목들을 확인하세요. 각각을 릴리스 차단 항목으로 처리하세요. 대부분의 사고는 하나의 누락된 안전장치에서 옵니다.
빠른 체크리스트
- 서버에서 허용목록, 실제 콘텐츠 검사(파일명만이 아닌), 기능별 하드 최대 크기로 검증하세요.
- 업로드는 기본적으로 비공개로 저장하고 파일이 읽히거나 다운로드되거나 미리보기될 때마다 권한을 확인하세요.
- 서명된 URL 업로드를 사용하는 경우 수명이 짧고 하나의 오브젝트 키에 범위가 제한되며 발급 기록을 남겨 남용을 추적할 수 있게 하세요.
- 먼저 격리하고 그다음 스캔하세요: 파일이 클린으로 표시되기 전에는 미리보기나 다운로드를 허용하지 마세요.
- 안전한 다운로드 동작 강제: 예측 가능한
Content-Type, 안전한 파일명, 문서에는attachment사용.
투자 대비 효과가 큰 다음 단계
허용 형식, 최대 크기, 누가 어떤 파일에 접근하는지, 서명된 URL 수명, 그리고 ‘스캔 통과’가 무엇을 의미하는지 평이한 언어로 문서화하세요. 이는 제품, 엔지니어링, 지원 간의 공유 계약이 됩니다.
과다 업로드, 이름 바꾼 실행 파일, 무단 읽기, 만료된 서명된 URL, ‘스캔 대기’ 상태에서의 다운로드 같은 일반 실패를 잡는 몇 가지 테스트를 추가하세요. 이런 테스트는 사고 대비 비용에 비해 저렴합니다.
빠르게 빌드하고 반복하는 팀이라면 변경을 계획하고 안전하게 롤백할 수 있는 워크플로를 사용하는 것이 도움이 됩니다. Koder.ai (koder.ai)를 사용하는 팀은 플래닝 모드와 스냅샷/롤백을 활용하며 업로드 규칙을 점진적으로 강화하는 경향이 있지만, 핵심 요구사항은 변하지 않습니다: 정책은 UI가 아닌 백엔드가 강제해야 합니다.
자주 묻는 질문
파일 업로드를 ‘충분히 안전’하게 만들기 위해 최소한 무엇을 해야 하나요?
먼저 기본은 비공개로 설정하고 모든 업로드를 신뢰하지 않는 입력으로 취급하세요. 서버 측에서 다음 네 가지를 적용하면 대부분의 사고를 막을 수 있습니다:
- 누가 업로드할 수 있는지
- 허용할 파일 형식 (허용목록)
- 용량/빈도 제한 (크기 + 비율 제한)
- 누가 나중에 읽을 수 있는지 (파일별 권한 검사)
이 항목들에 명확히 답할 수 있으면 대부분의 사고를 피할 수 있습니다.
왜 파일 업로드가 흔한 첫 보안 사고인가요?
사용자가 시스템에 ‘미지의 상자’를 보내고 애플리케이션이 그 파일을 저장하며 다른 사람에게 제공할 수 있기 때문에 업로드가 흔한 첫 보안 사고 지점입니다. 그 결과로는:
- 민감한 문서의 무단 접근
- 업로드된 파일이 신뢰된 웹 컨텐츠로 제공되어 피싱 또는 계정 탈취로 이어짐
- 업로드 폭주나 큰 파일로 인한 서비스 중단 및 비용 급증
대부분의 경우 단순히 “바이러스를 업로드했다”는 문제만은 아닙니다.
파일을 저장하는 것과 제공하는 것의 차이는 무엇이며 왜 중요한가요?
저장(스토리지)은 바이트를 보관하는 것입니다. 서빙(전달)은 브라우저와 앱에 그 바이트를 전달하는 방식입니다.
문제는 앱이 사용자 업로드를 메인 사이트와 동일한 신뢰 수준과 규칙으로 제공할 때 발생합니다. 위험한 파일이 일반 페이지처럼 처리되면 브라우저가 실행하거나 사용자가 과도하게 신뢰할 수 있습니다.
더 안전한 기본값은: 먼저 비공개로 저장하고, 제어된 다운로드 응답으로 안전한 헤더를 사용해 제공하는 것입니다.
다른 사용자의 업로드 파일을 다운로드하지 못하게 하려면 어떻게 하나요?
기본 원칙은 기본 거부(default deny) 이며, 파일이 다운로드되거나 미리보기될 때마다 접근을 확인하세요.
실무 규칙:
- 각 파일 레코드는 소유자(사용자/조직)와 용도(아바타, 인보이스 등)를 가져야 합니다.
- 읽기/다운로드 시 해당 특정 파일에 대해 요청자가 허용되는지 검증하세요.
/uploads/같은 폴더 기반 보안은 피하세요.- 서포트 권한은 일시적이고 기록이 남도록 제한하세요(특정 파일에 대한 접근을 부여하고 자동 만료).
대부분의 버그는 단순히 “다른 사용자의 파일을 볼 수 있다”는 실수입니다.
파일명이나 Content-Type을 믿지 않고 파일 형식을 어떻게 검증하나요?
파일 확장자나 브라우저의 Content-Type 헤더를 신뢰하지 마세요. 서버 측에서 검증하세요:
- 기능별 허용목록 사용(예: 아바타는 JPEG/PNG, 영수증은 PDF)
- 서버에서 타입을 감지하고 매직 바이트(파일 시그니처)를 확인
- 저장 시 무작위 ID로 파일명을 교체하고 원본명은 메타데이터로만 보관
- 필요하지 않은 위험한 형식(특히 HTML, SVG, 스크립트 유사 콘텐츠)은 차단
바이트가 허용된 형식과 일치하지 않으면 업로드를 거부하세요.
업로드 기반 DoS 공격을 막기 위해 어떤 제한을 설정해야 하나요?
대부분의 다운타임은 단순한 남용에서 옵니다: 너무 많은 업로드, 큰 파일, 느린 연결이 서버 자원을 묶습니다.
권장 기본값:
- 기능별 최대 크기 설정(아바타는 작게, 문서는 더 크게)
- 여러 계층에서 제한 적용(앱 + 리버스 프록시 + 타임아웃)
- 사용자별·IP별 비율 제한, 익명 트래픽에 더 엄격한 제한 적용
모든 바이트는 비용으로 간주하고 모든 요청을 잠재적 남용으로 취급하세요.
업로드에 서명된 URL을 사용해야 하나요? 안전한 기본값은 무엇인가요?
예, 하지만 신중하게 사용하세요. 서명된 URL은 브라우저가 버킷을 공개하지 않고도 스토리지로 직접 업로드/다운로드할 수 있게 해줍니다.
안전한 기본값:
- 쓰기 URL은 짧게(보통 1–5분) 유지
- 각 URL을 하나의 오브젝트 키에만 적용
- 권한 검사를 통과한 후에만 URL 발급
- 누가 어떤 파일을 위해 요청했는지 기록
Direct-to-storage는 API 부하를 줄여주지만, 범위 지정과 만료는 필수입니다.
구현할 수 있는 안전한 단계별 업로드 흐름은 무엇인가요?
가장 안전한 패턴은:
pending상태로 업로드 레코드 생성- 바이트를 비공개 위치에 업로드
- 서버 측에서 크기 + 타입(매직 바이트) 검증
- 스캔(주로 비동기) 후
clean또는quarantined로 상태 변경 - 상태가
clean일 때만 다운로드/미리보기 허용
이렇게 하면 “스캔 실패” 또는 “처리 중”인 파일이 실수로 공유되는 것을 막을 수 있습니다.
악성코드 스캔이 정말 필요한가요? ‘기본 스캔’은 어떤 것들이 포함되나요?
스캔은 보조 안전장치일 뿐 완전한 보장은 아닙니다. 다음 실무 방식이 도움이 됩니다:
- 먼저 격리(quarantine): 스캔 완료 전까지 링크를 노출하지 마세요.
- 확장자는 악의적 아카이브(예: zip bomb)나 큰 압축 해제 크기를 검사하세요.
- 비동기 스캔을 사용하면 규모 확장에 유리하지만, 스캔 실패/타임아웃시 파일을 의심스럽게 간주하고 차단하세요.
- “스캔되지 않음”이 곧 “사용 가능”을 의미해서는 안 됩니다.
업로드된 파일을 안전하게 전달하려면 어떻게 해야 하나요(헤더, 도메인, 다운로드)?
브라우저가 파일을 웹페이지로 해석하지 못하게 안전하게 제공하세요.
권장 기본값:
- 문서에는
Content-Disposition: attachment사용 - 서버가 선택한 안전한
Content-Type사용(또는application/octet-stream) - URL에 사용자 파일명을 노출하지 않는 불투명한 스토리지 키 사용
- 가능하면 사용자 콘텐츠를 위한 별도 다운로드 도메인 사용
이렇게 하면 업로드된 파일이 피싱 페이지나 스크립트 실행으로 이어질 위험이 줄어듭니다.