1. XML-RPC反序列化命令执行(CVE-2020-9496)
1-1. 前言
官方公告:
https://issues.apache.org/jira/browse/OFBIZ-11716
漏洞描述:
由于 webtools 中的 2 个 xmlrpc 相关请求(xmlrpc 和 ping)未使用身份验证,以及未对传入XML-RPC消息进行校验,所以容易受到不安全反序列化的影响。
影响版本:
Apache OfBiz 17.12.03
1-2. xml-rpc请求格式
XML内容的根元素是,每个元素都包含一个元素和一个元素。元素标识要调用的过程的名称,而元素包含参数列表和值。每个元素包括一个元素列表,而元素又包含值元素。
示例:
`<?xml version="1.0"?>``<methodCall>` `<methodName>方法名</methodName>` `<params>` `<param>` `<value>` `值元素` `</value>` `</param>` `</params>``</methodCall>`
值元素类型可以是基本数据类型,也可以是复合数据类型。
基本数据类型有:
int:27, 27
double:27.31415, -1.1465
Boolean:1, 0
string:Hello, bonkers! @
dateTime.iso8601:<dateTime.iso8601> 20021125T02:20:04 </dateTime.iso8601>
base64:SGVsbG8sIFdvcmxkIQ==
复合数据类型就是指数组和结构;
数组可以包含多种基本数据类型:
`<array>` `<data>` `<value><boolean>1</boolean></value>` `<value><string>Array collection, eh?</string></value>` `<value><int>-91</int></value>` `<value><double>142.14325</double></value>` `</data>``</array>`
结构通常用来传递复杂的数据结构(键-值对形式):
`<struct>` `<member>` `<name>键1</name>` `<value><string>值1</string></value>` `</member>`` ` `<member>` `<name>键2</name>` `<value><int>值2</int></value>` `</member>``</struct>`
参考链接:
https://www.yiibai.com/xml-rpc/xml\_rpc\_data\_model.html
1-3. 环境搭建
下载地址:https://archive.apache.org/dist/ofbiz/
解压后用IDEA打开,build之后会生成一个biuld目录,添加配置ofbiz.jar;
能成功访问https://localhost:8443/webtools就说明环境搭好了。
Apache OFBiz 结构简单介绍:
applications:各个应用的目录,包含了OFBiz核心的应用程序组件,如订单管理,电子商务存储等。
framework:框架目录,包含OFBiz框架的组件,例如实体引擎和服务引擎。这是OFBiz框架的核心,其他应用程序都是基于它来构建的。
Ofbiz-component.xml:定义应用程序指定配置文件,库文件等,数据模型,业务逻辑,web应用程序的定位。
src:源文件目录,包含实现业务逻辑的Java类文件。
web.xml:用于配置 Web 应用程序的部署信息和行为,设置路由、filter等。
Controller.xml:定义view,controller之间的映射关系,不仅包含了请求的映射关系,同时还包含了视图的映射关系,以及一系列的处理器比如视图解析处理器,事件处理器等。
1-4. 漏洞复现
POST请求/webtools/control/xmlrpc,使用ysoserial工具生成一个 CommonsBeanutils1 链的payload,要用base64编码。
`<?xml version="1.0"?>``<methodCall>``<methodName>ProjectDiscovery</methodName>``<params>` `<param>` `<value>` `<struct>` `<member>` `<name>test</name>` `<value>` `<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">` `base64 payload` `</serializable>` `</value>` `</member>` `</struct>` `</value>` `</param>``</params>``</methodCall>`
发送请求,dnslog成功收到请求信息。
1-5. 漏洞分析
入口/webtools/control/xmlrpc的路由配置在\framework\webtools\webapp\webtools\WEB-INF\web.xml,由ControlServlet处理;
直接下断点开始跟踪调试,提取了一堆值,然后调用requestHandler.doRequest()处理具体请求;
跟进,requestMap是从\framework\webtools\webapp\webtools\WEB-INF\controller.xml中获取到的xmlrpc相关处理器;
跟进RequestHandler.runEvent()方法,调用XmlRcpEventHandler处理 XML-RPC 请求,传递必要的参数,然后获取并保存事件处理器执行后的返回结果。
跟进,调用getRequest()获取信息对象;
先依次扫描、、、、元素标签,记录它的值;
读到标签,返回一个MapParser对象;
重复调用XmlRpcRequestParser.startElement(),继续读取元素标签及其值;
读到标签和使用的XML-RPC 扩展库链接时,跟进到RecursiveTypeParserImpl.startElement(),只有当pURI与EXTENSIONS_URI相同时,才能匹配且返回SerializableParser对象,所以payload中必须要添加这个扩展库;
SerializableParser是继承ByteArrayParser的,所以跟进到ByteArrayParser.startElement(),创建了一个base64解码器,将payload解码写入baos中。
然后开始扫描结束标签,扫描到标签,将payload的字节码设为result;
接着扫描到标签,解析SerializableParser对象中的result添加到到结果中;
跟进SerializableParser.getResult(),对payload字节数组进行了反序列化,触发命令执行。
1-5-1. 使用 array 标签
前面使用的是 struct 结构传入数据,扫描到标签返回的是MapParser对象,MapParser.endElement()中调用了父类RecursiveTypeParserImpl.endValueTag(),进而调用SerializableParser.getResult()。
同理,ObjectArrayParser.endElement()中也调用了父类RecursiveTypeParserImpl.endValueTag(),所以也可以使用标签构造传入数组数据触发反序列化。
poc:
`<?xml version="1.0"?>` `<methodCall>` `<methodName>ProjectDiscovery</methodName>` `<params>` `<param>` `<value>` `<array>` `<data>` `<value>` `<serializable xmlns="http://ws.apache.org/xmlrpc/namespaces/extensions">` `base64 payload` `</serializable>` `</value>` `</data>` `</array>` `</value>` `</param>` `</params>` `</methodCall>`
1-6. 补丁分析
补丁链接:https://github.com/apache/ofbiz-framework/commit/d955b03
在controller.xml中对/xmlrpc和/ping路由添加了身份校验。
但是身份校验仍然可以绕过,产生POST-Auth漏洞(OFBIZ-12332):
https://issues.apache.org/jira/browse/OFBIZ-12332
又在后续补丁中添加CacheFilter对/control/xmlrpc和</serializable进行校验,但是还是可以绕过。
2. XML-RPC反序列化命令执行(CVE-2023-49070)
2-1. 前言
官方通告:
https://issues.apache.org/jira/browse/OFBIZ-1281
https://lists.apache.org/thread/jmbqk2lp4t4483whzndp5xqlq4f3otg3
漏洞描述:
由于对CVE-2020-9496修复的不完全,导致可以绕过路径检查和授权检查触发恶意数据的反序列化。
影****响版本:
Apache OfBiz < 18.12.10
2-2. 漏洞复现
环境搭建操作与CVE-2020-9496一样,这里用18.12.09复现。
用CVE-2020-9496的poc进行测试,可以看到调用了CacheFilter进行路径和</serializable标签的检查,检查失败出现报错。
在url中略作修改,即可绕过路径检查和授权检查,成功执行命令。
/webtools/control/xmlrpc/;/?USERNAME=ofbiz&PASSWORD=blckder02&requirePasswordChange=Y
说一下url的构造,这个请求结构如下:
路径信息:/webtools/control/xmlrpc/
矩阵参数:;/
查询参数:?USERNAME=ofbiz&PASSWORD=blckder02&requirePasswordChange=Y
矩阵参数(Matrix Parameters)是一种在URL中传递参数的方式,其特点是使用分号;将参数附加在路径中。
这里就是附加了一个参数/,或许将参数换成;1要好理解一点,效果一样的。
与查询参数不同的是,使用getRequestURI()的返回结果会包含矩阵参数,所以能绕过路径检测。但是getPathInfo()仍然只返回/xmlrpc/,不影响后面xmlrpc处理器的调用。
查询参数的内容则是绕过授权检查的必要条件。
2-3. 漏洞分析
在发送请求的时候会进入CacheFilter.doFilter(),因为在uri的末尾添加了/;/,所以不会进入if语句,也就不会对</serializable进行校验;
然后进入ControlServlet处理请求,在RequestHandler.doRequest()中,securityAuth就是指Controller.xml中的security标签的值,为true则进入if分支;
跟进,调用JavaEventHandler处理该事件;
进入LoginWorker.checkLogin(),如果检测到是未登录状态,则直接从请求中获取USERNAME和PASSWORD参数的值,如果值为null,则会进入343行的if语句,返回error,表示校验失败;
参数值不为null,还需要使login(request, response)不返回error。
进入login(),从请求中获取了requirePasswordChange参数的值,执行登录操作时由于传入的用户名是随便构造的,所以返回“没有找到用户”,登录失败,进入else分支;
最后根据requirePasswordChange的值判断是返回参数值还是error,所以只要传入requirePasswordChange为true,这里就可以返回true了。
回到checkLogin(),uri中不包含loginout,security.login.authorised.during.impersonate默认为false,登录历史也为null,所以跳过if语句,最后返回success,成功绕过登录检测。
后面的反序列化调用过程就与CVE-2020-9496一样了。
2-4. 补丁分析
补丁链接:https://github.com/apache/ofbiz-framework/commit/c59336f604f503df5b2f7c424fd5e392d5923a27
在补丁中删除了XML-RPC的相关引用。
3. groovy命令执行(CVE-2023-51467)
3-1. 前言
官方公告**:**
https://lists.apache.org/thread/9tmf9qyyhgh6m052rhz7lg9vxn390b
https://issues.apache.org/jira/browse/OFBIZ-12873
漏洞描述:
仍然是基于对CVE-2020-9496修复的不完全,导致可以绕过路径检查和授权检查。以及对传入数据的校验不完善,导致可以通过groovy实现命令执行。
影响版本:
Apache OfBiz < 18.12.11
3-2. 漏洞复现
添加groovyProgram参数传入要执行的命令,payload:
/webtools/control/ProgramExport?USERNAME=ofbiz&PASSWORD=blckder02&requirePasswordChange=Y&groovyProgram='calc.exe'.execute()
3-3. 漏洞复现
授权检查绕过的过程与CVE-2023-49070的一致,这里就不重复了。
从绕过授权后开始看,在RequestHandler.doRequest()中,requestUri为ProgramExport,获取到对应的requestMap,又从中获取到success对应的响应,赋给了nextRequestResponse;
在controller.xml中定义了响应类型为view,名称为ProgramExport,然后调用renderView()进行视图渲染。
跟进renderView(),从controller.xml中获取名称为ProgramExport的视图映射,定义在 component://webtools/widget/EntityScreens.xml#ProgramExport文件中;
获取到screen类型的视图处理器;
一直跟进,从EntityScreens.xml中获取到了ProgramExport对应的脚本文件位置component://webtools/groovyScripts/entity/ProgramExport.groovy,当遍历到此文件时跟进;
检测到文件后缀为.groovy,运行这个脚本文件;
进入ProgramExport.groovy,从传入的参数中获取到了groovyProgram参数值,最后调用evaluate()执行命令。
其中还调用了SecuredUpload.isValidText()对groovyProgram进行黑白名单校验,黑名单内容来自\framework\security\config\security.properties#deniedWebShellTokens,白名单就是import。
3-4. 补丁分析
补丁地址:https://github.com/apache/ofbiz-framework/commit/ee02a33509
修复了授权检查的地方,在登录失败后直接返回error,而不是再去判断requirePasswordChange的值。
参考链接:
https://mp.weixin.qq.com/s/iAvitO6otPdHSu1SjRNX3g
https://cwiki.apache.org/confluence/display/OFBIZ/Understanding+the+OFBiz+Widget+Toolkit