XXEinjector is a Ruby tool for testing XML external-entity behavior using a prepared HTTP request. Its direct and out-of-band modes have different request contracts. A malformed template can fail before the parser behavior you intended to test is reached.
This guide combines the tool reference with the existing local XML lab. It separates source-verified configuration from observations you must collect in your own run, including negative results.
Quick reference
| Option | Meaning and caveat |
|---|---|
--file | Prepared HTTP request containing appropriate XML |
--path | Resource path for the selected mode |
--direct | Response delimiter configuration; not a Boolean YES switch |
--direct-xml | Prints the expected direct-mode XML shape |
--host | Callback address for modes needing a return connection |
--oob | Out-of-band method; upstream defaults to FTP |
--httpport, --ftpport | Listener ports |
--phpfilter | Encodes resource bytes for XML transport on applicable PHP parsers |
--cdata | Direct-result construction that also needs an external DTD |
--2ndfile | Prepared follow-up request for a second-order flow |
--logger | Receives/logs results without sending test requests |
--output | Output file for logger and brute modes |
--proxy | Host:port form in the current parser |
--test | Displays the constructed request instead of sending it |
--timeout | Wait for returned resource content |
--contimeout | Connection-closing timeout |
--verbose | Additional diagnostic output |
The upstream options define further modes. This local walkthrough does not require directory enumeration, credential collection or command execution.
Tested tool and parser versions
Record the exact checkout and runtime; a moving branch and image tag are not version pins. The teaching lab's Dockerfile starts from php:8.2-apache, so the precise PHP patch and libxml version depend on the image you build.
git clone https://github.com/enjoiz/XXEinjector.git
cd XXEinjector
ruby --version
ruby XXEinjector.rb --help
ruby XXEinjector.rb --direct-xml
git rev-parse HEADSuccessful help output establishes only that path through startup. It does not prove all features work on your Ruby release. The source contains legacy API calls, so do not assume that every Ruby 3.x release is compatible.
The request logic and options here were inspected against upstream source. No fresh successful XXEinjector extraction run is claimed. In particular, earlier fixed transcripts and a claimed universal libxml 2.9.14 result are not reliable compatibility guarantees.
The lab target
The existing XXE-basic lab deliberately enables entity substitution and external-DTD loading. It also explicitly calls DOMDocument::xinclude(). These are application choices, not safe parser defaults.
| Endpoint | Response behavior | Observation to separate |
|---|---|---|
/import.php | Renders parsed bookmark name text | Whether resolved data reaches the HTTP response |
/upload-blind.php | Returns a status without echoing the parsed content | Whether external resolution occurred through another observable channel |
xxe-basic-collab service | Local companion HTTP listener | Whether a request arrived, and what it actually contained |
Start only the supplied loopback-bound lab:
git clone https://github.com/ishankaru/techearl-labs.git
cd techearl-labs
docker compose up xxe-basicCompose also starts the companion service. Inspect the runtime in another terminal:
docker compose exec xxe-basic php -r 'echo PHP_VERSION, " ", LIBXML_DOTTED_VERSION, PHP_EOL;'
docker compose logs -f xxe-basic-collabThe companion service has no published host port. Container DNS names are resolved inside the Compose network; they are not automatically usable from a tool running on the host. Likewise, loopback inside a container points to that container, not to a host-side listener. Record where each process runs before interpreting missing callbacks.
Request preparation
A raw HTTP request file is not a cURL command. It contains a method line, headers, a blank line and the XML body. Retain the real endpoint's content type and legitimate document structure.
In direct mode, upstream replaces XXEINJECT inside the supplied XML with a resource reference. It does not turn a body containing only that marker into a complete application document. The direct-mode template also needs response delimiters around the entity's use; --direct=YES does not mean “enable direct mode” in the sense used by the old walkthrough.
Use --direct-xml to inspect the installed tool's expected shape, then compare it with the application's accepted XML. Preserve required elements such as the lab's bookmark/name structure. Request preparation and mode selection must agree. Upstream request construction.
The --test flag prevents the constructed target request from being sent, but startup may still prepare listeners. It is not a promise of a completely side-effect-free command. Keep this old tool in a disposable local environment and inspect its actual behavior before using additional modes.
Existing lab outcomes: establish a normal document first
Check that the intended XML shape reaches the local import endpoint:
curl -s -X POST --data-binary @- \
-H 'Content-Type: application/xml' \
http://127.0.0.1:8086/import.php <<'XML'
<?xml version="1.0"?>
<bookmarks>
<bookmark><name>Local fixture</name><url>http://example.test/</url></bookmark>
</bookmarks>
XMLThe endpoint source renders bookmark name text. If the ordinary document fails, investigate the content type, route and parser error before attributing a failed tool run to security hardening.
In-band entity resolution
The existing lab demonstration uses the container's hostname as a disposable local-file fixture:
curl -s -X POST --data-binary @- \
-H 'Content-Type: application/xml' \
http://127.0.0.1:8086/import.php <<'XML'
<?xml version="1.0"?>
<!DOCTYPE bookmarks [<!ENTITY fixture SYSTEM "file:///etc/hostname">]>
<bookmarks>
<bookmark><name>&fixture;</name><url>http://example.test/</url></bookmark>
</bookmarks>
XMLCompare the response with your container's actual fixture. This request demonstrates the lab's deliberately unsafe external-resolution path when it succeeds; it does not establish what an unrelated parser permits. Record the result instead of copying a predicted transcript.
A local XML filesystem read does not execute PHP merely because the file has a .php suffix. The filesystem stream returns bytes. Base64 encoding can make those bytes easier to transport through XML without parsing conflicts; it is not needed to prevent a filesystem read from “running” the PHP file. PHP filesystem wrapper.
Blind resolution and callback evidence
A static marker appearing in the companion HTTP log proves that a request reached that listener. It does not prove that a file's contents were extracted. Keep the callback URL, timestamp and parser response together so you can distinguish an initial DTD fetch from a later resource transfer.
An HTTP listener that returns a generic page is not interchangeable with a DTD server supplying the document the parser expects. A missing second request can be caused by that mismatch, network reachability, malformed XML, encoding restrictions or parser policy. Do not infer a universal library-version cutoff from one missing callback.
XInclude is a separate path
The lab invokes XInclude explicitly after parsing. Entity substitution and XInclude are distinct operations; disabling one does not automatically prove the other is unused. Review calls to xinclude() and their resource-loading behavior separately. PHP XInclude documentation.
This tool guide does not need an entity-expansion denial-of-service payload to explain that distinction. Keep parser resource limits enabled and verify rejection with bounded fixtures in your test suite.
Default defenses and negative results
PHP documents that libxml2 2.9.0 changed default entity substitution, but unsafe options can re-enable external loading paths. LIBXML_NOENT is misleadingly named: it enables substitution. LIBXML_DTDLOAD enables external DTD loading. Inspect the flags actually passed by your application rather than treating a version number as the complete policy. PHP entity loader documentation.
LIBXML_NONET addresses network access; it is not a complete local-file restriction. PHP 8.4 with libxml2 2.13 or later exposes LIBXML_NO_XXE for blocking external entity loading. Choose the control supported by the deployed runtime and verify it with local-file and network-resolution regression cases. PHP libxml constants.
For an application that does not require external resources:
- Do not enable external DTD loading or entity substitution unnecessarily.
- Suppress external resource resolution using the parser's supported controls.
- Do not invoke XInclude unless the product explicitly needs and constrains it.
- Keep parsing and expansion limits enabled; bound input size and processing resources.
- Give the parsing process minimal filesystem access and restrict outbound networking.
- Return controlled errors instead of exposing filesystem paths or raw parser diagnostics.
Do not replace parser controls with a simple substring search for DOCTYPE. XML encodings and parser behavior need to be handled at the parsing boundary.
Tool scope and limitations
Separate three failure categories: the HTTP request never reached the intended parser; the parser rejected the document or resource; or the tool failed to recognize a result that the application returned. Only the second category can provide evidence about the configured parser defense, and even then it is evidence for that specific case.
Saved results and verbose logs can contain sensitive document data. Keep local lab output separate from real credentials, and do not assume --output relocates every artifact the tool creates.
When finished, remove only these disposable lab containers:
docker compose stop xxe-basic xxe-basic-collab
docker compose rm -f xxe-basic xxe-basic-collabContinue with the XML external entity guide for the underlying issue, blind XXE for the distinction between callbacks and returned data, and XInclude for the separate application-controlled feature.
Sources
Authoritative references this article was fact-checked against.
- XXEinjector — upstream optionsgithub.com
- XXEinjector — request construction and output handlinggithub.com
- PHP — external entity loading and version caveatsphp.net
- PHP — libxml parser constantsphp.net
- PHP — filesystem readsphp.net
- PHP — explicit XInclude processingphp.net
- XXE-basic — local teaching labgithub.com





