This is a detailed walk-thru for secret.htb written by dR1PPy
This challenge was just what was needed on the heels of a really good JWT UDemy course.
Based on the items covered in that class this machine was very easy and straight forward.
Below is the walk-thru for getting access and escalating thru the HTB challenge known as “Secret”
Enumeration
As always we start with a fresh scan and enumeration of services on the target.
autorecon -o ./ 10.10.11.120
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack 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)
| 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=
| 256 95:ed:65:8d:cd:08:2b:55:dd:17:51:31:1e:3e:18:12 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOL9rRkuTBwrdKEa+8VrwUjloHdmUdDR87hBOczK1zpwrsV/lXE1L/bYvDMUDVD0jE/aqMhekqNfBimt8aX53O0=
| 256 33:7b:c1:71:d3:33:0f:92:4e:83:5a:1f:52:02:93:5e (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINM1K8Yufj5FJnBjvDzcr+32BQ9R/2lS/Mu33ExJwsci
80/tcp open http syn-ack nginx 1.18.0 (Ubuntu)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: nginx/1.18.0 (Ubuntu)
3000/tcp open http syn-ack Node.js (Express middleware)
|_http-title: DUMB Docs
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
We launch a web content scan and find the following valid URL’s.
/download (Status: 301) [Size: 183] [--> /download/]
/docs (Status: 200) [Size: 20720]
/assets (Status: 301) [Size: 179] [--> /assets/]
/api (Status: 200) [Size: 93]
/Docs (Status: 200) [Size: 20720]
/API (Status: 200) [Size: 93]
/DOCS (Status: 200) [Size: 20720]
Lastly we review the website and see it offers documentation for an API, access to an API, and a link to download the source code.
Gaining Access
First we download the source code from:
http://secret.htb/download/files.zip
Providing all the source code directly for the live site you are running. What could possibly go wrong?
We extract the source and review the git history and find one reference to the removal of the .env for security reasons.
git log
...
commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
Author: dasithsv <dasithsv@gmail.com>
Date: Fri Sep 3 11:30:17 2021 +0530
removed .env for security reasons
Reviewing the revision changes shows a token for the DB user
git log -p -2
...
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
-TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
+TOKEN_SECRET = secret
From the API documentation it seems you can query to verify you admin access.
Reviewing the source shows this may also be vulnerable to abuse.
local-web/routes/private.js
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,"
}
})
}
else{
res.json({
role: {
role: "you are normal user",
desc: userinfo.name.name
}
})
}
})
We see if we are able to change our name to “theadmin” we will be granted an admin role.
Let’s try some methods to do this.
First we create a valid user.
Then Login to get a JWT token to manipulate.
Now with our token and our secret key found in the source code we can modify our JWT token as needed. In this case we will change our name to “theadmin” to allow us access to those Admin resources.
Using this newly created token we get admin access to the API
Another review of the API source code shows one more item missed during initial review.
We can see there is a special “/logs” url that can be accessed by the Admin.
This also appears to accept user input without any sanitation.
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);
})
}
else{
res.json({
role: {
role: "you are normal user",
desc: userinfo.name.name
}
})
}
})
We can confirm this with a valid URL request. To see what a valid request looks like we first run that same “git log –oneline” command against our source code files. We see a valid request could work with index.js or validations.js
─$ git log --oneline validations.js
55fe756 first commit
We use this file to make a valid request.
Then another request with command injection.
Another request to /etc/passwd reveals the users home directory.
1000:1000:dasith:/home/dasith:/bin/bash\nlxd:x:
Using this method we wget a copy of our public SSH key and place it in .ssh under the users home directory.
We then login via SSH to get our shell.
Escalating Privileges
Next we use the great tool that is HTB-Enum (https://github.com/SolomonSklash/htbenum) and enumerate the host.
From the results in the lse-report we can see the following item.
============================================================( file system )=====
[*] fst000 Writable files outside user's home.............................. yes!
[*] fst010 Binaries with setuid bit........................................ yes!
[!] fst020 Uncommon setuid binaries........................................ yes!
---
/opt/count
---
In this same /opt directory we find a file named “code.c” which seems to be the source code being used by the count program.
Reviewing the source code and also the valgrind log does not reveal any interesting info.
Despite this we spend some time trying to debug the program both remote and locally looking for some way to overflow or escalate.
After an hour of failure we finally make note of one important clue from a 2nd SSH session.
The process loads the filename given into memory and holds it there during the “Save” prompt before exiting.
lsof -p 233129
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
count 233129 dasith cwd DIR 253,0 4096 394284 /home/dasith
count 233129 dasith rtd DIR 253,0 4096 2 /
count 233129 dasith txt REG 253,0 17824 393236 /opt/count
count 233129 dasith mem REG 253,0 2029224 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
count 233129 dasith mem REG 253,0 191472 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
count 233129 dasith 0u CHR 136,1 0t0 4 /dev/pts/1
count 233129 dasith 1u CHR 136,1 0t0 4 /dev/pts/1
count 233129 dasith 2u CHR 136,1 0t0 4 /dev/pts/1
count 233129 dasith 3r REG 253,0 33 265779 /root/root.txt
So if we can force a segmentation fault and memory dump we may be able to scrape our info from memory.
We use our 2nd session to create a crash of the program to access its memory dump.
Then we extract and review the crash files for our final flag (sanitized for your protection).
dasith@secret:/opt$ apport-unpack /var/crash/_opt_count.1000.crash /tmp/count-dump.txt
dasith@secret:/opt$ cd /tmp/count-dump.txt/
dasith@secret:/tmp/count-dump.txt$ strings * | grep -A1 root
/root/root.txt
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
--
/root/root.txt
Total characters = 33
dasith@secret:/tmp/count-dump.txt$
And now we know the secret formula……
Resources
https://infosecwriteups.com/attacking-json-web-tokens-jwts-d1d51a1e17cb
https://git-scm.com/book/tr/v2/Git-Basics-Viewing-the-Commit-History
https://jvns.ca/blog/2018/04/28/debugging-a-segfault-on-linux/