用友 NC 任意文件读取漏洞分析
字数 1632 2025-11-21 12:32:11
用友NC任意文件读取漏洞分析教学文档
一、漏洞概述
1.1 漏洞简介
用友NC系统的CourseWareAction接口存在任意文件读取漏洞,攻击者可通过构造特定的fileName参数,直接读取服务器上的任意文件,导致敏感信息泄露。
1.2 漏洞危害等级
- 危害程度:高危
- 影响范围:可读取服务器任意文件,包括配置文件、敏感数据等
二、影响版本
2.1 确认受影响版本
- 用友NC 6.5版本
2.2 可能受影响版本
- 其他未打补丁的用友NC版本可能存在类似问题
三、漏洞原理深度分析
3.1 漏洞位置定位
漏洞位于hrss包目录下的CourseWareAction接口的download方法中。
3.2 关键漏洞代码分析
@Action
public void download() {
String fileName = this.request.getParameter("fileName");
fileName = StringUtil.convertToCorrectEncoding(fileName);
// 漏洞核心代码:未对fileName进行安全过滤
String strSrcDir = CodeGenUtils.buildFileURL(RuntimeEnv.getInstance().getNCHome(),
new String[]{"ware", fileName});
OutputStream out = null;
BufferedInputStream bufferedInputStream = null;
try {
this.request.setCharacterEncoding("UTF-8");
File file = new File(strSrcDir);
FileInputStream fileInput = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(fileInput);
byte[] pngBytes = new byte[bufferedInputStream.available()];
bufferedInputStream.read(pngBytes);
this.response.reset();
this.response.setCharacterEncoding("UTF-8");
this.response.setContentType("APPLICATION/OCTET-STREAM");
this.response.setHeader("Content-disposition",
"attachment;filename=" + new String(fileName.getBytes("gb2312"), "ISO8859-1"));
out = new BufferedOutputStream(this.response.getOutputStream());
out.write(pngBytes);
out.flush();
} catch (Exception var11) {
Exception e = var11;
throw new LfwRuntimeException("指定位置找不到课件附件,下载失败!", e);
} finally {
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(bufferedInputStream);
}
}
3.3 漏洞成因分析
3.3.1 路径构造缺陷
String strSrcDir = CodeGenUtils.buildFileURL(RuntimeEnv.getInstance().getNCHome(),
new String[]{"ware", fileName});
- 直接使用用户输入的fileName参数构建文件路径
- 未对路径遍历字符(../)进行过滤
- 未对文件路径进行合法性校验
3.3.2 文件读取流程
- 获取用户输入的fileName参数
- 构建完整文件路径:NCHome + "ware" + fileName
- 直接创建File对象并读取文件内容
- 通过HTTP响应输出文件内容
3.4 安全缺陷点
- 缺乏输入验证:未对fileName参数进行有效性检查
- 路径遍历漏洞:允许使用../进行目录遍历
- 权限控制缺失:未验证用户是否有权访问请求的文件
- 错误信息泄露:异常信息可能暴露系统路径信息
四、漏洞利用方法
4.1 利用条件
- 目标系统存在漏洞版本
- 攻击者能够访问目标系统的Web服务
4.2 攻击Payload构造
4.2.1 基础利用方式
GET /portal/pt/downCourseWare/download?fileName=../webapps/nc_web/WEB-INF/web.xml&pageId=login HTTP/1.1
Host: target_ip
4.2.2 可读取的关键文件
- 配置文件:../webapps/nc_web/WEB-INF/web.xml
- 数据库配置:../webapps/nc_web/WEB-INF/classes/数据库配置文件
- 系统文件:../../../../etc/passwd(Linux系统)
- 日志文件:../logs/系统日志文件
4.3 利用步骤
- 识别目标系统为用友NC
- 构造包含路径遍历字符的fileName参数
- 发送HTTP请求到漏洞接口
- 获取服务器返回的文件内容
五、漏洞检测方法
5.1 手工检测
使用curl或浏览器插件发送测试请求:
curl "http://target_ip/portal/pt/downCourseWare/download?fileName=../webapps/nc_web/WEB-INF/web.xml&pageId=login"
5.2 自动化检测
编写检测脚本,特征判断:
- 响应内容包含XML声明或配置文件内容
- 响应头包含文件下载标识
- 文件存在时的正常响应与文件不存在时的错误响应差异
5.3 资产发现
使用FOFA进行资产测绘:
app="用友-UFIDA-NC"
六、漏洞修复方案
6.1 临时修复措施
6.1.1 输入验证加固
public void download() {
String fileName = this.request.getParameter("fileName");
// 添加输入验证
if (fileName == null || fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
throw new SecurityException("非法文件名");
}
// 限制文件类型
if (!fileName.endsWith(".pdf") && !fileName.endsWith(".doc") && !fileName.endsWith(".ppt")) {
throw new SecurityException("不支持的文件类型");
}
// 继续原有逻辑...
}
6.1.2 路径规范化检查
// 规范化路径并检查是否在允许目录内
Path basePath = Paths.get(RuntimeEnv.getInstance().getNCHome(), "ware");
Path requestedPath = basePath.resolve(fileName).normalize();
if (!requestedPath.startsWith(basePath)) {
throw new SecurityException("路径遍历攻击检测");
}
6.2 官方修复方案
- 安装用友官方发布的最新安全补丁
- 升级到不受影响的版本
- 遵循最小权限原则配置系统权限
6.3 安全加固建议
6.3.1 应用层防护
- 对所有用户输入进行严格验证
- 实施白名单机制限制可访问的文件
- 添加身份认证和权限验证
6.3.2 系统层防护
- 使用专用用户运行应用,限制其文件系统访问权限
- 定期更新系统和应用补丁
- 部署WAF防护路径遍历攻击
七、漏洞防范最佳实践
7.1 安全开发规范
- 输入验证原则:所有用户输入都视为不可信的
- 最小权限原则:应用程序以最小必要权限运行
- 防御性编程:假设所有外部输入都可能是恶意的
7.2 代码审查要点
- 检查所有文件操作相关的用户输入处理
- 验证路径解析和规范化逻辑
- 确认权限检查机制的完整性
7.3 安全测试重点
- 路径遍历漏洞测试
- 文件权限绕过测试
- 输入验证机制测试
八、总结
用友NC任意文件读取漏洞是一个典型的安全编码问题,根源在于对用户输入缺乏充分的验证和过滤。通过本次分析,应重点掌握:
- 路径遍历漏洞的原理和利用方式
- 安全编码中输入验证的重要性
- 多层防御策略的实施方法
- 漏洞修复和防护的最佳实践
此漏洞案例为安全开发和代码审计提供了重要的参考价值,强调了在文件操作等敏感功能中实施严格安全控制的重要性。