Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-72731

CVE-2026-72731: Discourse Data Explorer SQL Injection

CVE-2026-72731 is a SQL injection vulnerability in Discourse Data Explorer plugin that allows unauthorized database access through parameter manipulation. This article covers technical details, affected versions, and mitigation.

Published:

CVE-2026-72731 Overview

CVE-2026-72731 is a SQL injection vulnerability in the Discourse open-source discussion platform, specifically within the discourse-data-explorer plugin. The flaw allows any user permitted to run a parameterized Data Explorer query, including non-staff members of groups that queries are shared with, to inject arbitrary SQL. Attackers craft parameter values that escape the intended query, using recursive parameter interpolation or parameter declarations embedded in SQL comments. Queries execute inside a read-only transaction, which prevents data modification, but any table in the database remains readable. The issue is tracked under [CWE-89: SQL Injection] and fixed in versions 2026.1.7, 2026.6.2, 2026.7.1, and 2026.8.0-latest.1.

Critical Impact

Authenticated low-privilege users can read arbitrary database tables, exposing sensitive user data, private messages, API keys, and password hashes stored in the Discourse database.

Affected Products

  • Discourse versions 2026.1.0-latest through 2026.1.6
  • Discourse 2026.6.x prior to 2026.6.2 and 2026.7.x prior to 2026.7.1
  • Discourse 2026.8.0-latest prior to 2026.8.0-latest.1 with the discourse-data-explorer plugin enabled

Discovery Timeline

  • 2026-08-10 - CVE-2026-72731 published to NVD
  • 2026-08-10 - Last updated in NVD database

Technical Details for CVE-2026-72731

Vulnerability Analysis

The vulnerability resides in the Data Explorer plugin's SQL parameter handling logic in plugins/discourse-data-explorer/lib/discourse_data_explorer/data_explorer.rb and plugins/discourse-data-explorer/lib/discourse_data_explorer/workflows/sql_action/v1.rb. The plugin passed user-supplied parameter values through MiniSql::InlineParamEncoder, but the encoder was invoked recursively against previously interpolated content. An attacker supplying a value for parameter :name could embed another parameter reference such as :secret, causing the interpolator to substitute that value in a second pass, breaking the safe-parameter boundary.

A second injection surface exists in SQL comments. Because parameter declarations could appear inside -- or /* */ comments and those comments were preserved before interpolation, an attacker could smuggle in parameter definitions that later resolved to executable SQL fragments. The read-only transaction wrapper limits attackers to SELECT operations, but this still exposes every table Discourse can read.

Root Cause

The root cause is unsafe query construction: user-controlled parameter values were interpolated into the SQL string without first stripping comments or preventing recursive substitution. Both preconditions were required for exploitation, and both were reachable through the standard Data Explorer parameter form.

Attack Vector

An authenticated user who can execute at least one shared parameterized Data Explorer query submits a crafted parameter value. The malicious payload either references another parameter to trigger recursive interpolation, or leverages a parameter declaration hidden inside a SQL comment. The resulting SQL runs against the Discourse PostgreSQL database in a read-only transaction.

ruby
# Security patch: strip SQL comments and switch to non-recursive interpolation
# File: plugins/discourse-data-explorer/lib/discourse_data_explorer/data_explorer.rb

# frozen_string_literal: true

require "strscan"

module DiscourseDataExplorer
  module DataExplorer
    # the lookbehind skips `::type` casts
    PARAM_REGEX = /(?<!:):([a-zA-Z_][a-zA-Z0-9_]*)/

    # Used for ftype calls, see PG type OID definitions
    PG_TYPE_OID_JSON = 114
ruby
# Security patch in workflows/sql_action/v1.rb - remove recursive encoder call

  req_params[param["name"].to_sym] = param["value"] if param["name"].present?
end

- if req_params.present?
-   sql =
-     MiniSql::InlineParamEncoder.new(ActiveRecord::Base.connection.raw_connection).encode(
-       sql,
-       req_params,
-     )
- end
+ sql = DiscourseDataExplorer::DataExplorer.strip_comments(sql)
+ sql = DiscourseDataExplorer::DataExplorer.interpolate_params(sql, req_params)

query = DiscourseDataExplorer::Query.new(name: "workflow", sql: sql)

Source: Discourse commit 2bdab88

Detection Methods for CVE-2026-72731

Indicators of Compromise

  • Data Explorer query executions containing parameter values with embedded :parameter_name references, particularly for parameters not declared in the original query template.
  • Query submissions where parameter values contain SQL comment markers (--, /*, */) or unexpected UNION SELECT fragments.
  • PostgreSQL logs showing SELECT statements against tables such as users, email_tokens, api_keys, or user_api_keys originating from Data Explorer query IDs shared with non-staff groups.

Detection Strategies

  • Audit the data_explorer_query_groups and data_explorer_queries tables to identify every query shared with non-staff groups, then review the run history of those queries.
  • Correlate Discourse application logs with PostgreSQL query logs to flag Data Explorer executions whose rendered SQL differs materially from the stored query template.
  • Alert on any Data Explorer request where the submitted parameter value matches the regex :[a-zA-Z_][a-zA-Z0-9_]* before interpolation.

Monitoring Recommendations

  • Enable PostgreSQL statement logging for the Discourse database role and forward logs to a centralized analytics platform for retention and searching.
  • Track the volume and result size of Data Explorer queries per user; investigate outliers such as sudden large result sets or access to previously unused tables.
  • Review group membership of Data Explorer-shared queries on a recurring cadence and remove any groups that no longer require access.

How to Mitigate CVE-2026-72731

Immediate Actions Required

  • Upgrade Discourse to 2026.1.7, 2026.6.2, 2026.7.1, or 2026.8.0-latest.1 depending on your release branch.
  • Rotate any credentials, API keys, or tokens that may have been exposed through readable database tables during the vulnerable window.
  • Review Data Explorer query sharing settings and revoke access for any non-staff group that does not have a documented business need.

Patch Information

The fix strips SQL comments before interpolation and replaces the recursive MiniSql::InlineParamEncoder call with a single-pass interpolate_params helper. Review the upstream commits 2bdab88, 3dc7f0d, 674ba6f, and ce9ccf2, along with the GHSA-wm63-83xp-59r5 advisory.

Workarounds

  • Disable the discourse-data-explorer plugin entirely until the upgrade is complete.
  • If disabling is not possible, remove all group shares from Data Explorer queries so that only administrators can execute them.
  • Restrict PostgreSQL role privileges used by Discourse to the minimum tables required, limiting the blast radius of any read-only injection.
bash
# Configuration example: disable the Data Explorer plugin as a temporary workaround
# Edit app.yml in your Discourse install directory

hooks:
  after_code:
    - exec:
        cd: $home/plugins
        cmd:
          # Comment out or remove the following line:
          # - git clone https://github.com/discourse/discourse-data-explorer.git

# Then rebuild the container:
# ./launcher rebuild app

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.