Secret is an easy linux box where you have to exploit a sensitive information leak in a git repo to recover a JWT secret, which allows you to forge a JWT token that gives you access to an API endpoint that’s vulnerable to command injection. Once on the box, you will be exploiting a custom SUID binary that allows for core dumping.
Info
Recon
NMAP
# Nmap 7.70 scan initiated Sat Oct 30 20:00:45 2021 as: nmap -sV -sC -v -oN nmap.txt 10.10.11.120
Nmap scan report for secret.htb (10.10.11.120)
Host is up (0.25s 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)
80/tcp open http 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 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
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sat Oct 30 20:01:41 2021 -- 1 IP address (1 host up) scanned in 56.17 seconds
Web
The /doc
path in the website provides a demo for creating and accessing accounts using the API;
Possible credential?
Account login;
There is a link to download source code in the homepage;
It is a ZIP file, which I download and extract. It contains a .git
folder in the root, so it’s a GIT repository. Running git log
command showed something interesting;
So I ran the git diff
command using the commit ID to see what changes were made, and I got the token used to sign JWT tokens as indicated by the file routes/verifytoken.js
;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const jwt = require("jsonwebtoken");
module.exports = function (req, res, next) {
const token = req.header("auth-token");
if (!token) return res.status(401).send("Access Denied");
try {
const verified = jwt.verify(token, process.env.TOKEN_SECRET);
req.user = verified;
next();
} catch (err) {
res.status(400).send("Invalid Token");
}
};
The login API is also vulnerable to user enumeration as login attempt using a valid email with a bad password said Password is wrong, while attempt with invalid email says Email is wrong.
Foothold
Using Burp Suite, I was able to create an account using the following request;
Logging into the account gave me a token, which I forged on https://jwt.io using the JWT secret obtained, and changed the username to theadmin
(the administrative username obtained by going through the source files). Using the forged token, request to /api/priv
showed I am now the admin;
I am now authenticated as an admin, but still have no idea how to utilise the API.
Using the package.json
file in the root of the repo, which contains dependencies of the project and their version numbers, I started looking for exploits online, but couldn’t find anything interesting. So I continue to enumerate previous commits using git diff
, and found an interesting commit with the ID e297a2797a5f62b6011654cf6fb6ccb6712d2d5b
;
That looks like a shell command that can be exploited if I can control the value of req.query.file
. Crafting a request in Burp Suite for the endpoint, I got this;
I assumed req.query
is used to store all queries defined in the request, so I added a GET parameter named file
with a test value, and it got included in the command;
Injecting an encoded bash reverse shell in the file
parameter, I got a shell as the user dasith;
PrivEsc
Can’t list sudo permissions for the user as I still don’t have his password. netstat
showed a service running locally, which was identified as MongoDB using telnet
;
Checking the .env
file, I got a path for the service;
The user does not have SSH public key authentication setup, so I used ssh-keygen
to setup one, so I can setup port-forwarding to the MongoDB service over SSH. I still got the same error when trying to access the MongoDB service over HTTP.
After reading up on MongoDB, I found a cheatsheet at https://gist.github.com/michaeltreat/d3bdc989b54cff969df86484e091fd0c that helped me enumerate the service. I found a database named web-auth
containing four (4) hashes;
Attempt to crack the hashes using john
was taking way too long, so I aborted it and move on. One of the hashes was eventually cracked, but didn’t give me access to anything.
Inside /opt/
, I found a SUID binary owned by root, and what looks like the source code. Running it, it asks for a filename, and then report some stats for the file;
Privileges are dropped when saving results, which means I can’t write anything to protected paths. Attempts to step through the process while loading protected files using strace
and gdb
failed because they can’t attach to a process of higher privileges.
Going through the source code, I noticed core dumping was enabled;
Fuzzing the program’s input did not cause the program to crash. After taking a sanity check, I realized I could cause a program to crash and dump core by sending it a SIGSEGV (signal ID: 11). Running kill -n 11 <PID>
while the program is asking where the results should be saved, I was able to crash it;
Checking /var/crash
, I found the crash file generated;
After googling around, I learned that such crash files can be processed using apport-unpack
tool, so I did;
The file CoreDump
was identified as the core dump;
So I load it in the gdb
installed on the host, and searched the memory mappings of the program using thefind
command, and manually go through the stack using x/s <some-address>
, but could only locate the name of the file (/root/.ssh/id_rsa
) that was given to the SUID binary before the crash. With the help of the strings
command-line tool, I was able to extract the private key of root
(which was the file given to the binary before the crash);
I used the key to gain access to the box as root
over SSH;
Summary
- Identified running services with
nmap
- Used the documentation to create an account on the web API.
- Found the JWT token signing key in a previous commit in the source files using
git diff
, and used it to forge a token for the admin user. - Found the path
/api/logs
that’s accessible only to admins is vulnerable to command injection, which I exploited to gain access to the box as the userdasith
- Inside the box as
dasith
;- Exploited a SUID binary by causing it to core dump after reading the private SSH key of the root user.
- Retrieved the SSH key in the crash file using
apport-unpack
andstrings
, and gained root access to the box over SSH.