#!/usr/bin/env python3 """ PoC: GGUF/ZIP Structural Polyglot -- Security Scanner Bypass via Interpretation Conflict (CWE-436) Root cause: the GGUF specification never requires an implementation to verify that a file's total length matches exactly what its declared header/tensor-info/tensor-data actually consumes. Confirmed empirically across THREE independent GGUF implementations, in three different languages, by three different authors/teams: - gguf-py (Python, ggml-org/llama.cpp reference implementation) - fs/gguf (Go, ollama/ollama's own independent reimplementation) - gguf crate (Rust, independently authored by Jiayu Liu) All three accept a file consisting of [valid GGUF content][arbitrary trailing bytes] as a fully valid GGUF file, silently ignoring everything after the last tensor's data. Separately, Python's own `zipfile` module builds ZIP archives by locating the "End of Central Directory" record by scanning backward from EOF -- meaning a ZIP archive can be validly opened even when arbitrary bytes precede it. Combining these two facts: [valid GGUF prefix][valid ZIP/NPZ suffix] is simultaneously valid under BOTH formats. Two contrasting formats tested and found NOT vulnerable to this specific technique: ONNX (protobuf's ParseFromString requires the ENTIRE buffer to be valid protobuf) and safetensors (explicit `total_size == header + data` check in its Rust core). Threat scenario demonstrated here (CWE-436, Interpretation Conflict): a model-hosting/scanning pipeline where stage A validates an uploaded file as GGUF and only inspects the GGUF-visible content (approving it as safe/clean), while a DIFFERENT stage B elsewhere in the same pipeline (e.g. a generic archive-indexing utility, a backup/dedup tool, or any code that treats uploaded files as "might also be a zip sidecar bundle") extracts and uses the hidden ZIP payload -- content that stage A's security review NEVER saw. This script builds one 760-byte file with a `.gguf` extension that: - Looks completely clean to a GGUF scanner: 1 tensor named "dummy.weight", value [0,0,0,0], metadata name "tiny-demo-model" -- nothing a scanner would flag. - Actually carries a hidden NPZ payload with different, real values that a GGUF-only scanner never inspects. It then simulates both pipeline stages against the SAME file to show they see different content. Requires: pip install gguf numpy """ import io import struct import numpy as np from gguf.gguf_reader import GGUFReader POLYGLOT_PATH = "model_smuggled.gguf" def build_polyglot(path: str) -> None: GGUF_MAGIC = 0x46554747 def pack_str(s: str) -> bytes: b = s.encode("utf-8") return struct.pack("