<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Argus Blog</title><link href="https://blog.argus-systems.ai/" rel="alternate"/><link href="https://blog.argus-systems.ai/feeds/all.atom.xml" rel="self"/><id>https://blog.argus-systems.ai/</id><updated>2026-06-08T11:00:00-07:00</updated><subtitle>Security and engineering</subtitle><entry><title>Trusted Until It Isn't: The Zabbix SQL Injection That "Needs an Admin"</title><link href="https://blog.argus-systems.ai/blog/zabbix-oauth-sql-injection.html" rel="alternate"/><published>2026-06-08T11:00:00-07:00</published><updated>2026-06-08T11:00:00-07:00</updated><author><name>Argus</name></author><id>tag:blog.argus-systems.ai,2026-06-08:/blog/zabbix-oauth-sql-injection.html</id><summary type="html">&lt;p&gt;Zabbix takes data from an external OAuth server's response and drops it straight into a SQL &lt;code&gt;UPDATE&lt;/code&gt; statement with zero escaping. When we reported this, Zabbix dismissed it as a non-issue because "an admin has to configure the OAuth provider." That defense points at the wrong end of the data flow.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Zabbix is one of the most widely deployed open source monitoring platforms, used by everyone from small shops to large enterprises to keep an eye on servers, networks, and applications. A central Zabbix server collects metrics from across the fleet, evaluates alerting rules, and sends notifications when something goes wrong. Because it sits in the middle of all that infrastructure and holds the credentials to reach it, the Zabbix server is a high value target: whoever controls it has a foothold into much of the environment it watches.&lt;/p&gt;
&lt;p&gt;We recently found a SQL injection in the Zabbix server's OAuth2 token refresh path. The injected values come directly from an external HTTP response, not from user input. Yet, when we reported the vulnerability, Zabbix declined to treat it as a security issue. Their rationale? An administrator has to configure the OAuth media type in the first place.&lt;/p&gt;
&lt;p&gt;But the privilege to select an OAuth endpoint is not the same as the privilege to supply the bytes that get executed in your database. This post breaks down the bug, the data flow, and why "you need an admin" is an answer to a question nobody asked.&lt;/p&gt;
&lt;h2&gt;The bug&lt;/h2&gt;
&lt;p&gt;When an Email media type is configured with OAuth2 authentication and the access token expires, Zabbix server POSTs to the configured token endpoint to refresh it. It then takes the &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;refresh_token&lt;/code&gt; strings out of the JSON response and writes them into the database.&lt;/p&gt;
&lt;p&gt;Here is the write, in &lt;code&gt;src/libs/zbxalerter/oauth.c&lt;/code&gt;, lines 301 to 307:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;zbx_db_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;update media_type_oauth set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; access_token=&amp;#39;%s&amp;#39;,access_token_updated=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_TIME_T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;access_expires_in=%d,refresh_token=&amp;#39;%s&amp;#39;,tokens_status=%hhu&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; where mediatypeid=&amp;quot;&lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_UI64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;mediatypeid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;data-&amp;gt;access_token&lt;/code&gt; and &lt;code&gt;data-&amp;gt;refresh_token&lt;/code&gt; are parsed straight out of the OAuth server's JSON response (&lt;code&gt;oauth.c:248-264&lt;/code&gt;) and handed to &lt;code&gt;zbx_db_execute()&lt;/code&gt; with no escaping at all. The &lt;code&gt;else&lt;/code&gt; branch at lines 311 to 317 has the same problem for &lt;code&gt;access_token&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The MySQL connection is opened with &lt;code&gt;CLIENT_MULTI_STATEMENTS&lt;/code&gt;, so this is not limited to breaking the one UPDATE. An attacker who controls the response can append stacked queries: &lt;code&gt;INSERT&lt;/code&gt;, &lt;code&gt;UPDATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, anything the database user can do.&lt;/p&gt;
&lt;p&gt;The call chain is short and fully automatic:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;alert_manager.c:491&lt;/code&gt;, the alerter processes an email alert that uses an OAuth2 media type&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth.c:400&lt;/code&gt;, the token is detected as expired&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth.c:408&lt;/code&gt;, &lt;code&gt;oauth_access_refresh()&lt;/code&gt; POSTs to &lt;code&gt;token_url&lt;/code&gt; from the database&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth.c:242-264&lt;/code&gt;, the JSON response is parsed and &lt;code&gt;access_token&lt;/code&gt; / &lt;code&gt;refresh_token&lt;/code&gt; are taken as-is&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth.c:413&lt;/code&gt;, &lt;code&gt;oauth_db_update()&lt;/code&gt; is called with the unescaped values&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oauth.c:301-307&lt;/code&gt;, the values are interpolated into SQL and executed&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nobody clicks anything. A trigger fires, an alert gets queued, the token happens to be expired (OAuth access tokens usually live minutes to hours), and the refresh runs on its own.&lt;/p&gt;
&lt;p&gt;We belive this is a definitly bug, not a design choice, but you don't have to take our threat model on faith; Zabbix's own code disagrees with their triage. Look anywhere else in this codebase where data hits SQL, and you will find it passing through &lt;code&gt;zbx_db_dyn_escape_string()&lt;/code&gt;. It is heavily utilized throughout &lt;code&gt;dbconn.c&lt;/code&gt; for this exact reason.&lt;/p&gt;
&lt;h2&gt;Why "an admin sets the OAuth URL" is the wrong objection&lt;/h2&gt;
&lt;p&gt;Zabbix's position is that configuring an Email media type (including setting the token endpoint) requires Admin rights. That is true, but it only gates the &lt;em&gt;destination&lt;/em&gt;. It controls the URL Zabbix will talk to, not the data that comes back.&lt;/p&gt;
&lt;p&gt;These are two entirely different trust boundaries, and the injection lives firmly on the second one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Boundary 1 (Configuration):&lt;/strong&gt; The admin controls where Zabbix sends the refresh request. This is trusted and restricted to administrators.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Boundary 2 (Execution):&lt;/strong&gt; The injected SQL comes from the response body, the tokens returned by whatever is listening at that URL. That data crosses the network on every single refresh, and it is untrusted by definition.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attacker exploiting this doesn't need to be the Zabbix admin.&lt;/p&gt;
&lt;h2&gt;Why it is a critical issue&lt;/h2&gt;
&lt;p&gt;A successful injection grants arbitrary SQL execution as the Zabbix database user. With stacked queries enabled, an attacker can read every credential and secret in the database, alter monitoring data, manipulate alert states, trivially create a backdoor administrator by inserting a record directly into the &lt;code&gt;users&lt;/code&gt; table, or possibly execute arbitrary code.&lt;/p&gt;
&lt;p&gt;Our proof of concept spun up Zabbix 8.0.0beta2, MySQL, and a hostile OAuth server. We seeded an Email media type pointing at our server and let the alerter do its job. The hostile server returned an &lt;code&gt;access_token&lt;/code&gt; carrying a stacked &lt;code&gt;INSERT&lt;/code&gt; statement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;=== Users BEFORE injection ===
userid  username        passwd
1       Admin   $2y$10$92nDno4n0Zm7Ej7Jfsz8WukBfgSS/U0QkIuu8WkJPihXBb2A1UrEK
2       guest   $2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06

