1.前言
官方公告:
https://issues.apache.org/jira/browse/OFBIZ-13006
https://lists.apache.org/thread/w6s60okgkxp2th1sr8vx0ndmgk68fqrd
https://issues.apache.org/jira/browse/OFBIZ-13092
https://lists.apache.org/thread/sv0xr8b1j7mmh5p37yldy9vmnzbodz2o
漏洞描述:
通过目录遍历可以绕过授权验证实现命令执行。
影响版本:
CVE-2024-32113:
Apache OfBiz < 18.12.13
CVE-2024-36104:
Apache OfBiz < 18.12.14
2. 环境搭建
下载地址:https://archive.apache.org/dist/ofbiz/
解压后用 IDEA 打开,点击右侧栏 Gradle 中的 build 之后会生成一个 biuld 目录,该目录下面会生成一个ofbiz.jar,Run/Debug Configurations 中会自动生成一个 Gradle 配置项;
新增JAR Application,添加指定ofbiz.jar路径。
启动这个jar。
能成功访问https://localhost:8443/webtools就说明环境搭好了。
3. 漏洞复现
在调试分析的过程中发现,导致命令执行的关键原理跟目录遍历并没有绝对关系,与其说这是目录遍历导致的代码执行,不如说是授权检查绕过。
基于 CVE-2024-51467 修复授权检查绕过的方法是修改 LoginWorker.checkLogin() 登录校验方法的返回逻辑,所以不能再通过传参来绕过授权检查。
但是仍然可以从securityAuth入手,以不需要身份验证的 uri 为跳板来调用/ProgramExport对应的视图。
(详情可回顾:Apache OFBiz 命令执行漏洞分析)
直接构造 18.12.14 及之前版本通杀的路径 payload,不需要进行目录遍历。
/webtools/control/ListTimezones/ProgramExport?groovyProgram='calc.exe'.execute()
其中/ListTimezones部分是可替换的,在 common-comtroller.xml 中,只要满足auth="false"、以及success响应结果对应type="view"的 uri 都可以进行利用。比如这些:forgotPassword、ListSetCompanies、showHelpPublic、getUiLabels、ListTimezones、ListLocales。
18.12.13 版本中,在 security.properties 中的黑名单里新增了execute等关键字;
所以 18.12.13 和 18.12.14 版本的 payload 需要对 groovyProgram 参数内容进行编码传入来绕过黑名单校验。
`/webtools/control/ListSetCompanies/ProgramExport` ` ``groovyProgram=\u0027\u0063\u0061\u006c\u0063\u002e\u0065\u0078\u0065\u0027\u002e\u0065\u0078\u0065\u0063\u0075\u0074\u0065\u0028\u0029`
uri ListTimezones的 auth 改为了 true,不能进行利用了,不过其他的 uri 仍然可以利用。
4. 漏洞分析
接下来以 18.12.12 版本为例分析看看,如果请求的路径为/webtools/control/ListTimezones/./ProgramExport,在 ControlFilter.doFilter() 中,getRequestURI() 首先获取到的是/control/ListTimezones/./ProgramExport,经过规范化后去除了其中的.,变成了/control/ListTimezones/ProgramExport。
所以特殊符号在这一步都会被过滤掉,18.12.13 以及 18.12.14 版本的补丁都在围绕这一块代码进行修改补充,不过这并不是关键所在。
接着断点 ControlServlet.doGet(),这时 request 中的路径就为/webtools/control/ListSetCompanies/ProgramExport。
跟进 RequestHandler.doRequest(),看获取到的几个路径参数,path 为/ListSetCompanies/ProgramExport,requestUri 取的是 path 的第一部分ListSetCompanies,overrideViewUri 取的是路径的第二部分ProgramExport,如果没有就为 null;
rmaps 为ListSetCompanies对应的 requestMap,其中 securityAuth 属性为 false。
继续向下看,到判断 securityAuth 的值,为 false 则不进入 if 分支,也就不会调用 LoginWorker.checkLogin() 进行登录验证。
获取 requestMap 中 success 的响应数据,然后赋给 nextRequestResponse,其中 type 为view,下面进入 view 的分支;
此时 eventReturn 的值是 null,740 行的逻辑表达式为 true,视图名称得到 overrideViewUri 的值ProgramExport,关键就在这里,将路径的第二部分作为视图名称赋值,后面就是进行视图渲染。
获取传入的 groovyProgram 参数值,从 \framework\webtools\webapp\webtools\WEB-INF\controller.xml 中找到 ProgramExport 的视图信息定义在 component://webtools/widget/EntityScreens.xml#ProgramExport 文件中。
获取到 screen 类型的视图处理器。
一直跟进,从 EntityScreens.xml 中获取到了ProgramExport对应的脚本文件位置 component://webtools/groovyScripts/entity/ProgramExport.groovy ,当遍历到此文件时跟进。
检测到.groovy后缀,进入 ProgramExport.groovy 文件,执行 groovyProgram 参数的命令。
5. 补丁分析
18.12.13版本: 主要体现在将ListTimezones的 auth 改为 true,黑名单里增加execute关键字,ControlFilter.doFilter() 中校验了规范化前后的 url 是否一致,以此来判断 url 是否含有特殊字符。
18.12.14版本:
网上使用的 payload 是将.换为%2e或;来绕过 18.12.13 版本的修复代码,于是官方在 18.12.14 版本的 ControlFilter.doFilter() 中添加过滤了%2e和;,不过仍然没有修复到问题关键。