
PoC per CVE-2026-73847 - emlog Assistente IA: da CSRF a esecuzione SQL fino alla compromissione dell'admin (CVSS 6.8)
PoC per la mancata protezione CSRF sull'endpoint execute_tool dell'Assistente AI di emlog pro, che consente a un attaccante di cavalcare la sessione autenticata di un admin per eseguire SQL arbitrario sul database del sito — inclusa la completa compromissione dell'account admin.
| CVE | CVE-2026-73847 |
| CNA | GitHub |
| Advisory | GHSA-v6wr-4x55-7qp5 |
| CVSS 3.1 | 6.8 Medio — AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N |
| CWE | CWE-352 (CSRF), CWE-1275 (SameSite improprio), CWE-798 (stringa hardcoded di conferma scrittura) |
| Versioni interessate | emlog pro fino alla 2.6.23 |
| Crediti | Dostxodjayev Abdullox (@squeeze440) — correzione in sospeso nel record CVE, vedi sotto |
Il pannello di amministrazione di emlog pro include un assistente AI che può eseguire SQL per conto dell'amministratore tramite POST /admin/ai.php?action=execute_tool. Diversi problemi si accumulano su questo unico endpoint:
admin/ chiama prima LoginAuth::checkToken() (ad es. admin/media.php:140). admin/ai.php non lo fa mai.admin/ai.php:152, User::isAdmin()) — nessun controllo Origin/Referer.include/service/ai.php:594: if (trim($confirm_code) !== 'confirm'). Qualsiasi richiesta falsificata invia semplicemente confirm_code=confirm.include/service/ai.php:578,589) — una semplice richiesta autenticata può fare SELECT su qualsiasi tabella.blog è protetta in scrittura () — e tutte le altre tabelle sono completamente scrivibili.Combinati insieme: una singola richiesta falsificata dal browser di un amministratore legge ogni tabella (incluse le hash delle password) e scrive su ogni tabella tranne blog, compresa la sovrascrittura diretta di user.password.
poc_raw_impact.sh)Isola la primitiva di bypass SQL/autenticazione dalla questione della consegna CSRF. Eseguilo contro un'istanza locale che controlli:
./poc_raw_impact.sh http://TARGET admin '<adminpass>'
Questo script accede come admin, scarica le hash delle password tramite l'elusione con alias di colonna, sovrascrive direttamente la password dell'admin tramite la tabella user, poi accede di nuovo con la password scelta dall'attaccante da un nuovo cookie jar — dimostrando la completa compromissione dell'account non appena una richiesta autenticata raggiunge l'endpoint.

poc_csrf.html)Risolve la questione SameSite con un browser reale invece di darla per scontata. Servi poc_csrf.html da un'origine qualsiasi distinta dal target (un IP diverso è sufficiente — Chrome tratta IP letterali distinti come siti separati) e fai aprire la pagina a un admin autenticato entro circa due minuti dall'accesso:
python3 -m http.server 8888
# then point poc_csrf.html's form action at your target and get it opened
Il modulo si invia automaticamente al caricamento, inviando una chiamata falsificata query_database in POST cross-site con confirm_code=confirm.

Verificato dal vivo: la POST cross-site falsificata trasportava il cookie di autenticazione reale dell'admin (sec-fetch-site: cross-site, cookie incluso), restituiva 200 {"code":0,"msg":"ok",...}, e la riga iniettata è stata confermata presente tramite una successiva lettura autenticata. Ripetendo la richiesta identica ~48 minuti dopo contro lo stesso cookie jar, ormai invecchiato, la richiesta è fallita — nessun cookie è stato incluso e il server ha restituito un redirect non autenticato, confermando che la finestra Lax+POST di circa due minuti è il vincolo reale (riflesso in AC:H).
emlog_options (credenziali SMTP, chiavi API, ecc.).blog, inclusa user — sovrascrittura di ruolo/password/email, dimostrata end-to-end come compromissione dell'account.role=admin; writer/editor sono bloccati da User::checkRolePermission(). Non è un'escalation di privilegi da un ruolo inferiore — trasforma un singolo clic su un link dannoso da parte di un admin autenticato in una compromissione completa e silenziosa del sito.Aggiungi LoginAuth::checkToken() a execute_tool, imposta SameSite=Strict sul cookie di autenticazione e sostituisci la stringa statica confirm_code con un token reale per sessione, monouso. Dettagli completi sulla remediation nell'advisory.
Nota sui crediti: GitHub in qualità di CNA ha pubblicato il record CVE senza una voce credits, nonostante la GHSA stessa accrediti e accetti il segnalatore. Una richiesta di correzione è stata inviata a [email protected] il 2026-08-16; questo README verrà aggiornato se il record verrà corretto.
Pubblicato dopo che l'advisory era pubblico e un CVE era stato assegnato, per uso difensivo/educativo. Non eseguirlo contro un'istanza emlog che non possiedi o per la quale non sei esplicitamente autorizzato a fare test.
include/service/ai.php:591userpassword (include/service/ai.php:822-828) — SELECT password AS pwd_hash FROM user restituisce l'hash grezzo.SameSite (include/lib/loginauth.php:99), lasciando come unica barriera tra questo e una consegna cross-site affidabile la finestra di grazia predefinita di Chrome "Lax+POST" (all'incirca i primi due minuti dopo il login).