
PoC für CVE-2026-73847 - emlog AI Assistant CSRF zu SQL-Ausführung zu Admin-Übernahme (CVSS 6.8)
PoC für fehlenden CSRF-Schutz am execute_tool-Endpunkt des AI Assistant von emlog pro, der es einem Angreifer ermöglicht, die authentifizierte Sitzung eines Admins zu nutzen, um beliebiges SQL gegen die Datenbank der Website auszuführen — einschließlich einer vollständigen Übernahme des Admin-Kontos.
| CVE | CVE-2026-73847 |
| CNA | GitHub |
| Advisory | GHSA-v6wr-4x55-7qp5 |
| CVSS 3.1 | 6.8 Mittel — AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N |
| CWE | CWE-352 (CSRF), CWE-1275 (Fehlerhaftes SameSite), CWE-798 (Hartkodierter Schreibbestätigungs-String) |
| Betroffen | emlog pro bis einschließlich 2.6.23 |
| Danksagung | Dostxodjayev Abdullox (@squeeze440) — Korrektur im CVE-Eintrag ausstehend, siehe unten |
Das Admin-Panel von emlog pro enthält einen AI Assistant, der im Auftrag des Admins SQL über POST /admin/ai.php?action=execute_tool ausführen kann. An diesem einen Endpunkt kommen mehrere Probleme zusammen:
admin/ ruft zuerst LoginAuth::checkToken() auf (z. B. admin/media.php:140). admin/ai.php tut das nie.admin/ai.php:152, User::isAdmin()) — keine Origin-/Referer-Prüfung.include/service/ai.php:594: if (trim($confirm_code) !== 'confirm'). Jede gefälschte Anfrage sendet einfach confirm_code=confirm.include/service/ai.php:578,589) — eine bloße authentifizierte Anfrage kann jede Tabelle per SELECT auslesen.blog ist vor Schreibzugriffen geschützt (include/service/ai.php:591) — user und alle anderen Tabellen sind vollständig beschreibbar.password ab (include/service/ai.php:822-828) — SELECT password AS pwd_hash FROM user liefert den rohen Hash.SameSite-Attribut (include/lib/loginauth.php:99), sodass nur das standardmäßige "Lax+POST"-Schonfrist-Fenster von Chrome (etwa die ersten zwei Minuten nach dem Login) zwischen dieser Schwachstelle und einer zuverlässigen Cross-Site-Zustellung steht.Zusammengenommen: Eine einzige gefälschte Anfrage aus dem Browser eines Admins liest jede Tabelle (einschließlich Passwort-Hashes) und schreibt in jede Tabelle außer blog — inklusive direkter Überschreibung von user.password.
poc_raw_impact.sh)Isoliert die SQL-/Auth-Bypass-Primitive von der Frage der CSRF-Zustellung. Gegen eine lokale Instanz ausführen, die du kontrollierst:
./poc_raw_impact.sh http://TARGET admin '<adminpass>'
Das Skript meldet sich als Admin an, extrahiert Passwort-Hashes über die Spaltenalias-Umgehung, überschreibt das Admin-Passwort direkt über die Tabelle user und meldet sich dann mit einem frischen Cookie-Jar erneut mit dem vom Angreifer gewählten Passwort an — was eine vollständige Kontenübernahme beweist, sobald eine authentifizierte Anfrage den Endpunkt erreicht.

poc_csrf.html)Beantwortet die SameSite-Frage mit einem echten Browser, anstatt sie vorauszusetzen. poc_csrf.html von einer beliebigen Origin ausliefern, die sich vom Ziel unterscheidet (eine andere IP genügt — Chrome behandelt unterschiedliche konkrete IP-Adressen als getrennte Sites), und einen eingeloggten Admin dazu bringen, sie innerhalb von etwa zwei Minuten nach dem Anmelden zu öffnen:
python3 -m http.server 8888
# then point poc_csrf.html's form action at your target and get it opened
Das Formular sendet sich beim Laden automatisch ab und übermittelt cross-site einen gefälschten query_database-Aufruf mit confirm_code=confirm.

Live verifiziert: Der gefälschte Cross-Site-POST trug das echte Auth-Cookie des Admins (sec-fetch-site: cross-site, Cookie angehängt), gab 200 {"code":0,"msg":"ok",...} zurück, und die injizierte Zeile wurde per anschließendem authentifizierten Lesezugriff bestätigt. Die Wiederholung derselben Anfrage etwa 48 Minuten später gegen denselben, nun gealterten Cookie-Jar schlug fehl — kein Cookie wurde angehängt und der Server gab eine nicht authentifizierte Weiterleitung zurück, was bestätigt, dass das etwa zweiminütige Lax+POST-Fenster die tatsächliche Einschränkung ist (reflektiert in AC:H).
emlog_options (SMTP-Zugangsdaten, API-Schlüssel usw.).blog, einschließlich user — Überschreibung von Rolle/Passwort/E-Mail, Ende-zu-Ende als Kontenübernahme demonstriert.role=admin; writer/editor werden durch User::checkRolePermission() blockiert. Keine Privilegienerweiterung von einer niedrigeren Rolle — ein einziger Klick auf einen bösartigen Link durch einen eingeloggten Admin führt zu einer vollständigen, stillen Kompromittierung der Website.LoginAuth::checkToken() zu execute_tool hinzufügen, SameSite=Strict auf dem Auth-Cookie setzen und den statischen confirm_code-String durch ein echtes, pro Sitzung einmalig verwendbares Token ersetzen. Vollständige Details zur Behebung im Advisory.
Anmerkung zur Danksagung: GitHub als CNA veröffentlichte den CVE-Eintrag ohne einen credits-Eintrag, obwohl die GHSA selbst den Melder würdigt und akzeptiert. Ein Korrekturantrag wurde am 2026-08-16 an [email protected] gesendet; dieses README wird aktualisiert, falls der Eintrag korrigiert wird.
Veröffentlicht, nachdem das Advisory öffentlich war und eine CVE zugewiesen wurde, zu defensiven und lehrreichen Zwecken. Führe dies nicht gegen eine emlog-Instanz aus, die dir nicht gehört oder für die du nicht ausdrücklich zum Testen autorisiert bist.