online / endpoints 59 / categories 14 / rate 60/min/ip /

GET /xml

GET /xml

Returns XML documents with parser-attack or well-formedness flaws. Targets SOAP, B2B, RSS/sitemap, and config-file ingestion pipelines. Default mode is visible misnesting; other modes cover entity-expansion DoS, external-entity references, and declaration-vs-body encoding mismatch.

mode mismatched-tags (default; `<alpha><beta></alpha></beta>` — every conformant parser rejects), billion-laughs (3 levels of self-similar entity expansion; ~3KB expanded; hardened parsers reject), xxe-external (external entity referencing nonexistent.invalid per RFC 6761; parsers with external-entity resolution hang or fail), encoding-mismatch (declaration claims utf-8 but body bytes are CP1252; strict parsers fail, lenient produce mojibake).

control Compare against the well-formed counterpart: not.catastrophic.io/xml side-by-side

build a request:

expect: 200 OK with Content-Type: application/xml; charset=utf-8. Cache-Control: no-transform prevents the CDN edge from rewriting the body. X-Chaos-Xml-Mode reflects the selected mode; X-Chaos-Xml-Note explains the flaw.

bash
curl -i 'https://chaos.catastrophic.io/xml?mode=mismatched-tags'
# defusedxml refuses billion-laughs, XXE, and DTD by default — the
# safe choice for ingesting untrusted XML. The stdlib xml.etree parser
# silently disables external entities since 3.7.1 but still parses
# internal entities, so billion-laughs hits home.
import urllib.request
from defusedxml import ElementTree as DET
from defusedxml.common import EntitiesForbidden, DTDForbidden

raw = urllib.request.urlopen("https://chaos.catastrophic.io/xml?mode=mismatched-tags").read()
try:
    DET.fromstring(raw)
    print("Parsed (defusedxml accepted)")
except (EntitiesForbidden, DTDForbidden) as e:
    print(f"defusedxml refused: {type(e).__name__}: {e}")
except Exception as e:
    print(f"Other parse error: {type(e).__name__}: {e}")
// Browser DOMParser is permissive by default; check parsererror node.
const res = await fetch("https://chaos.catastrophic.io/xml?mode=mismatched-tags");
const raw = await res.text();
const doc = new DOMParser().parseFromString(raw, "application/xml");
const err = doc.querySelector("parsererror");
console.log("Mode:", res.headers.get("X-Chaos-Xml-Mode"));
console.log("Parsed clean:", !err);
if (err) console.log("Parser said:", err.textContent);
package main

import (
    "encoding/xml"
    "fmt"
    "io"
    "net/http"
    "strings"
)

func main() {
    resp, _ := http.Get("https://chaos.catastrophic.io/xml?mode=mismatched-tags")
    defer resp.Body.Close()
    raw, _ := io.ReadAll(resp.Body)
    fmt.Println("Mode:", resp.Header.Get("X-Chaos-Xml-Mode"))

    // encoding/xml refuses DOCTYPE entirely (since Go 1.13). For modes
    // that use a DOCTYPE (billion-laughs, xxe-external), the decoder
    // returns an error immediately — which is the safe behaviour.
    var v any
    dec := xml.NewDecoder(strings.NewReader(string(raw)))
    err := dec.Decode(&v)
    fmt.Println("Parse error:", err)
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
//             quick-xml = "0.36"
use quick_xml::reader::Reader;
use quick_xml::events::Event;

fn main() -> Result<(), Box> {
    let raw = reqwest::blocking::get("https://chaos.catastrophic.io/xml?mode=mismatched-tags")?.text()?;
    let mut reader = Reader::from_str(&raw);
    reader.config_mut().expand_empty_elements = true;
    loop {
        match reader.read_event()? {
            Event::Eof => break,
            Event::Start(e) => println!("start: {}", String::from_utf8_lossy(e.name().as_ref())),
            Event::End(e)   => println!("end:   {}", String::from_utf8_lossy(e.name().as_ref())),
            _ => {}
        }
    }
    Ok(())
}
// Java's built-in javax.xml.parsers.SAXParser is vulnerable by default
// — disable DTDs and external entities explicitly when parsing
// untrusted input.
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.helpers.DefaultHandler;
import java.net.URI;
import java.net.http.*;

public class XmlChaos {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/xml?mode=mismatched-tags")).build();
        var resp = client.send(req, HttpResponse.BodyHandlers.ofInputStream());

        var f = SAXParserFactory.newInstance();
        f.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
        f.setFeature("http://xml.org/sax/features/external-general-entities", false);
        try {
            f.newSAXParser().parse(resp.body(), new DefaultHandler());
            System.out.println("Parsed clean");
        } catch (Exception e) {
            System.err.println("Refused: " + e.getMessage());
        }
    }
}
// .NET 4.5.2+ disables DTD processing on XmlReader by default. Older
// configurations are vulnerable — set DtdProcessing.Prohibit explicitly.
using System.Xml;
using var client = new HttpClient();
var raw = await client.GetStringAsync("https://chaos.catastrophic.io/xml?mode=mismatched-tags");
var settings = new XmlReaderSettings {
    DtdProcessing = DtdProcessing.Prohibit,
    XmlResolver = null,
};
try {
    using var reader = XmlReader.Create(new StringReader(raw), settings);
    while (reader.Read()) { }
    Console.WriteLine("Parsed clean");
} catch (XmlException e) {
    Console.Error.WriteLine($"Refused: {e.Message}");
}
# Nokogiri::XML defaults to disallowing network access; DTD loading
# still happens unless NONET + NOENT are explicitly cleared.
require "net/http"
require "nokogiri"

resp = Net::HTTP.get_response(URI("https://chaos.catastrophic.io/xml?mode=mismatched-tags"))
raw  = resp.body
puts "Mode: #{resp['X-Chaos-Xml-Mode']}"
doc = Nokogiri::XML(raw) { |c| c.strict.nonet }
puts "Errors: #{doc.errors}"
puts "Root: #{doc.root && doc.root.name}"
# [xml] casts via System.Xml.XmlDocument — strict by default; throws
# on malformed input. Use [xml]::new() with XmlResolver = $null to
# also reject external entity resolution.
$resp = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/xml?mode=mismatched-tags' -SkipHttpErrorCheck
$resp.Headers['X-Chaos-Xml-Mode']
try {
    [xml]$resp.Content | Out-Null
    'Parsed clean'
} catch {
    "Refused: $($_.Exception.Message)"
}