DEV Community

CVE Reports
CVE Reports

Posted on Originally published at cvereports.com

CVE-2026-62361: CVE-2026-62361: Authenticated SQL Injection and Table Access Control Bypass in Listmonk Subscriber Export

CVE-2026-62361: Authenticated SQL Injection and Table Access Control Bypass in Listmonk Subscriber Export

Vulnerability ID: CVE-2026-62361
CVSS Score: 5.5
Published: 2026-11-06

CVE-2026-62361 (GHSA-xgjr-7j9q-2h4r) is an authenticated SQL injection and authorization bypass vulnerability in listmonk, an open-source, self-hosted newsletter and mailing list manager written in Go. In versions prior to 6.2.0, the subscriber export endpoint (GET /api/subscribers/export) directly interpolates user-supplied SQL filter expressions into a PostgreSQL query without invoking the table allowlist validation function (validateQueryTables). Consequently, authenticated users possessing the subscribers:sql_query and subscribers:get_all permissions can bypass relational access controls, execute arbitrary subqueries against restricted tables such as users and settings, and modify database state via writable Common Table Expressions (CTEs).

TL;DR

An omission of query plan table validation in listmonk's ExportSubscribers handler allows authenticated users with SQL query permissions to query restricted database tables (such as users and settings) via error-based or blind SQL injection, and modify records due to the lack of a read-only transaction wrapper.


⚠️ Exploit Status: POC

Technical Details

  • CVE ID: CVE-2026-62361 (GHSA-xgjr-7j9q-2h4r)
  • Weakness Class: CWE-89 (SQL Injection) / Table Allowlist Bypass
  • CVSS v3.1 Score: 5.5 Medium (CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:L/A:N)
  • Affected Component: ExportSubscribers in internal/core/subscribers.go (GET /api/subscribers/export)
  • Prerequisites: Authenticated user with subscribers:sql_query and subscribers:get_all permissions
  • Primary Impact: Exfiltration of admin password hashes (users table) and SMTP/API credentials (settings table)
  • Exploit Status: Public Proof-of-Concept (PoC) available; EPSS 0.30%
  • Patched Version: listmonk v6.2.0

Affected Systems

  • knadh/listmonk
  • listmonk: < 6.2.0 (Fixed in: 6.2.0)

Code Analysis

Commit: c0a6525

Fix missing SQL query validation on subscriber export for superadmisn with 'sql_query' permission.

File: internal/core/subscribers.go
@@ -18,15 +18,15 @@ import (

 var (
    allowedSubQueryTables = map[string]struct{}{
-       "subscribers":       {},
-       "lists":             {},
-       "subscribers_lists": {},
-       "campaigns":         {},
-       "campaign_lists":    {},
-       "campaign_views":    {},
-       "links":             {},
-       "link_clicks":       {},
-       "bounces":           {},
+       "subscribers":      {},
+       "lists":            {},
+       "subscriber_lists": {},
+       "campaigns":        {},
+       "campaign_lists":   {},
+       "campaign_views":   {},
+       "links":            {},
+       "link_clicks":      {},
+       "bounces":          {},
    }
 )

@@ -128,7 +128,7 @@ func (c *Core) QuerySubscribers(searchStr, queryExp string, listIDs []int, subSt
    stmt = strings.ReplaceAll(stmt, "%order%", orderBy+" "+order)

    // Validate the tables used in the query.
-   if err := validateQueryTables(c.db, stmt, allowedSubQueryTables); err != nil {
+   if err := validateQueryTables(c.db, stmt, allowedSubQueryTables, pq.Array(listIDs), subStatus, searchStr, offset, limit); err != nil {
        c.log.Printf("error validating query tables: %v", err)
        return nil, 0, echo.NewHTTPError(http.StatusBadRequest,
            c.i18n.Ts("subscribers.errorPreparingQuery", "error", err.Error()))
@@ -248,6 +248,14 @@ func (c *Core) ExportSubscribers(searchStr, query string, subIDs, listIDs []int,

    stmt := strings.ReplaceAll(c.q.QuerySubscribersForExport, "%query%", cond)

+   // Validate the tables used in the query.
+   if err := validateQueryTables(c.db, stmt, allowedSubQueryTables,
+       pq.Array(listIDs), 0, pq.Array(subIDs), subStatus, searchStr, batchSize); err != nil {
+       c.log.Printf("error validating query tables: %v", err)
+       return nil, echo.NewHTTPError(http.StatusBadRequest,
+           c.i18n.Ts("subscribers.errorPreparingQuery", "error", err.Error()))
+   }
+
    // Create a readonly transaction that just does COUNT() to obtain the count of results
    // and to ensure that the arbitrary query is indeed readonly.
    if _, err := c.getSubscriberCount(searchStr, cond, subStatus, listIDs); err != nil {
@@ -593,7 +601,7 @@ func (c *Core) getSubscriberCount(searchStr, queryExp, subStatus string, listIDs
 }

 // validateQueryTables checks if the query accesses only allowed tables.
-func validateQueryTables(db *sqlx.DB, query string, allowedTables map[string]struct{}) error {
+func validateQueryTables(db *sqlx.DB, query string, allowedTables map[string]struct{}, args ...any) error {
    // Get the EXPLAIN (FORMAT JSON) output.
    tx, err := db.BeginTxx(context.Background(), &sql.TxOptions{ReadOnly: true})
    if err != nil {
@@ -602,7 +610,7 @@ func validateQueryTables(db *sqlx.DB, query string, allowedTables map[string]str
    defer tx.Rollback()

    var plan string
-   if err = tx.QueryRow("EXPLAIN (FORMAT JSON) "+query, nil, models.SubscriberStatusEnabled, "", 0, 10).Scan(&plan); err != nil {
+   if err = tx.QueryRow("EXPLAIN (FORMAT JSON) "+query, args...).Scan(&plan); err != nil {
        return err
    }
Enter fullscreen mode Exit fullscreen mode

Exploit Details

  • GitHub Security Advisory: Official advisory containing proof-of-concept HTTP requests demonstrating error-based exfiltration of bcrypt password hashes and SMTP credentials, as well as time-based blind execution.

Mitigation Strategies

  • Upgrade listmonk to version 6.2.0 or later to enforce table allowlist validation on subscriber export queries.
  • Restrict the 'subscribers:sql_query' permission strictly to fully trusted Super Admin accounts.
  • Implement reverse proxy or WAF filtering on the '/api/subscribers/export' endpoint to block subqueries and type-casting functions in the 'query' parameter.
  • Ensure the PostgreSQL database user configured for listmonk does not have PostgreSQL superuser privileges.

Remediation Steps:

  1. Identify all running instances of listmonk and verify their version numbers (versions < 6.2.0 are vulnerable).
  2. Pull the latest listmonk v6.2.0+ binary or Docker container image from https://github.com/knadh/listmonk/releases/tag/v6.2.0.
  3. Restart the listmonk service using the updated binary or container.
  4. Review user roles in the listmonk administrative dashboard and remove 'subscribers:sql_query' from non-administrative roles.
  5. If exploitation is suspected, rotate all user passwords, API tokens, and external SMTP/S3 credentials stored in listmonk settings.

References


Read the full report for CVE-2026-62361 on our website for more details including interactive diagrams and full exploit analysis.

Top comments (0)