← ALL ADVISORIES

CRITICAL Won't Fix

SQL Injection via OAuth2 Token Refresh Response

Zabbix server takes access_token and refresh_token values directly from an external OAuth server's HTTP response and interpolates them into a SQL UPDATE with no escaping. Stacked queries are enabled, giving an attacker who controls the token endpoint arbitrary SQL execution against the Zabbix database.

Vendor
Zabbix
Product
Zabbix Server
Affected
8.0.0beta2 (commit 31eccf9)

Summary

When an Email media type is configured with OAuth2 authentication, Zabbix server automatically POSTs to the configured token endpoint to refresh expired tokens. The access_token and refresh_token values parsed from the JSON response are passed directly to zbx_db_execute() with no escaping, allowing an attacker who controls the OAuth endpoint to inject arbitrary SQL.

Affected Versions

  • Zabbix Server 8.0.0beta2 (commit 31eccf9ddaf7adf129f9cd611c85b7451b188eb9)

Details

The injection is in src/libs/zbxalerter/oauth.c, oauth_db_update(), lines 301–317. Both the if and else branches build the SQL string by interpolating data->access_token and data->refresh_token directly:

zbx_db_execute("update media_type_oauth set"
        " access_token='%s',access_token_updated=" ZBX_FS_TIME_T ","
        "access_expires_in=%d,refresh_token='%s',tokens_status=%hhu"
        " where mediatypeid="ZBX_FS_UI64,
        data->access_token, data->access_token_updated, data->access_expires_in,
        data->refresh_token, data->tokens_status,
        mediatypeid);

These values are parsed directly from the OAuth server's JSON response (oauth.c:248–264) with no call to zbx_db_dyn_escape_string(), which is used everywhere else in the database layer for exactly this purpose.

The MySQL connection is opened with CLIENT_MULTI_STATEMENTS, so stacked queries are fully supported.

The call chain is automatic and requires no user interaction:

  1. An alert is queued for an Email media type using OAuth2
  2. The alerter detects the access token is expired (oauth.c:400)
  3. Zabbix POSTs to the configured token_url (oauth.c:408)
  4. The JSON response is parsed and tokens are stored as-is (oauth.c:242–264)
  5. oauth_db_update() interpolates the unescaped values into SQL (oauth.c:301–307)

Proof of concept: a hostile OAuth server returning a crafted access_token with a stacked INSERT statement created a backdoor admin account in the users table without any Zabbix credentials.

Impact

Arbitrary SQL execution as the Zabbix database user, triggerable by any party that can influence the response from the configured OAuth token endpoint (e.g., provider compromise, DNS hijack, network interception). Full database read/write on a monitoring server that already holds credentials and network reach into the monitored fleet.

Remediation

No fix — Zabbix declined to treat this as a security issue on the basis that configuring the OAuth media type requires administrator privileges. That reasoning gates the endpoint selection, not the content of the server's response.

The one-line fix is to pass access_token and refresh_token through zbx_db_dyn_escape_string() before interpolation, in both branches of oauth_db_update().

Timeline

  • 2026-06-06 — Reported to Zabbix with proof of concept
  • 2026-06-08 — Zabbix responds: not a security vulnerability
  • 2026-06-09 — Public disclosure