Secret - HackTheBox
Secret Machine(10.10.11.120)
Info:
This machine had pretty sweet learning curve for new comers, exploiting command injection to get foothold and core-dump abuse to get root on machine.
Recon:
Starting with portscan, we get 3 open ports.
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
| http-methods:
|_ 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)
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
on port 3000 node js application is running and port 80 has docs for same application. And there is source-code avialable for downlaod.
Docs shows how using API we can register new user and login it will then give JWT token for that user. there is theadmin user which is admin.
Directory fuzzing reveal already known paths only.
Foothold: Command Injection
Reading docs let's register a user by sending post requests to /api/user/register.
Login to get auth-token
A JWT token is set as our auth-token and using this we can make request to /api/priv
And it tells we are normal user. Let's decode our jwt token at jwt.io
Finding vulnerable route
Looking into source code we downloaded. There is an interesting function in /routes/private.js file
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);
})
}
The endpoint /api/logs is vulnerable to command injection as user input file is directly passed into exec command. But for that we have to somehow become theadmin user. But we don't have any secret to forge JWT token. One i tried using was secret=secret as listed in .env file but that didn't work.
Finding .git folder
Let's examine downloaded source-code once again.
There is folder .git which we didn't notice earlier. Let's change directory and see what it offers.
using git log command we can list all the commits that happened.
second commit is interesting as it says removed .env where our secret is stored let's look into that using git show <commit_id>.
As it shows, older JWT secret is replaced with secret. Let's use this to sign our new auth-token and get admin.
gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
As code only checks username from jwt token, we just have to change that to theadmin and give it secret obtained.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MjNlYzk2NGFiN2I1YzA0NTkxOGJkOTIiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJpYXQiOjE2NDgyODE5ODB9.fjQzAdpsLN1B7_0gnLv_hWN_E2LAG7KuIMdJhjkm0vM
Let's put this new JWT into request and verify.
Command injection:
As it requires a file name to show git logs. Let's do that
As shown our input "ip" is concatenated in command. Let's get code execution with "ip;id"
Let's get shell from here
Privilege escalation: Abuse Core-Dump to read files
In /opt there is a suid binary count which counts number of lines, characters, words in a file. Being suid binary it can also read privileged files. we just have to find a way to extract what it reads from memory. If you try to run suid binaries with some external tool like gdb, suid privilege will drop.
There is also code.c file given for the binary. One important thing i noticed was this
PR_SET_DUMPABLE will decide whether to generate core-dump when crashing. And this crash can also contain sensitive info. Google core dump privilege escalation gave this blog.
Which states that which type of process kill signals will generate core_dump.
Get another shell in another pane to kill the process after reading the sensitive file.
Dumping core:
Run the binary from one pane
Now kill this process from another pane, this blog has detailed explanation on how to send kill signals.
using SIGBUS signal we kill the process , killall -s SIGBUS count
.
and it kills the process in another pane & generates the core dump in /var/crash
Now this .crash file has all the information we need but we can't just read it from here, as i tried decoding base64 value in it. it doesn't work.
Little bit of google gave this, using apport-unpack, which is luckily installed on system, we can generate useful Human redable information.
apport-unpack _opt_count.1000.crash crash
will generate crash directory with all information.
and in CoreDump file we can see the contents of file we read.
To get shell let's read root's ssh keys. or crack the password obtained from /etc/shadow or directly read root flag,totally your choice.
Let's again create the core-dump after reading /root/.ssh/id_rsa & read the CoreDump content.
and using this key we can login as root.
ssh -i id_rsa root@10.10.11.120
That's how we get root on this machine and don't forget to remove your scripts and crash dump from machine before leaving.
Thank you for reading.
Twitter: Avinashkroy
Comments
Post a Comment