Sploitus

Exploit for CVE-2026-73847

githubexploit Β· 2026-08-16

Exploit Code

README83 lines
## https://sploitus.com/exploit?id=F0666787-BCF6-595C-918A-632B2AEBA0A4
# CVE-2026-73847 β€” emlog AI Assistant CSRF β†’ SQL Execution β†’ Admin Takeover

PoC for missing CSRF protection on emlog pro's AI Assistant `execute_tool` endpoint, which lets an attacker ride an admin's authenticated session to run arbitrary SQL against the site database β€” including a full admin account takeover.

| | |
|---|---|
| **CVE** | [CVE-2026-73847](https://vulners.com/cve/CVE-2026-73847) |
| **CNA** | GitHub |
| **Advisory** | [GHSA-v6wr-4x55-7qp5](https://github.com/emlog/emlog/security/advisories/GHSA-v6wr-4x55-7qp5) |
| **CVSS 3.1** | 6.8 Medium β€” `AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:N` |
| **CWE** | CWE-352 (CSRF), CWE-1275 (Improper SameSite), CWE-798 (Hardcoded write-confirmation string) |
| **Affected** | emlog pro through 2.6.23 |
| **Credit** | Dostxodjayev Abdullox ([@squeeze440](https://github.com/squeeze440)) β€” pending correction in the CVE record, see below |

## Root cause

emlog pro's admin panel ships an AI assistant that can execute SQL on the admin's behalf via `POST /admin/ai.php?action=execute_tool`. Several issues stack on this one endpoint:

1. **No CSRF token.** Every other destructive action file in `admin/` calls `LoginAuth::checkToken()` first (e.g. `admin/media.php:140`). `admin/ai.php` never does.
2. **Auth is session-cookie-only** (`admin/ai.php:152`, `User::isAdmin()`) β€” no Origin/Referer check.
3. **The write-confirmation gate is a hardcoded public string.** `include/service/ai.php:594`: `if (trim($confirm_code) !== 'confirm')`. Any forged request just sends `confirm_code=confirm`.
4. **Read-only SQL needs no confirmation at all** (`include/service/ai.php:578,589`) β€” a bare authenticated request can `SELECT` any table.
5. **Only the `blog` table is write-protected** (`include/service/ai.php:591`) β€” `user` and every other table are fully writable.
6. **Password redaction is alias-bypassable.** Redaction only matches the literal output column name `password` (`include/service/ai.php:822-828`) β€” `SELECT password AS pwd_hash FROM user` returns the raw hash.
7. **Auth cookie has no `SameSite` attribute** (`include/lib/loginauth.php:99`), leaving Chrome's default "Lax+POST" grace window (roughly the first two minutes after login) as the only thing standing between this and reliable cross-site delivery.

Chained together: one forged request from an admin's browser reads every table (including password hashes) and writes to every table except `blog`, including a direct `user.password` overwrite.

## PoC

### Part 1 β€” raw impact chain (`poc_raw_impact.sh`)

Isolates the SQL/auth-bypass primitive from the CSRF delivery question. Run against a local instance you control:

```bash
./poc_raw_impact.sh http://TARGET admin ''
```

This logs in as admin, dumps password hashes via column-alias bypass, overwrites the admin password directly through the `user` table, then logs in again with the attacker-chosen password from a fresh cookie jar β€” proving full account takeover once one authenticated request reaches the endpoint.

![Attacker logs in as admin with the SQL-overwritten password, landing on the authenticated dashboard in a fresh, isolated session](evidence/06-account-takeover-login.png)

### Part 2 β€” real cross-site CSRF delivery (`poc_csrf.html`)

Settles the SameSite question with a live browser rather than assuming it. Serve `poc_csrf.html` from any origin distinct from the target (a different IP is enough β€” Chrome treats distinct literal IPs as separate sites) and get a logged-in admin to open it within roughly two minutes of signing in:

```bash
python3 -m http.server 8888
# then point poc_csrf.html's form action at your target and get it opened
```

The form auto-submits on load, POSTing a forged `query_database` call with `confirm_code=confirm` cross-site.

![emlog admin login page](evidence/01-admin-login.png)
![Authenticated admin dashboard after login](evidence/02-admin-dashboard.png)
![Attacker page source, served from a separate origin](evidence/03-attacker-page-source.png)
![Cross-site POST lands on the raw JSON success response](evidence/04-cross-site-post-response.png)
![Injected CSRF marker row visible in the victim's own admin Links panel](evidence/05-injected-marker-in-admin-ui.png)

Verified live: the forged cross-site POST carried the admin's real auth cookie (`sec-fetch-site: cross-site`, cookie attached), returned `200 {"code":0,"msg":"ok",...}`, and the injected row was confirmed present via a follow-up authenticated read. Repeating the identical request ~48 minutes later against the same, now-aged cookie jar failed β€” no cookie was attached and the server returned an unauthenticated redirect, confirming the roughly two-minute Lax+POST window is the real constraint (reflected in `AC:H`).

## Impact

- Full database read: every table/column, including password hashes and any secrets in `emlog_options` (SMTP creds, API keys, etc.).
- Full database write to every table except `blog`, including `user` β€” role/password/email overwrite, demonstrated end-to-end as account takeover.
- Limited to accounts with `role=admin`; `writer`/`editor` are blocked by `User::checkRolePermission()`. Not privilege escalation from a lower role β€” it turns one malicious link click by a logged-in admin into full, silent site compromise.

## Fix

Add `LoginAuth::checkToken()` to `execute_tool`, set `SameSite=Strict` on the auth cookie, and replace the static `confirm_code` string with a real per-session, single-use token. Full remediation detail in the [advisory](https://github.com/emlog/emlog/security/advisories/GHSA-v6wr-4x55-7qp5).

## Disclosure timeline

- 2026-07-31 β€” Reported via GitHub Security Advisories per emlog's own SECURITY.md.
- 2026-08-01 β€” Maintainer published the advisory and requested a CVE.
- 2026-08-16 β€” CVE-2026-73847 assigned by GitHub (CNA).

**Note on credit:** GitHub-as-CNA published the CVE record without a `credits` entry, despite the GHSA itself crediting and accepting the reporter. A correction request was sent to `security-advisories@github.com` on 2026-08-16; this README will be updated if the record is corrected.

## Disclaimer

Published after the advisory was public and a CVE assigned, for defensive/educational use. Do not run this against an emlog instance you don't own or aren't explicitly authorized to test.