(In)Security of File Uploads in Node.js

2024년에 나온 구글에서 Florida 대학교와 협업하여 작성한 논문입니다. Node.js 환경에서 파일 업로드 시 발생할 수 있는 취약점 13가지를 열거하고, 상용중인 유명 파일 업로드 라이브러리들이 이 취약점에 얼마나 해당되는지를 분석한 논문입니다.
취약점들을 살펴보면서 파일 업로드 시 서버에 어떤 방어로직들을 작성해야하는지 알아볼 수 있습니다.
이러한 파일 취약점을 본 논문에서는 Unrestricted File Upload라고 기술합니다. 이후부턴 UFU로 칭합니다.
Node.js 에서는 파일 업로드 로직 작성 시에 대개 서드파티 라이브러리를 이용합니다. 대표적으론 formildable, multer와 같은 라이브러리가 존재합니다. 이러한 라이브러리를 이용하여 파일 업로드를 구현하였을 때 잠재적인 UFU 노출 가능성을 파악합니다.
만약 웹 애플리케이션이 UFU에 노출되어 해커에 의해 악용될 경우, XSS나 타 유저의 개인정보 유출 등 심각한 보안사고를 야기할 수 있습니다.
Attack descriptions
1. File name-based attacks
[A1] File Extension Injection
이 공격에서 공격자는 웹 애플리케이션에서 부적절한 파일 이름 확장자 제어를 악용하기 위해 파일 이름의 확장자를 수정합니다. 예를 들어, 공격자는 파일 확장자를 기반으로 파일 유효성 검사 로직을 우회하기 위해 여러 파일 확장자를 삽입할 수 있습니다.
test.js
와 같은 시드 파일을 입력받아,test.js.png
(악성.js
확장자를 숨기고.png
로 보이게 함) 또는test.png.js
(유효한.png
확장자 뒤에 실행 가능한.js
를 붙임)와 같이 여러 확장자를 무작위로 붙여 주입하거나,test
(확장자를 완전히 제거함)와 같이 파일 확장자를 제거하기도 합니다. 또한,test.Js
또는testJS
와 같이 확장자의 대소문자를 무작위로 변경하여 확장자를 위장하거나seed.html5
,test.js6
와 같은 특이한 확장자를 추가하기도 합니다. 심지어seed.pdf.html.png
와 같이 세 개의 확장자를 추가하거나,test.hTml.jPEg
와 같이 무작위로 대소문자를 섞거나jsx
,mjs
,xhtml
과 같은 특이한 확장자를 사용할 수도 있습니다.[A2] Null Byte Injection
이 공격에서 공격자는 애플리케이션의 의도된 로직을 변경하기 위해 파일 이름에 null 바이트를 삽입합니다.
공격자는 이러한 유형의 공격을 수행하기 위해 파일 이름의 여러 부분을 삽입할 수 있습니다. 예를 들어, File Extension Injection 공격 (A1)과 유사하게 공격자는 금지된 확장자와 허용된 확장자 사이에 null 바이트를 삽입하여 대상 Node.js 애플리케이션의 의도된 로직을 변경합니다.
예를 들어 파일 이름 내 임의의 위치에 널 바이트 문자를 삽입하여
test.js%00.png
또는test.js%.png
와 같은 파일 이름을 생성합니다.널 바이트 문자 삽입 공격은 사실 Node.js 환경에서는 큰 문제가 되지 않을 수 있습니다. c 언어 환경에서는 문제가 발생할 수 있습니다.
char s[] = "test.js\0.png"; printf("%s", s); // 출력: "test.js"
위처럼 널 바이트 이후의 문자열을 제외시킬 수 있습니다. 만약 Node.js가 파일 이름을 파싱할 때 JS API만 사용하지 않고 C언어 기반 서드파티 도구(ImageMagick, FFmpeg 등)를 활용할 때 취약해질 수도 있습니다.
[A3] Script-named file name
이 공격에서 공격자는 XSS payload와 같은 스크립트를 파일 이름에 삽입하며, 업로드된 파일 이름이 제대로 삭제되지 않으면 피해자의 브라우저에서 payload 실행을 트리거할 수 있습니다.
test.png.js
와 같은 JavaScript 시드 페이로드 파일 이름을 test.png[payload].js
와 같이 스크립트 페이로드를 파일 이름 내 임의의 위치에 주입하여 변환할 수 있습니다
[A4] Path Traversal
이 공격에서 공격자는 악성 문자를 파일 이름에 삽입하여 경로 순회 공격을 수행하여 Node 서버의 제한된 디렉토리 외부의 디렉토리에 액세스할 수 있습니다.
유효한 PNG 파일 file.png
를 입력받아 /../..png
와 같은 파일 이름을 생성할 수 있습니다
[A5] Overwrite Attack
이 공격에서 공격자는 특히 서버 구성 파일을 대상으로 하는 웹 애플리케이션 서버에서 파일을 덮어쓰는 것을 목표로 서버 설정을 악의적으로 변경합니다. 이 공격을 통해 공격자는 대상 웹 애플리케이션의 작동에 중요한 역할을 하는 중요한 구성 파일을 외부에서 제어할 수 있습니다.
2.File type-based attacks
[A6] MIME Type Spoofing
파일의 content-type
은 파일과 해당 구조를 설명하는 파일의 MIME type
을 나타냅니다. 파일 업로드 라이브러리는 MIME type
을 사용하여 파일 유형의 유효성을 검사할 수 있습니다. 그러나 공격자는 파일의 content type
을 수정하거나 스푸핑하여 이 시도를 쉽게 우회할 수 있습니다. 대상 서버가 파일 content
의 유효성을 검사하기 위해 MIME type
검사에만 의존하는 경우 MIME type
스푸핑을 통해 공격자는 검사를 우회하고 악성 payload 파일을 업로드하여 서버 측에서 코드 실행을 유도할 수 있습니다.
공격자는 실제로는 자바스크립트 파일(test.js
)이지만, HTTP 요청의 Content-type
을 'application/pdf
'와 같이 다른 허용된 MIME type
으로 변경하여 업로드합니다. 서버가 MIME type
만으로 파일 유효성을 검사한다면, 이 악성 .js
파일을 PDF로 인식하여 업로드를 허용하게 됩니다.
[A7] Magic Byte Spoofing
파일 유형의 유효성을 검사하는 데 사용되는 또 다른 기술은 magic header byte
를 확인하는 것입니다. 공격자는 스크립트와 같은 악성 파일을 만들고 magic byte
를 PNG 파일과 같은 다른 파일 유형으로 변경하여 웹 애플리케이션에서 수행하는 파일 유형 유효성 검사를 우회할 수 있습니다. 이 공격은 서버에서 악성 코드 실행으로 이어질 수 있습니다.
Polyglot
파일은 여러 다른 파일 형식에서 유효한 파일이므로 공격자가 이러한 파일을 만들어 악성 payload
를 숨기고 웹 애플리케이션의 파일 유형 유효성 검사 로직을 우회할 수 있습니다. 공격자가 파일의 magic bytes
및/또는 MIME type
만 변경하는 스푸핑 기반 공격과 달리, polyglot
파일은 여러 파일 형식의 구문과 의미를 병합하여 구성됩니다. 결과적으로 웹 애플리케이션은 스푸핑 기반 공격에 대해 복원력이 있지만 polyglot
파일 공격에 취약할 수 있습니다. Polyglot
파일은 악성 스크립트를 삽입하고 웹 애플리케이션의 파일 업로드 메커니즘의 content security policy
를 우회하는 데 사용될 수 있으며, XSS
및 RCE
와 같은 다양한 유형의 공격으로 이어질 수 있습니다.
공격자는 실행 가능한 스크립트 파일(malicious.sh
또는 malicious.php
)을 만들고, 이 파일의 시작 부분에 PNG
파일의 매직 바이트인 89 50 4E 47 0D 0A 1A 0A
를 삽입합니다. 이 파일은 매직 바이트 검사를 통해 PNG
파일로 인식될 수 있지만, 내부에는 악성 스크립트가 포함되어 있어 업로드 후 특정 조건에서 실행될 수 있습니다.
[A8] JS+JPEG Polyglot
이 유형의 polyglot
파일은 JPEG
및 JS
파일 형식 모두에서 유효합니다. 웹 애플리케이션의 content
필터링 메커니즘이 JPEG 파일로 허용하면 서버에 업로드됩니다. 파일이 웹 애플리케이션 서버에 업로드되면 공격자는 파일을 원격으로 액세스하거나 구문 분석 중에 서버를 다운시킬 수 있는 악성 payload
를 실행할 수 있습니다.
기존 PNG(JPEG)
파일을 읽어 헤더 크기를 계산하고, PNG
이미지로서의 유효성에 영향을 주지 않으면서 널 바이트 시퀀스 뒤에 JavaScript 페이로드를 파일에 주입하여 PNG+JS
폴리글랏 파일을 생성합니다. 이 파일이 서버에 업로드되면, 공격자는 원격으로 파일에 접근하거나 파싱하는 동안 서버 다운을 유발하여 악성 페이로드를 실행할 수 있습니다.
하지만 실제로 해당 취약점의 위험성이 크진 않습니다. 현대 브라우저에서
img
태그를 통해polyglot
파일(test.jpg
)을 실행시킬 경우,js
코드는 실행되지 않습니다. 하지만 이 파일을<script src=”test.jpg”/>
혹은iframe
에 실행시킬 경우는js
코드가 해석되어 실행됩니다. 보통jpg
파일을script
나iframe
태그로 실행시킬 일은 많지 않지만 실행가능성을 인지는 하고 있어야합니다.
[A9] HTML+PDF Polyglot
PDF+HTML polyglot
파일은 PDF 및 HTML 파일 형식 모두에서 유효합니다. 공격자가 웹 애플리케이션의 content security
검사를 우회하고 HTML
파일 내에 악성 payload
를 삽입하는 데 사용할 수 있습니다. JS+JPEG Polyglot
파일과 유사하게 공격자는 브라우저에서 파일을 원격으로 액세스하여 악성 payload
를 실행할 수 있습니다.
[A10] Executable File Upload Attack
이 공격에서 공격자는 웹 애플리케이션의 클라이언트 또는 서버 측에서 실행될 수 있는 실행 파일 (예: EML, HTML)을 업로드합니다. 이 공격에서 공격자는 HTML payload 파일을 대상 웹 애플리케이션에 업로드합니다. 업로드된 payload 파일은 피해자를 악성 웹 사이트로 리디렉션하거나 payload 파일에 포함된 JavaScript payload
를 실행할 수 있습니다.
3.File content-based attacks
[A11] JavaScript Embedded PDF
이 공격에서 공격자는 PDF
문서 내부에 악성 JavaScript
코드를 삽입합니다. 예를 들어, 공격자는 JavaScript payload
를 PDF
문서에 삽입하고 웹 애플리케이션에 업로드하여 저장된 XSS
공격을 수행할 수 있습니다.
공격자는 자바스크립트 페이로드를 PDF
문서에 주입하고 이를 웹 애플리케이션에 업로드하여 저장된 XSS
공격을 수행할 수 있습니다
[A12] PDF Bomb Attack
이 공격은 공격자가 PDF
파일의 인코딩 옵션을 남용하여 스트림을 압축하는 것을 포함합니다. 악성 PDF
파일이 웹 애플리케이션 서버에 업로드되면 content
를 압축 해제하여 대상 서버에서 리소스를 소모시켜 Dos 상태를 유발할 수 있습니다.
[A13] SVG File Upload Attack
SVG
파일 공격은 인라인 JavaScript
코드를 지원하는 SVG 파일의 기능을 악용합니다. 이 공격에서 공격자는 JS payload
를 SVG
파일에 삽입하여 XSS
와 같은 다양한 유형의 공격을 수행합니다. 이 공격에서, 공격자는 XSS
와 같은 다양한 유형의 공격을 달성하기 위해 JS
페이로드를 SVG
파일에 삽입합니다.
악성 스크립트가 포함된 SVG
파일을 아이콘 형태로 만들어 업로드하고, 사용자가 이 파일을 브라우저에서 열거나 웹 페이지에 표시될 때 스크립트가 실행되어 사용자의 세션 정보를 탈취하거나 악성 웹사이트로 리디렉션할 수 있습니다.
Analyzing Libraries & Applications
본 논문은 위 취약점들을 기반으로 분석할 수 있는 툴 NodeSec을 개발하여, 실제 Node.js 환경에서 대중적으로 사용되는 라이브러리, 실제 애플리케이션을 대상으로 대입 및 분석을 진행했습니다.
위 라이브러리들은 File content-based attacks(A11 ~ A13)에 대해 안전하지 않았다고 보고합니다.
실제 상용 애플리케이션(버전 참고)에 대해 NodeSec을 대입해보았을 때 위와 같은 결과를 얻었다고 합니다.
Root Causes & Recommendations
본 논문은 파일 업로드 취약점에 대한 근본적인 원인은 포괄적인 보안 문서가 부족했다는 점을 지적합니다. 이런 결함은 보안에 대해 잘 모르는 개발자에게는 중요한 문제인데, 그들은 이러한 문제에 대해 인지하지 못할 수 있기 때문입니다.
또한 보안 테스트 케이스의 부족 문제도 존재합니다. 서비스를 구현 후 표준화된 테스트 케이스를 돌려 문제점을 일괄적으로 찾을 수 있다면 보안 사고가 능성을 줄일 수 있다고 이야기합니다.
So, what can we do?
Node.js 애플리케이션에서 안전한 파일 업로드를 구현하기 위해서는 세 가지 주요 목표를 달성해야 합니다.
- 파일 이름 유효성 검사 (File Name Validation)
업로드된 파일 이름에 무작위로 생성된 안전한 문자열(예: UUID)을 할당합니다. 이는 공격자가 주입한 악성 문자가 파일 이름에 포함되는 것을 근본적으로 방지하여 대부분의 파일 이름 기반 공격을 무력화할 수 있습니다.
파일 이름에서 악성 문자를 제거하는 파일 이름 위생 처리(sanitization) 기능을 구현하거나, 이를 수행하는 서드 파티 패키지를 통합합니다. 예를 들어, wikijs
는 sanitize-filename
패키지를 사용하여 업로드된 파일 이름을 처리합니다.
- 파일 유형 유효성 검사 (File Type Validation)
클라이언트가 제공하는 MIME type
은 쉽게 스푸핑될 수 있으므로 신뢰해서는 안 됩니다. 파일 업로드 요청에서 MIME type
만 확인하는 것에 의존하면 안됩니다.
클라이언트에서 제공하는 Content-Type 헤더는 쉽게 조작될 수 있기 때문에 무시하거나 최소한의 1차 필터링으로만 사용합니다. 하십시오. 이는 쉽게 조작될 수 있습니다.
파일의 내용을 읽어 magic byte
또는 file signature
를 확인하여 실제 파일 형식을 식별합니다. file-type
, exif-js
, image-type
과 같은 라이브러리를 활용할 수 있습니다.
허용된 파일 형식의 화이트리스트를 엄격하게 관리하고, 그 외의 모든 파일은 거부하십시오.
HTML, SVG
등 스크립트를 포함할 수 있는 파일 형식에 대해서는 File Content Sanitization를 반드시 적용하여 script 태그를 제거합니다.
- 파일 콘텐츠 위생 처리 (File Content Sanitization)
파일 내부에 삽입된 악성 콘텐츠(예: 스크립트)를 Sanitization 처리해야 합니다.
파일을 위생 처리하거나 임의 코드 실행을 방지하기 위한 보안 헤더를 설정하기 위해 오픈 소스 패키지를 활용합니다. 예를 들어 sanitize-html
패키지를 사용하여 SVG 업로드 공격(A13)에 대응합니다.
A12(PDF 폭탄 공격)와 같은 DoS 공격을 방지하기 위해 파일 크기 제한 및 유효성 검사를 추가하는 것도 중요합니다.
References
Subscribe to my newsletter
Read articles from ByeongGyuKim directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
