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

GET /zip

GET /zip

ZIP archive responses with a deliberate structural flaw — wrong CRC32, central-directory-vs-local-header disagreement, ZIP64 size lie, or truncated central directory. Tests how archive extractors, package managers, and scanners handle metadata-vs-bytes contradictions. Zip-slip and zip-bomb are deliberately not served — see the note below.

mode bad-crc (default; LFH/CDH declare CRC32 0xDEADBEEF, body's real CRC differs), central-dir-mismatch (LFH names alpha.txt, CDH names beta.txt for the same offset and body), zip64-lie (sizes use the 0xFFFFFFFF ZIP64 sentinel and the ZIP64 extra field claims 8 GiB; body is ~40 bytes), truncated (LFH + body intact, central directory cut off mid-record, no EOCD).

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

build a request:

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

bash
# Save the archive, then unzip and check for CRC errors / structural complaints.
curl -sD - 'https://chaos.catastrophic.io/zip?mode=bad-crc' -o /tmp/chaos.zip | grep -i 'content-type\|x-chaos-zip'
unzip -l /tmp/chaos.zip
unzip -t /tmp/chaos.zip
import urllib.request, zipfile, io
resp = urllib.request.urlopen("https://chaos.catastrophic.io/zip?mode=bad-crc")
raw = resp.read()
print("Content-Type:", resp.headers.get("Content-Type"))
print("X-Chaos-Zip-Mode:", resp.headers.get("X-Chaos-Zip-Mode"))
print("X-Chaos-Zip-Note:", resp.headers.get("X-Chaos-Zip-Note"))
try:
    zf = zipfile.ZipFile(io.BytesIO(raw))
    print("Entries:", zf.namelist())
    bad = zf.testzip()
    print("Failed CRC on:", bad)
except zipfile.BadZipFile as e:
    print("BadZipFile:", e)
// Inspect headers and the first few bytes of the archive.
const res = await fetch("https://chaos.catastrophic.io/zip?mode=bad-crc");
const bytes = new Uint8Array(await res.arrayBuffer());
console.log("Content-Type:", res.headers.get("content-type"));
console.log("X-Chaos-Zip-Note:", res.headers.get("x-chaos-zip-note"));
const magic = Array.from(bytes.slice(0, 4))
    .map(b => b.toString(16).padStart(2, "0")).join("");
console.log("First 4 bytes (504b0304 = LFH signature):", magic);
package main

import (
    "archive/zip"
    "bytes"
    "fmt"
    "io"
    "net/http"
)

func main() {
    resp, _ := http.Get("https://chaos.catastrophic.io/zip?mode=bad-crc")
    defer resp.Body.Close()
    body, _ := io.ReadAll(resp.Body)
    fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
    fmt.Println("X-Chaos-Zip-Note:", resp.Header.Get("X-Chaos-Zip-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  declared=%d crc=%08x\n", f.Name, f.UncompressedSize64, f.CRC32)
    }
}
// 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/zip?mode=bad-crc")?;
    println!("Content-Type: {:?}", resp.headers().get("content-type"));
    println!("X-Chaos-Zip-Note: {:?}", resp.headers().get("x-chaos-zip-note"));
    let bytes = resp.bytes()?;
    match zip::ZipArchive::new(Cursor::new(bytes.to_vec())) {
        Ok(mut zf) => {
            for i in 0..zf.len() {
                let f = zf.by_index(i)?;
                println!("  {}  declared={}  crc={:08x}", f.name(), f.size(), f.crc32());
            }
        }
        Err(e) => println!("ZipArchive error: {}", e),
    }
    Ok(())
}
// Java 11+ HttpClient + java.util.zip.
import java.net.URI;
import java.net.http.*;
import java.util.zip.*;
import java.io.*;

public class ZipChaos {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();
        var req = HttpRequest.newBuilder(URI.create("https://chaos.catastrophic.io/zip?mode=bad-crc")).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-Zip-Note: " +
            resp.headers().firstValue("X-Chaos-Zip-Note").orElse(""));
        try (var zis = new ZipInputStream(new ByteArrayInputStream(resp.body()))) {
            ZipEntry e;
            while ((e = zis.getNextEntry()) != null) {
                System.out.printf("  %s  declared=%d crc=%08x%n",
                    e.getName(), e.getSize(), e.getCrc());
            }
        } catch (ZipException ze) {
            System.out.println("ZipException: " + ze.getMessage());
        }
    }
}
using System.IO.Compression;
using var client = new HttpClient();
var resp = await client.GetAsync("https://chaos.catastrophic.io/zip?mode=bad-crc");
Console.WriteLine($"Content-Type: {resp.Content.Headers.ContentType}");
Console.WriteLine($"X-Chaos-Zip-Note: {resp.Headers.GetValues("X-Chaos-Zip-Note").FirstOrDefault()}");
var bytes = await resp.Content.ReadAsByteArrayAsync();
try {
    using var zf = new ZipArchive(new MemoryStream(bytes), ZipArchiveMode.Read);
    foreach (var e in zf.Entries) {
        Console.WriteLine($"  {e.FullName}  declared={e.Length}  crc={e.Crc32:x8}");
    }
} catch (InvalidDataException ex) {
    Console.WriteLine($"InvalidDataException: {ex.Message}");
}
require "net/http"
require "tempfile"
uri = URI("https://chaos.catastrophic.io/zip?mode=bad-crc")
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-Zip-Note: #{res['X-Chaos-Zip-Note']}"
    Tempfile.create(["chaos", ".zip"]) do |f|
        f.binmode; f.write(res.body); f.flush
        puts `unzip -l #{f.path} 2>&1`
    end
end
# Invoke-WebRequest -OutFile saves the archive, then Expand-Archive checks structure.
$tmp = Join-Path $env:TEMP 'chaos.zip'
$r = Invoke-WebRequest -Uri 'https://chaos.catastrophic.io/zip?mode=bad-crc' -OutFile $tmp -PassThru
"Content-Type: $($r.Headers['Content-Type'])"
"X-Chaos-Zip-Note: $($r.Headers['X-Chaos-Zip-Note'])"
try {
    $dst = Join-Path $env:TEMP ('chaos-out-' + [Guid]::NewGuid())
    Expand-Archive -Path $tmp -DestinationPath $dst -ErrorAction Stop
    Get-ChildItem $dst | Format-Table Name, Length
} catch {
    "Expand-Archive failed: $($_.Exception.Message)"
}

What’s deliberately not served

Two famous ZIP attack classes are missing from the mode list on purpose:

  • zip-slip — a working path-traversal payload differs from a real attack only by the filename string and body, both swappable with a hex editor. Serving one — even with an inert filename like ../escaped.txt — would amount to hosting a turnkey exploit template, and the handler source on GitHub is itself a generator.
  • zip-bomb — hostile-by-mass. The site’s posture is chaos that misbehaves, not payloads designed to exhaust the resources of whatever opens them.

The four modes above keep the structural-ambiguity story without serving anything that’s already an exploit shape.