GET /ooxml
Office Open XML container responses (DOCX, XLSX, PPTX) where one structural part of the package deliberately lies — wrong content type declaration, missing referenced part, dangling image relationship, or [Content_Types].xml claiming the package is multiple formats simultaneously. Macro-enabled containers, encrypted OOXML, and embedded OLE objects are deliberately not served — see the note below.
control Compare against the well-formed counterpart: not.catastrophic.io/ooxml side-by-side
expect: An OOXML-shaped body (DOCX/XLSX/PPTX, picked by `format`) whose internal package structure contains one deliberate lie. The specific lie depends on `mode`. X-Chaos-Ooxml-Note explains the flaw in plain text. Cache-Control: no-transform prevents the CDN edge from rewriting the body.
# Save the archive, then inspect the OOXML package structure.
curl -sD - 'https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type' -o /tmp/chaos.docx | grep -i 'content-type\|x-chaos-ooxml'
unzip -p /tmp/chaos.docx '[Content_Types].xml' | head -20
unzip -l /tmp/chaos.docx
# python-docx / openpyxl / python-pptx are strict; chaos modes hard-fail.
import urllib.request, io, zipfile
resp = urllib.request.urlopen("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type")
raw = resp.read()
print("Content-Type:", resp.headers.get("Content-Type"))
print("X-Chaos-Ooxml-Mode:", resp.headers.get("X-Chaos-Ooxml-Mode"))
print("X-Chaos-Ooxml-Note:", resp.headers.get("X-Chaos-Ooxml-Note"))
zf = zipfile.ZipFile(io.BytesIO(raw))
print("Parts:", zf.namelist())
print("Content_Types.xml:")
print(zf.read("[Content_Types].xml").decode())
// Inspect headers; the body is binary, leave parsing to a library.
const res = await fetch("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type");
const bytes = new Uint8Array(await res.arrayBuffer());
console.log("Content-Type:", res.headers.get("content-type"));
console.log("X-Chaos-Ooxml-Format:", res.headers.get("x-chaos-ooxml-format"));
console.log("X-Chaos-Ooxml-Mode:", res.headers.get("x-chaos-ooxml-mode"));
console.log("X-Chaos-Ooxml-Note:", res.headers.get("x-chaos-ooxml-note"));
console.log("Bytes:", bytes.length);
package main
import (
"archive/zip"
"bytes"
"fmt"
"io"
"net/http"
)
func main() {
resp, _ := http.Get("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type")
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
fmt.Println("X-Chaos-Ooxml-Note:", resp.Header.Get("X-Chaos-Ooxml-Note"))
r, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
fmt.Println("zip.NewReader error:", err)
return
}
for _, f := range r.File {
fmt.Printf(" %s\n", f.Name)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
// zip = "2"
use std::io::Cursor;
fn main() -> Result<(), Box> {
let resp = reqwest::blocking::get("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type")?;
println!("Content-Type: {:?}", resp.headers().get("content-type"));
println!("X-Chaos-Ooxml-Note: {:?}", resp.headers().get("x-chaos-ooxml-note"));
let bytes = resp.bytes()?;
let mut zf = zip::ZipArchive::new(Cursor::new(bytes.to_vec()))?;
for i in 0..zf.len() {
println!(" {}", zf.by_index(i)?.name());
}
Ok(())
}
// Java 11+ HttpClient.
import java.net.URI;
import java.net.http.*;
import java.util.zip.*;
import java.io.*;
public class OoxmlChaos {
public static void main(String[] args) throws Exception {
var client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type")).build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofByteArray());
System.out.println("Content-Type: " +
resp.headers().firstValue("Content-Type").orElse(""));
System.out.println("X-Chaos-Ooxml-Note: " +
resp.headers().firstValue("X-Chaos-Ooxml-Note").orElse(""));
try (var zis = new ZipInputStream(new ByteArrayInputStream(resp.body()))) {
ZipEntry e;
while ((e = zis.getNextEntry()) != null) {
System.out.println(" " + e.getName());
}
}
}
}
using System.IO.Compression;
using var client = new HttpClient();
var resp = await client.GetAsync("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type");
Console.WriteLine($"Content-Type: {resp.Content.Headers.ContentType}");
Console.WriteLine($"X-Chaos-Ooxml-Note: {resp.Headers.GetValues("X-Chaos-Ooxml-Note").FirstOrDefault()}");
var bytes = await resp.Content.ReadAsByteArrayAsync();
using var zf = new ZipArchive(new MemoryStream(bytes), ZipArchiveMode.Read);
foreach (var e in zf.Entries) {
Console.WriteLine($" {e.FullName}");
}
require "net/http"
require "tempfile"
uri = URI("https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type")
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
res = http.get(uri.request_uri)
puts "Content-Type: #{res['Content-Type']}"
puts "X-Chaos-Ooxml-Note: #{res['X-Chaos-Ooxml-Note']}"
Tempfile.create(["chaos", ".docx"]) do |f|
f.binmode; f.write(res.body); f.flush
puts `unzip -l #{f.path}`
end
end
# Save the OOXML package, then inspect [Content_Types].xml.
$tmp = Join-Path $env:TEMP 'chaos.docx'
$r = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/ooxml?format=docx&mode=wrong-content-type' -OutFile $tmp -PassThru
"Content-Type: $($r.Headers['Content-Type'])"
"X-Chaos-Ooxml-Mode: $($r.Headers['X-Chaos-Ooxml-Mode'])"
"X-Chaos-Ooxml-Note: $($r.Headers['X-Chaos-Ooxml-Note'])"
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($tmp)
$zip.Entries | Select-Object FullName, Length | Format-Table
$zip.Dispose()
What’s deliberately not served
Three OOXML attack classes are missing from the mode list on purpose:
- Macro-enabled containers (DOCM, XLSM, PPTM with VBA payloads) — a working VBA macro is a known malware vector and would be effectively indistinguishable from a real attack.
- Encrypted OOXML — wrapped in a Compound File Binary container rather than a ZIP, a separate format problem off the OOXML-chaos topic.
- Embedded OLE objects — can carry executable payloads.
The four modes above keep the package-structure-disagreement story
without serving anything that’s already an exploit shape, consistent
with /zip.
Behaviour across readers
OOXML readers vary substantially in how strictly they validate the package. The same chaos mode can hard-fail in one reader and open cleanly in another — which is itself useful chaos. Observed from the canonical Python libraries:
| Mode | python-docx | openpyxl | python-pptx |
|---|---|---|---|
wrong-content-type | ValueError (not a Word file) | OSError (no valid workbook part) | ValueError (not a PowerPoint file) |
missing-part | KeyError (no word/document.xml) | KeyError (no xl/workbook.xml) | KeyError (no relationship of type officeDocument) |
dangling-relationship | KeyError (no word/media/image1.png) | KeyError (rId1) | OPENED (relationship resolved lazily) |
format-confusion | OPENED | OPENED | OPENED |
format-confusion opens cleanly in all three because the readers
match the first matching Override entry and ignore the extras.
Tools that enumerate every Override and dispatch by content type
behave differently.