[+] New admin appeared!

=== Users AFTER injection ===
userid  username        passwd
1       Admin   $2y$10$92nDno4n0Zm7Ej7Jfsz8WukBfgSS/U0QkIuu8WkJPihXBb2A1UrEK
2       guest   $2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06
9999    backdoor        $2y$10$92nDno4n0Zm7Ej7Jfsz8WukBfgSS/U0QkIuu8WkJPihXBb2A1UrEK
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Full database write on a monitoring server is about as high as impact gets, because a monitoring server already has reach into the rest of your fleet. Pair that with a reachability path that needs no Zabbix credentials at all (a provider compromise), and "not a security issue" is not a defensible call.&lt;/p&gt;
&lt;h2&gt;The fix&lt;/h2&gt;
&lt;p&gt;Escape &lt;code&gt;access_token&lt;/code&gt; and &lt;code&gt;refresh_token&lt;/code&gt; with &lt;code&gt;zbx_db_dyn_escape_string()&lt;/code&gt; before they are passed to &lt;code&gt;zbx_db_execute()&lt;/code&gt; in &lt;code&gt;oauth_db_update()&lt;/code&gt;, in both the &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; branches at lines 301 to 307 and 311 to 317. This is the same function the rest of the database layer already uses:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;diff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zbxalerter&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zbxalerter&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dadca8f&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;b008c49&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100644&lt;/span&gt;
&lt;span class="o"&gt;---&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zbxalerter&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="o"&gt;+++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;zbxalerter&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;oauth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;
&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-294&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;294&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;oauth_db_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zbx_uint64_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mediatypeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zbx_oauth_data_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;access_token_esc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens_status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ZBX_OAUTH_TOKEN_ACCESS_VALID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ZBX_OAUTH_TOKEN_REFRESH_VALID&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* access_token and refresh_token originate from the external OAuth server response, */&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="cm"&gt;/* so they must be escaped before being interpolated into the SQL statement */&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="n"&gt;access_token_esc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zbx_db_dyn_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;old_refresh_token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="cm"&gt;/* data-&amp;gt;refresh_token has changed */&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;refresh_token_esc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="n"&gt;refresh_token_esc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zbx_db_dyn_escape_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="n"&gt;zbx_db_execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;update media_type_oauth set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; access_token=&amp;#39;%s&amp;#39;,access_token_updated=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_TIME_T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;access_expires_in=%d,refresh_token=&amp;#39;%s&amp;#39;,tokens_status=%hhu&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; where mediatypeid=&amp;quot;&lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_UI64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;access_token_esc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;refresh_token_esc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="n"&gt;mediatypeid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                       &lt;/span&gt;&lt;span class="n"&gt;zbx_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refresh_token_esc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;-312&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;324&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;@@&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="n"&gt;oauth_db_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;zbx_uint64_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mediatypeid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zbx_oauth_data_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; access_token=&amp;#39;%s&amp;#39;,access_token_updated=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_TIME_T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;,&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;access_expires_in=%d,tokens_status=%hhu&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="s"&gt;&amp;quot; where mediatypeid=&amp;quot;&lt;/span&gt;&lt;span class="n"&gt;ZBX_FS_UI64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;                                       &lt;/span&gt;&lt;span class="n"&gt;access_token_esc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_token_updated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;access_expires_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tokens_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                                        &lt;/span&gt;&lt;span class="n"&gt;mediatypeid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;
&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="n"&gt;zbx_free&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token_esc&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Closing remarks&lt;/h2&gt;
&lt;p&gt;We reported this vulnerability in good faith, providing a fully functional proof of concept. Zabbix concluded it was not a security issue because setting up the OAuth provider requires an administrator.&lt;/p&gt;
&lt;p&gt;We disagree. Their reasoning treats the privilege to &lt;em&gt;choose&lt;/em&gt; an endpoint as the privilege to &lt;em&gt;control&lt;/em&gt; that endpoint's responses.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Affected version: Zabbix 8.0.0beta2, commit &lt;code&gt;31eccf9ddaf7adf129f9cd611c85b7451b188eb9&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;</content><category term="Security"/><category term="zabbix"/><category term="sqli"/><category term="oauth"/><category term="disclosure"/></entry></feed>