XmlDocumentSurrogateXxe
gadget
A [Serializable] IObjectReference carrier in System.Workflow.ComponentModel whose GetRealObject sets XmlDocument.InnerXml, so the target parses your XML. Variant 1 makes the target fetch your URL; variant 2 reads a target file back to you. No surrogate selector has to be registered. Needs System.Workflow.ComponentModel. Fires when the target app uses pre-4.5.2 XML resolver defaults, when the machine turned the EnableLegacyXmlSettings switch back on, or when the app declares NO target framework moniker at all - an ASP.NET app with no <httpRuntime targetFramework> is legacy even on a fully patched 4.8.1 machine.
Declared metadata, not a runtime result. How to read evidence.
.\ysonet.exe -g XmlDocumentSurrogateXxe -hTarget declarations
| Variant | Formatters | Inputs | Requirements | Runtime tokens |
|---|---|---|---|---|
| 1 | BinaryFormatter, SoapFormatter, LosFormatter, NetDataContractSerializer, DataContractSerializer, DataContractJsonSerializer, FsPickler | remote-url | built-in, net-framework | net-fx-4.0, net-fx-4.5, net-fx-4.5.1 |
| 2 | BinaryFormatter, SoapFormatter, LosFormatter, NetDataContractSerializer, DataContractSerializer, DataContractJsonSerializer, FsPickler | remote-url, target-path | built-in, net-framework | net-fx-4.0, net-fx-4.5, net-fx-4.5.1 |
Variants
- 1: Fetch an external DTD: the target requests your URL (SSRF, default) (default)
- 2: Read a target file out of band: needs --file, --dtd-out and a host you control
Options (4)
| Option | Help | Default | Required |
|---|---|---|---|
var|variant= | Which XML to put in the carrier's innerXml field. Choices: 1 (default) - declare one external parameter entity pointing at the -c URL and reference it, so the target FETCHES that URL. The effect is a single outbound request (SSRF / callback) and nothing comes back to you. Point it at any http or https URL you can observe. 2 - read a file on the TARGET and have the target send its content to you. -c is the BASE URL of a host you control; ysonet builds two URLs under it, "xmldocsurrogate-oob.dtd" and "collect". --file names what to read on the TARGET, in whatever form that target's parser resolves. --dtd-out is where ysonet writes the DTD you must publish at the first URL: without it hosted, the payload fetches a 404 and nothing is disclosed. Read the file content out of the query string of the request that arrives at the second URL. Default value: "1". Suggested values: 1, 2. | 1 | false |
rawinput | Skip the URL validation and put -c into the DTD external identifier exactly as typed, with no trimming. Normal mode accepts an absolute http or https URL and refuses whitespace, control characters and the characters " < > \, because the value goes inside a QUOTED DTD external identifier and those would corrupt or escape it. Use this only for research on a resolver that accepts something else; the network effect this gadget declares was proven with http(s). It does NOT turn off the outer formatter's escaping, so the payload is still a valid document - but nothing checks that the identifier inside it still is, and a broken one simply fetches nothing. Default value: "false". | false | false |
file= | Variant 2 only, and required there. What to read ON THE TARGET, usually an absolute file: URI, for example "file:///C:/Windows/system.ini". The value is NOT validated or rewritten: it goes into the hosted DTD exactly as typed, so a bare path, a UNC path, an http URL or any other form the target's XML parser resolves is accepted, and finding out what it resolves is the point. Nothing is opened on this machine. It sits in a quoted DTD system identifier, where no references are recognised, so '%' and '&' are literal and "file:///C:/Program%20Files/x.txt" is the right way to write a space; only a double quote ends the identifier and breaks the DTD. WHAT COMES BACK RELIABLY, measured rather than assumed: the content travels in a URL query string, so spaces, line breaks, < > and " all arrive percent-encoded and can be decoded. Any of & % ' # in the file BREAKS the chain and you get no second request at all, because the first three end a construct inside the DTD and the last one starts a URI fragment. That is why a short .ini style file comes back whole and a file full of entity references or apostrophes does not. | Not declared | false |
dtd-out= | Variant 2 only, and required there. Where to write the companion DTD you have to publish at <-c>/xmldocsurrogate-oob.dtd. The path is taken as given: a file already there is REPLACED and a missing folder is created, so generating twice to the same path works and ysonet says on stderr when it replaced something. The DTD is written only after the payload is built, so a failed run leaves what is already at that path alone. UTF-8 with no BOM, because the target reads it as an external subset with no XML declaration to learn an encoding from. This is separate from --outputpath, which still receives the payload itself. | Not declared | false |
Credit: [Finders: Soroush Dalili]