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

GET /pdf

GET /pdf

Returns a structurally flawed PDF where one element of the object graph deliberately lies — wrong xref byte offsets, a page tree claiming 100 pages when only one exists, a trailer referencing a nonexistent encryption dictionary, or a JavaScript OpenAction that fires on open. Default mode ships wrong xref offsets, the most fundamental structural lie.

mode bad-xref (default; xref byte offsets all shifted +100 bytes; object lookup by offset fails), page-count-lie (/Count declares 100 pages; only one page object exists), encrypted-ghost (trailer /Encrypt references object 9 which is not defined), javascript-action (Catalog OpenAction executes app.alert("pdf-chaos") on open; JS-enabled readers fire the alert, JS-disabled readers open silently, AV scanners may flag any JS action).

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

build a request:

expect: A PDF-shaped body whose structure contains one deliberate lie. The specific lie depends on mode. X-Chaos-Pdf-Note explains the flaw in plain text. Cache-Control: no-transform prevents the CDN edge from rewriting the body.

bash
curl -sD - 'https://chaos.catastrophic.io/pdf?mode=bad-xref' -o /tmp/chaos.pdf | grep -i 'content-type\|x-chaos-pdf'
import io, urllib.request
resp = urllib.request.urlopen("https://chaos.catastrophic.io/pdf?mode=bad-xref")
print("Content-Type:", resp.headers.get("Content-Type"))
print("X-Chaos-Pdf-Mode:", resp.headers.get("X-Chaos-Pdf-Mode"))
print("X-Chaos-Pdf-Note:", resp.headers.get("X-Chaos-Pdf-Note"))
raw = resp.read()
# pip install pypdf
try:
    from pypdf import PdfReader
    reader = PdfReader(io.BytesIO(raw))
    print("Pages:", len(reader.pages))
except Exception as e:
    print("PdfReader error:", e)
const res = await fetch("https://chaos.catastrophic.io/pdf?mode=bad-xref");
const bytes = new Uint8Array(await res.arrayBuffer());
console.log("Content-Type:", res.headers.get("content-type"));
console.log("X-Chaos-Pdf-Note:", res.headers.get("x-chaos-pdf-note"));
const header = new TextDecoder().decode(bytes.slice(0, 8));
console.log("Header:", header); // %PDF-1.4
package main

import (
    "fmt"
    "io"
    "net/http"
)

func main() {
    resp, _ := http.Get("https://chaos.catastrophic.io/pdf?mode=bad-xref")
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
    fmt.Println("X-Chaos-Pdf-Note:", resp.Header.Get("X-Chaos-Pdf-Note"))
    fmt.Println("Header bytes:", string(body[:8]))
}
// Cargo.toml: reqwest = { version = "0.12", features = ["blocking"] }
fn main() -> Result<(), Box> {
    let resp = reqwest::blocking::get("https://chaos.catastrophic.io/pdf?mode=bad-xref")?;
    println!("Content-Type: {:?}", resp.headers().get("content-type"));
    println!("X-Chaos-Pdf-Note: {:?}", resp.headers().get("x-chaos-pdf-note"));
    let bytes = resp.bytes()?;
    println!("Header: {:?}", std::str::from_utf8(&bytes[..8.min(bytes.len())]));
    Ok(())
}
import java.net.URI;
import java.net.http.*;

public class PdfChaos {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/pdf?mode=bad-xref")).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-Pdf-Note: " +
            resp.headers().firstValue("X-Chaos-Pdf-Note").orElse(""));
        var body = resp.body();
        System.out.println("Header: " +
            new String(body, 0, Math.min(8, body.length)));
    }
}
using var client = new HttpClient();
var resp = await client.GetAsync("https://chaos.catastrophic.io/pdf?mode=bad-xref");
Console.WriteLine($"Content-Type: {resp.Content.Headers.ContentType}");
Console.WriteLine($"X-Chaos-Pdf-Note: " +
    $"{resp.Headers.GetValues("X-Chaos-Pdf-Note").FirstOrDefault()}");
var bytes = await resp.Content.ReadAsByteArrayAsync();
Console.WriteLine($"Header: " +
    $"{System.Text.Encoding.ASCII.GetString(bytes[..Math.Min(8, bytes.Length)])}");
require "net/http"
uri = URI("https://chaos.catastrophic.io/pdf?mode=bad-xref")
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-Pdf-Note: #{res['X-Chaos-Pdf-Note']}"
    puts "Header: #{res.body[0..7]}"
end
$tmp = Join-Path $env:TEMP 'chaos.pdf'
$r = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/pdf?mode=bad-xref' -OutFile $tmp -PassThru
"Content-Type: $($r.Headers['Content-Type'])"
"X-Chaos-Pdf-Note: $($r.Headers['X-Chaos-Pdf-Note'])"
# Open with default PDF viewer to observe reader behaviour
Start-Process $tmp

What’s deliberately not served

  • Recursive page trees — a Pages node whose /Kids array points back to itself. Likely to cause stack overflows or hangs in strict readers rather than useful errors. Off-brand.
  • Embedded JavaScript beyond app.alert() — form-submit, data-exfil, and known CVE trigger shapes are exploit territory. app.alert("pdf-chaos") is the hello-world of PDF JS testing: it proves JS executes without doing anything harmful.
  • Zip-bomb style content streams — hostile-by-mass. Same posture as /zip.
  • Font/glyph table corruption — requires embedding a custom font object with malformed glyph programs. Different abstraction layer from the object-graph chaos above.