前端明文密钥+验证码逻辑缺失:一次SM4加密认证系统的暴力破解实战分析
字数 1920 2025-11-11 12:11:14
前端SM4加密认证系统安全漏洞分析与防御教学
一、漏洞概述
本教学文档基于对某业务系统登录模块的安全评估案例,详细分析前端SM4加密认证系统中存在的安全漏洞,包括前端硬编码密钥、验证码逻辑失效等问题,并给出完整的攻击复现方法和防御方案。
二、漏洞详细分析
2.1 前端加密逻辑完全暴露
漏洞发现过程
通过浏览器开发者工具分析前端Vue.js代码,定位到登录函数login()与加密模块efbb。
关键代码分析
// 登录请求封装
Object(i["login"])({
account: r.trim(),
password: Object(c["encrypt"])(o), // 密码经encrypt函数处理
uuid: s,
code: l
})
// 加密模块efbb
var i = n("8060").sm4; // 引入sm4库
var r = "E54CFxxxxxxxxxxxx99BE6C"; // 硬编码密钥!
function o(t) {
return i.encrypt(t, r); // 使用固定密钥加密
}
致命安全问题
- 密钥硬编码:SM4加密密钥
E54CFxxxxxxxxxxxx99BE6C以明文形式写死在前端代码中 - 加密参数暴露:加密模式(ECB)、填充方式(PKCS7)、输出格式(Hex)均可通过逆向分析获取
- 加密流程可复现:攻击者可以完全重现前端加密逻辑,将任意明文密码转换为合法密文
2.2 验证码机制失效分析
请求包分析
{
"account":"admin",
"password":"582a14732cf52ab9e2ed853f3f9000de",
"uuid":"",
"code":""
}
验证码机制缺陷
- 后端校验缺失:服务器未对
uuid和code字段进行有效性验证 - 绕过验证码:即使不触发验证码流程,也可直接提交登录请求
- 无防护措施:系统缺乏频率限制、失败锁定、IP封禁等安全机制
2.3 暴力破解攻击链构建
Step 1:复现SM4加密逻辑
使用Python复现前端SM4加密算法:
from sm4 import SM4Key
def sm4_encrypt(plaintext):
key = SM4Key(b'E54CFxxxxxxxxxxxx99BE6C')
ciphertext = key.encrypt(plaintext)
return ciphertext.hex()
# 验证加密结果
result = sm4_encrypt("123456")
# 输出:582a14732cf52ab9e2ed853f3f9000de
Step 2:构建爆破字典并加密
- 收集常用密码字典(如rockyou.txt、弱口令字典)
- 使用复现的SM4加密逻辑对所有密码进行预处理加密
- 生成加密后的密码列表作为爆破载荷
Step 3:自动化爆破实施
使用Burp Suite Intruder模块进行自动化攻击:
攻击配置:
- 目标:POST /apiuser/usr/session/Login
- 载荷位置:
{"account":"admin","password":"§payload§","uuid":"","code":""} - 载荷来源:预处理加密后的密码字典
- 识别标志:响应体中
"flag":true或"token"字段
攻击结果:
- 数分钟内成功爆破出admin账户的正确密码
- 获取JWT Token及用户上下文信息
- 获得管理员权限后可进行横向渗透
三、漏洞利用扩展
3.1 权限提升与横向移动
- 系统内漏洞发现:获取管理员权限后发现任意文件上传、SQL注入等高危漏洞
- 用户规律分析:分析系统用户名命名规律(如工号、邮箱前缀等)
- 批量账户接管:利用相同技术批量爆破其他用户账户
3.2 攻击工具优化
- 多线程爆破:提高爆破效率
- 智能字典生成:基于目标特征生成针对性字典
- 代理轮换:避免IP被封禁
四、技术启示与防御建议
4.1 技术启示
1. 前端加密的安全误区
- 错误认知:前端加密≠安全传输
- 根本问题:客户端执行的任何加密逻辑都可能被逆向分析
- 正确做法:敏感操作必须由服务端完成
2. 验证码机制的正确实现
- 前后端联动:验证码必须前后端协同校验
- 会话绑定:
uuid与code应在服务端绑定会话 - 一次性使用:验证码使用后立即失效
3. 国密算法的正确使用
- 算法误用:SM4等算法的错误使用反而增加安全风险
- 规范遵循:需严格按照安全编码规范实施
4.2 具体防御措施
1. 密码传输安全改进
// 错误做法:前端加密
password: Object(c["encrypt"])(plainPassword)
// 正确做法:HTTPS传输 + 服务端哈希
password: plainPassword // 通过HTTPS传输
服务端处理:
- 使用bcrypt、argon2等抗爆破哈希算法
- 每个密码单独加盐处理
- 实现安全的密码策略检查
2. 验证码机制强化
// 服务端验证逻辑
public boolean verifyCaptcha(String uuid, String code, HttpSession session) {
// 检查参数非空
if (StringUtils.isEmpty(uuid) || StringUtils.isEmpty(code)) {
return false;
}
// 从会话中获取验证码信息
CaptchaInfo captchaInfo = (CaptchaInfo) session.getAttribute("captcha_" + uuid);
// 验证码存在性、有效性、一次性检查
if (captchaInfo == null || !captchaInfo.isValid()
|| !captchaInfo.getCode().equalsIgnoreCase(code)) {
return false;
}
// 验证成功后立即失效
session.removeAttribute("captcha_" + uuid);
return true;
}
3. 登录防护策略
@Component
public class LoginSecurityService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public LoginResult attemptLogin(String username, String password,
String captchaUuid, String captchaCode) {
// 1. 验证码校验
if (!verifyCaptcha(captchaUuid, captchaCode)) {
return LoginResult.fail("验证码错误");
}
// 2. 失败次数检查
String failKey = "login_fail:" + username;
Integer failCount = (Integer) redisTemplate.opsForValue().get(failKey);
if (failCount != null && failCount >= 5) {
// 账户锁定30分钟
return LoginResult.fail("账户已锁定,请30分钟后重试");
}
// 3. IP频率限制
String ipKey = "login_ip:" + getClientIP();
Integer ipCount = (Integer) redisTemplate.opsForValue().get(ipKey);
if (ipCount != null && ipCount >= 10) {
// IP限流
return LoginResult.fail("操作过于频繁,请稍后重试");
}
// 4. 实际身份验证
User user = userService.findByUsername(username);
if (user != null && passwordEncoder.matches(password, user.getPassword())) {
// 登录成功,清除失败计数
redisTemplate.delete(failKey);
return LoginResult.success(generateToken(user));
} else {
// 登录失败,记录失败次数
incrementFailureCount(failKey);
incrementIPCount(ipKey);
return LoginResult.fail("用户名或密码错误");
}
}
}
4. 动态密钥方案(如必须前端加密)
// 前端获取临时密钥
async function getTemporaryKey() {
const response = await fetch('/api/get-encryption-key');
const data = await response.json();
// 密钥有时效性(如5分钟)
return {
key: data.key,
expires: data.expires
};
}
// 服务端密钥管理
@RestController
public class KeyController {
@GetMapping("/api/get-encryption-key")
public EncryptionKey getKey(HttpSession session) {
String keyId = UUID.randomUUID().toString();
String temporaryKey = generateRandomKey(); // 生成临时密钥
// 存储密钥与会话关联
redisTemplate.opsForValue().set("key_" + keyId, temporaryKey, 5, TimeUnit.MINUTES);
return new EncryptionKey(keyId, temporaryKey, System.currentTimeMillis() + 300000);
}
}
五、安全开发规范
5.1 前端安全规范
- 避免敏感信息硬编码:密钥、API凭证等必须从服务端动态获取
- 代码混淆保护:对关键业务逻辑进行代码混淆
- 最小化暴露原则:前端只处理必要的展示逻辑,业务逻辑交由服务端
5.2 服务端安全规范
- 输入验证:对所有输入参数进行严格验证
- 安全传输:强制使用HTTPS协议
- 防护机制:实现完善的频率限制、账户锁定等机制
- 日志监控:记录所有登录尝试,实时监控异常行为
5.3 加密算法使用规范
- 算法选择:使用经过验证的标准算法
- 参数配置:正确配置加密模式、填充方式等参数
- 密钥管理:实现安全的密钥生成、存储、轮换机制
六、总结
本案例揭示了"前端自欺式加密"的安全陷阱,强调了安全不是简单堆砌加密算法,而是构建端到端的可信链路。开发者必须牢记:客户端的一切皆可被控制,唯有服务端才是可信边界。
通过本次分析,我们不仅了解了具体的攻击技术,更重要的是建立了完整的安全防护体系思维,为开发安全的认证系统提供了实践指导。
注:本教学文档基于真实安全评估案例,文中涉及的技术细节仅用于安全教育目的,在实际渗透测试中必须获得相关授权。