浅析Smartbi逻辑漏洞

admin 2024年10月29日00:19:21评论10 views字数 3344阅读11分8秒阅读模式

浅析Smartbi逻辑漏洞

写在前面

仅分享逻辑漏洞部分思路,全文以无害路由做演示,后续利用部分打码处理

厂商已发布补丁:https://www.smartbi.com.cn/patchinfo

分析

最近可以看到smartbi官网突然发布了新的补丁,对比学习了下

利用也仍然和RMIServlet相关,在这个Servlet上还有个Filter(smartbi.freequery.filter.CheckIsLoggedFilter)

如果我们访问调用一些未授权的类方法,就会返回如下字段

浅析Smartbi逻辑漏洞

我们先来看看如果正常情况程序该怎么走,首先如果调用RMIServlet,则会尝试获取到className与methodName,获取的方式也多种多样

浅析Smartbi逻辑漏洞

有通过解码windowUnloading参数获取

浅析Smartbi逻辑漏洞

有通过GET/POST获取

浅析Smartbi逻辑漏洞

甚至支持从请求体流中解析

浅析Smartbi逻辑漏洞

后面通过下面这两个判断对类与方法做鉴权,如果为true则会继续判断是否登录,未登录则抛出CLIENT_USER_NOT_LOGIN

浅析Smartbi逻辑漏洞

这里对于未授权右边部分我们可以不必关心,按照运算符优先级只要FilterUtil.needToCheck返回false那么整个结果一定为false,而FilterUtil.needToCheck中返回false的都是白名单,代表我们不需要登录都能访问,这也就是为什么上个版本通过利用loginFromDB登录默认内置用户

浅析Smartbi逻辑漏洞

接下来过了过滤器部分,我们在看RMIServlet如何取值,通过parseRMIInfo从request当中取得

浅析Smartbi逻辑漏洞

在这个方法中首先通过request.getParameter取值,若为空则通过multipart获取参数,如果都不行则通过request.getAttribute从之前保存的属性当中获取

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
public static RMIInfo parseRMIInfo(HttpServletRequest request, boolean forceParse) {        if (!"/vision/RMIServlet".equals(request.getServletPath()) && !forceParse) {            return null;        } else {            RMIInfo info = getRMIInfoFromRequest(request);            if (info != null) {                return info;            } else {                String className = request.getParameter("className");                String methodName = request.getParameter("methodName");                String params = request.getParameter("params");                if (StringUtil.isNullOrEmpty(className) && StringUtil.isNullOrEmpty(methodName) && StringUtil.isNullOrEmpty(params) && request.getContentType() != null && request.getContentType().startsWith("multipart/form-data;")) {                    DiskFileItemFactory dfif = new DiskFileItemFactory();                    ServletFileUpload upload = new ServletFileUpload(dfif);                    String encodeString = null;                    try {                        List<FileItem> fileItems = upload.parseRequest(request);                        request.setAttribute("UPLOAD_FILE_ITEMS", fileItems);                        Iterator var10 = fileItems.iterator();                        while(var10.hasNext()) {                            FileItem fileItem = (FileItem)var10.next();                            if (fileItem.isFormField()) {                                String itemName = fileItem.getFieldName();                                String itemValue = fileItem.getString("UTF-8");                                if ("className".equals(itemName)) {                                    className = itemValue;                                } else if ("methodName".equals(itemName)) {                                    methodName = itemValue;                                } else if ("params".equals(itemName)) {                                    params = itemValue;                                } else if ("encode".equals(itemName)) {                                    encodeString = itemValue;                                }                            }                        }                    } catch (UnsupportedEncodingException | FileUploadException var14) {                        LOG.error(var14.getMessage(), var14);                    }                    if (!StringUtil.isNullOrEmpty(encodeString)) {                        String[] decode = (String[])((String[])CodeEntry.decode(encodeString, true));                        className = decode[0];                        methodName = decode[1];                        params = decode[2];                    }                }                if (className == null && methodName == null) {                    className = (String)request.getAttribute("className");                    methodName = (String)request.getAttribute("methodName");                    params = (String)request.getAttribute("params");                }                info = new RMIInfo();                info.setClassName(className);                info.setMethodName(methodName);                info.setParams(params);                request.setAttribute("ATTR_KEY_RMIINFO", info);                return info;            }        }    }

利用

这时候稍微对漏洞敏感的人已经意识到了一些问题

前面提到了有个windowUnloading参数,如果存在则会对值做解码,并将结果赋给className/methodName/params,

浅析Smartbi逻辑漏洞

那么我们是不是就能首先根据此对参数做污染,让其帮助我们通过FilterUtil.needToCheck的校验,之后等到了RMIServlet,又通过GET/POST/表单当中的值恢复真实调用

浅析Smartbi逻辑漏洞

关于构造windowUnloading,为了演示方便我选择else分支,因为这样返回的内容是明文,省去一次解码的问题

浅析Smartbi逻辑漏洞

当然选择上面这个if分支其实更好,这更方便我们使攻击流量更隐蔽,可以通过传入jsonpCallback参数去除解码,

浅析Smartbi逻辑漏洞

当然为了演示方便我还是选择else分支,任意选择FilterUtil.needToCheck当中的类方法

1
className=UserService&methodName=checkVersion&params=y4

下面做演示,未使用windowUnloading前,调用受限方法会提示未登录(这里以无害方法做演示)

浅析Smartbi逻辑漏洞

使用后成功调用

浅析Smartbi逻辑漏洞

通过未授权调用,我们可以获取用户敏感信息包括密码

浅析Smartbi逻辑漏洞

通过上版本中提到的直接比对数据库方式登录

浅析Smartbi逻辑漏洞

最终可实现RCE,当然RCE也不止这一种

浅析Smartbi逻辑漏洞

后话

上面提到可以配合RMICoder编解码使流量更隐蔽,同样以第一个无害方法getSystemId为例子

浅析Smartbi逻辑漏洞

- source:y4tacker

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月29日00:19:21
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅析Smartbi逻辑漏洞https://cn-sec.com/archives/3314613.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息