Smartbi那些事
前言:
Smartbi确实对于我在漏洞分析方面提供了很多思路和提升。要分析1day就得根据官方发布的补丁来“对症下药”,但是这个Smartbi的补丁是经过加密的,解包对于我一个菜鸡来说,还是有点顶。后来在师父提供的思路下,我通过学习还是搞了出来,首先在Smartbi的命令启动框会显示一些堆栈信息,在打了补丁之后,lib目录下的源码经过我diff之后发现没有一点变化,我用工具把Smartbi全局文件进行比较,发现只多了一个3k的ext文件。打开之后文件内容和我的大脑是一样的凌乱的。师父给了一种思路想,就是在打了补丁之后的程序中,去从内存中直接操作他的字节码,将补丁详细信息挖出来。后来我就学了类加载、学了javassist。终于在我的不懈努力下,将加密的ext给挖了出来。至于分析,很简单,难的是补丁解包。分析的话就跟着补丁走,找函数,看参数,跟就完了。经过Smartbi这一系列洞的分析,确实收获了不少的东西,希望后面自己可以多接触这种难题。
Smartbi的洞我都已经通过官方补丁复现和分析过了,只是有些能发有些发不了,后续要是能发我会第一时间分享出来。下面就先看两个洞,只是分析文章,并没有任何测试和攻击的payload,感兴趣的童鞋可以自己跟一下。
SmartbiToken回调
根据官方补丁,发现对setAddress添加了规则所以我们主要找setAddress。
通过查找我们找到了MonitorService.setAddress
:
可以看到,这里先是创建对象,然后对对象解密,之后用获取对象中的json数据,其中key有:type、c_address、u_address之后会对type做判断是哪个类型就执行哪个操作。
所以我们要传入这个address的话,并不能之间以明文形式传入,我们在函数内部用desEncode()
方法传入key:isPassword
再对我们传入的数据:{"type":"experiment","c_address":"http://10.65.182.92:9999","u_address":"http://10.65.182.92:9999"}
进行加密得到结果为:312E8684378EBDFF7E798B0BCCC45588EF682890F6F1701AF9D9416B4E357E80A1E8622D15B57E600944EC786F4E598DDE87CF4513277AC2EF3B38E69123E4B6C6BBDCDD5B6667AF74E81B278122E7F70E78A64F63A3D29BE2ADA07B0ADB17D88C825CA874F302FF
可以看到if分支中有两个方法:setEngineAddress()
、setServiceAddress
。且这里只能调用setEngineAddress()
方法,因为再正式调用方法之前会提前进行判断,调用setEngineAddress()
的两个参数serviceAddress和cAddress都是我们可以控制的,但是setServiceAddress
我们是控制不了的因为他会只有一个参数我们能控制:cAddress另外一个参数serviceAddress是会从服务器端获取的。
看到这里我们再跟进一下setEngineAddress()
:
跟进updateSystemComfig:
可以看到updateSystemConfig将我们传入的engineAddress进行了更新。
往下看发现了MonitorService.getToken()
:
他通过接收type然后对type进行判断,从而将result以json格式发送到指定的引擎URL。
到这里我们也就清楚了漏洞的成因,到这并不是最危险的,最危险的是他竟然有一个用Token登录的方法:
至此我们就把整个漏洞的利用过程也已经分析完。
其实还有一种利用方法就是直接调用setEngineAddress
设置引擎地址:
这种方法比上面介绍的那种方法更简单一些,因为少了加密的步骤,可以直接将地址传入,之后就是和之前相同的流程了。
Smartbi破解密码
官方补丁:
开始部分是判断请求路径是否以"/vision/RMIServlet"
开始之后进入后续接收参数的流程。
这里先用windowUnloading
方式,传入我们之前在内置用户绕过漏洞中自定义的RMICoder.encode
因为代码底层的encode和decode是不对称的。
之后我们会来到熟悉的needToCheck()
方法:
这一步我们在之前讲过,这段代码的作用是对请求进行过滤和验证,以确保符合指定条件后才执行后续的操作。
因为我们发送的是POST请求,紧接着会进入doPost()
:
在doPost中会对POST请求中传入的参数进行再次接收。
其实看到这里我们也就清楚了为什么既要用windowUnloading
传参,还要用POST传参。
我们的目的是利用windowUnloading
传入的参数去绕过needToCheck()
中的检测,之后等程序顺利走到doPost
中我们可以重新传入危险参数进行调用。
所以到这里我们就大概清楚了漏洞是如何产生的。下面我们具体分析一下每个参数的含义。
红色部分为className的利用过程。
首先processExecute()
会接收我们传入的className
、methodName
、params
。我们先来看className
:
它通过RMIModule.getInstance()
方法获取RMIModule
的实例,然后调用其getService(className)
方法来获取指定类名(className)
对应的服务对象。只有最终的service
不为null才能进入到后续的利用。
UserService
是一个用户服务类,用于管理用户、角色和用户组的信息。它提供了一系列方法来进行用户、角色和用户组的创建、更新、删除以及相关操作。
所以这里我们只能传入UserService
;我试过白名单中其他类方法,但是没有可以成功利用的。
接下来我们来看其他两个变量methondName
和jsonParams
。在满足service != null
的前提下,我们会进入到service.execute()
而这个方法正好就接收我们传入的methodName
和jsonParams
。
那我们可以跟进一下:
它通过反射机制获取方法对象,并根据参数的类型和个数进行校验和数据转换,最后调用executeInternal
方法进行反射调用传入的变量var3
方法中的this.b
传入var2
并返回执行结果
我们再进一步进行跟进:
终于发现了漏洞触发点,而getPassword
简单来说就是获取指定用户的密码。并返回查询结果,params
就是我们需要查询密码的用户名。
经过前面我们对前三个参数的分析我们已经大概清楚了漏洞利用流程。这时候你可能会问:你上面的POST数据可不止传入了三个参数。
其实最后一个参数的含义是为了让他能够不加密将结果返回到页面当中,我们看一下代码:
这一步会从request中接收参数jsonpCallback
并进行判断,若为空则对响应数据进行加密,若不为空则不加密。所以我们可以随意构造参数jsonpCallback
保证回显到页面的数据是没有加密的。
当然黑客也可以选择上面的if分支,得到加密的返回数据后,自己对其进行解密。这样使得流量会更加隐蔽从而不容易被发现。