路径操作(Path Manipulation)的漏洞,简言之就是在路径中包含有一些特殊字符(… 或者 / 等),导致可以访问到期望目录之外的文件。
比如路径地址是`/usr/local/myapp/../../../etc/passwd`,对应到访问到的文件就是`/etc/passwd`, 而这个文件是系统的文件,保存用户密码。
使用Coverity 扫描的信息如下:
```
Filesystem path, filename, or URI manipulation
An attacker may access, modify, or corrupt files that contain sensitive information or are critical to the application.
```
关于路径操作漏洞,可以参考:
[软件弱点预防之 —— Filesystem path, filename, or URI manipulation - 操控文件系统路径、文件名或 URI](https://blog.csdn.net/oscar999/article/details/127293940)
本篇介绍在 Java应用中如何防御路径操作(Path Manipulation)的攻击。
## 防御方法分析
在代码层面来看, 防御路径操作的方法就是对输入进行验证,根据对字符是否合法的角度来看, 可以分为两种:
1. 黑名单 : 将不安全的字符列入黑名单,
2. 白名单 : 将预期的字符加入白名单。 或者更严格一点,创建一份合法资源名的列表,并且规定用户只能选择其中的文件名。
黑名单的方式实现起来较为简单, 将一些不安全的字符进行转义或者过滤, 比如 `..` 上一级目录字符 和绝对路径目录, 但是这种方式的安全性可能不是很高, 可能会遗漏一些特殊字符。
白名单的安全性较高, 但是需要穷举所有的合法路径相对比较困难, 如果某个路径不包含在里面, 则可能会导致应用的功能不能正常工作。
本篇介绍使用白名单的方式进行防御, 也就是哪些路径是有效的路径。
## 路径操作的漏洞扫描
路径操作的弱点很容易被Coverity等静态扫描工具扫描出来, 比如下面的代码就是存在路径操纵漏洞的:
```
@RequestMapping("/unsafe")
public void unsafe(String filefullName, HttpServletRequest request) {
new File(filefullName);
}
```
要规避静态扫描其实很容易,只需要对路径使用函数转换一下, 类似下面:
```
@RequestMapping("/scanSafe")
public void scanSafe(String filefullName, HttpServletRequest request) {
new File(cleanFilePath(filefullName));
}
public static String cleanFilePath(String filePath) {
if (filePath != null) {
char[] originalChars = filePath.toCharArray();
char[] chars = new char[originalChars.length];
for (int i = 0; i < originalChars.length; i++) {
chars[i] = originalChars[i];
}
return new String(chars);
} else {
return null;
}
}
```
上面的cleanFilePath 其实没有对路径做任何改动, 也就是说这个函数没有任何作用, 但是这样的改动却是可以骗过Coverity, Coverity会认为 `new File(cleanFilePath(filefullName));` 这个代码是安全的, 当其实这个代码还是存在风险的。
以调用的cleanFilePath() 函数的效果来看, 路径完全没变化。
```
@Test
public void cleanFilePath() {
String fileFullName = "C:\\temp\\..\\Windows\\system.ini";
Assertions.assertTrue("C:\\temp\\..\\Windows\\system.ini".equals(cleanFilePath(fileFullName)));
}
```
但是网络上还是有很多煞有介事的代码, 对路径进行转化:
比如:
#### 错误解法1
```
public static String cleanString(String aString) {
if (aString == null)
return null;
String cleanString = "";
for (int i = 0; i < aString.length(); ++i) {
cleanString += cleanChar(aString.charAt(i));
}
return cleanString;
}
private static char cleanChar(char aChar) {
// 0 - 9
for (int i = 48; i < 58; ++i) {
if (aChar == i)
return (char) i;
}
// 'A' - 'Z'
for (int i = 65; i < 91; ++i) {
if (aChar == i)
return (char) i;
}
// 'a' - 'z'
for (int i = 97; i < 123; ++i) {
if (aChar == i)
return (char) i;
}
// other valid characters
switch (aChar) {
case '/':
return '/';
case '.':
return '.';
case '-':
return '-';
case '_':
return '_';
case ' ':
return ' ';
}
return '%';
}
```
上面的代码试图将路径中的不合法的字符替换为 %, 仅认为字母、数字以及`/ . -` 等字符是有效字符。 这个转化不仅没有解决路径操作的风险,而且路径转换也是错的,比如调用示例:
```
@Test
public void cleanString() {
String fileFullName = "C:\\temp\\..\\Windows\\system.ini";
fileFullName = cleanString(fileFullName);
System.out.println(fileFullName); //转化后的结果 C%%temp%..%Windows%system.ini
}
```
#### 错误解法2
还有的解法似乎考虑周全, 将中文字符也考虑进来了, 但是同样是漏洞没解决, 功能反而是错的。
```
public static String normalizeFilePath(String filePath) {
if (filePath != null) {
char[] originalChars = filePath.toCharArray();
char[] chars = new char[originalChars.length];
for (int i = 0; i < originalChars.length; i++) {
if (isValidPathChar(originalChars[i])) {
chars[i] = originalChars[i];
} else {
chars[i] = '-';
}
}
return new String(chars);
} else {
return null;
}
}
public static boolean isValidPathChar(char c) {
boolean isValid = false;
String whiteListpathChars = "abcdefghigklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/_-:\\.";
if (whiteListpathChars.indexOf(c) > 0) {
isValid = true;
}
return isValid;
}
public static boolean isChineseChar(char c) {
Character.UnicodeBlock ub = Character.UnicodeBlock.of(c);
return ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A
|| ub == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B
|| ub == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION
|| ub == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS
|| ub == Character.UnicodeBlock.GENERAL_PUNCTUATION;
}
```
## 正确姿势
比较简易的正确方式是先对路径进行规范化处理, 再判断路径是否在合法的路径列表中。
所谓路径归一化就是将路径中的 `..` 和 `.` 进行处理, 比如
```
Path path = Paths.get("D:\\temp\\subfold1\\..\\..\\subsubfolder1");
path = path.normalize();
Assert.assertEquals("D:\\subsubfolder1", path.toString());
```
基于Java 的路径类Path 对路径进行归一化, 之后在对归一化之后的路径进行白名单的判断, 完整的示例代码如下:
```
@RequestMapping("/safeRightWay")
public void safeRightWay(String filefullName, HttpServletRequest request) {
Path path = Paths.get(filefullName);
path = path.normalize();
String filePath = path.getParent().toString();
if(isValidPath(filePath)) {
File file = path.toFile();
}
}
public boolean isValidPath(String filePath) {
List<String> list = new ArrayList<String>();
list.add("D:\\temp1");
list.add("D:\\temp2");
return list.contains(filePath);
}
```
如果感觉Path 使用繁琐的话,也可以导入 apache common io 进行归一化处理,
首先导入依赖包:
```
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-io</artifactId>
<version>1.3.2</version>
</dependency>
```
导入之后就可以使用 FilenameUtils.normalize(fileFullName);进行归一化处理了,类似:
```
@Test
public void normalize() {
String fileFullName = "C:\\temp\\..\\Windows\\system.ini";
fileFullName = FilenameUtils.normalize(fileFullName);
System.out.println(fileFullName); // C:\Windows\system.ini
}
```
## 安全的解法
需要注意的是上面使用 Path只是对路径进行规范化处理,并没有进行路径操纵的防御,所以使用Coverity 进行扫码时, `Path path = Paths.get(filefullName);` 这一句还是会被扫描到存在风险。
```
@RequestMapping("/safeRightWayButScan")
public void safeRightWayButScan(String filefullName, HttpServletRequest request) {
Path path = Paths.get(filefullName);
path = path.normalize();
String filePath = path.getParent().toString();
if(isValidPath(filePath)) {
path.toFile();
}
}
```
所以, 安全的解法是结合 FilenameUtils.normalize(fileFullName) 进行处理, 代码如下:
```
@RequestMapping("/safeRightWay")
public void safeRightWay(String filefullName, HttpServletRequest request) {
filefullName = FilenameUtils.normalize(filefullName);
if(isValidPath(filefullName)) {
new File(filefullName);
}
}
```
注意: 这里只是解决了风险, 但是使用Coverity 扫描时依旧还是扫出了风险,使用Coverity扫描的状况下, 如何完美处理Path Manipulation风险,请参考:
[结合Coverity扫描Spring Boot项目进行Path Manipulation漏洞修复](https://blog.csdn.net/oscar999/article/details/128961641)
*****
*****
- 空白目录
- 基本知识
- 数据与指令
- 漏洞扫描
- Coverity Scan
- 常见漏洞与处理措施
- Presentation Topic 1
- Preface
- 常见的软件缺陷与风险
- 安全漏洞相关概念(CVE,CNA, CWE,CVSS,OWASP)
- Web 安全漏洞发生的原因
- XSS(跨站脚本)攻击与预防
- CSRF-跨站点请求伪造
- SQL Inject
- 软件弱点预防之 —— Filesystem path, filename, or URI manipulation - 操控文件系统路径、文件名或 URI
- Improper Limitation of a Pathname to a Restricted Directory
- 点击劫持漏洞 Clickjacking
- Java Web安全风险应对
- Concludes
- SQL注入风险与防范措施
- SQL注入类型
- Sample
- XSS - 跨站脚本攻击
- 宽字节编码引发的XSS血案
- HTML与JavaScript 的自解码
- XSS Sample
- XSS 攻击载荷
- Cross-Site Scripting: 跨站脚本攻击 XSS
- XSS 与 CSRF
- 参考
- 解决示例
- CSRF-跨站请求伪造
- 基于Servlet 的Java Web项目的CSRF防御概念
- 基于JSP的Java Web项目的CSRF防御示例
- CSRF-跨站点请求伪造
- CSRF(跨站请求伪造)漏洞及解决方法
- Java Web应用CSRF防御攻略
- Spring Boot 项目使用Spring Security防护CSRF攻击实战
- 一次基于Coverity 扫描Spring Boot项目的CSRF弱点解决的探索之旅
- Coverity + CSRF
- Spring Boot CSRF攻击防御
- 文件上传漏洞
- 敏感信息泄露
- Filesystem path, filename, or URI manipulation -
- PATH_MANIPULATION+Coverity
- 防御方法
- Java漏洞及修复
- Java高风险弱点
- Java 防御XSS攻击实战与示例代码
- Java防御路径操作(Path Manipulation) 的正确姿势
- 示例
- 示例2
- Java之路径操纵解决的误区
- 结合Coverity扫描Spring Boot项目进行Path Manipulation漏洞修复
- Spring Boot实战项目之CSRF防御处理
- Java高风险弱点与修复之——SQL injection(SQL注入)
- Very weak password hashing (WEAK_PASSWORD_HASH)
- Insecure SSL/TLS: bad HostnameVerifier (BAD_CERT_VERIFICATION)
- 主机验证示例1
- Resource Leak
- Java中风险弱点
- Java代码弱点与修复之——Arguments in wrong order(参数顺序错误)
- Java代码弱点与修复之——ORM persistence error(对象关系映射持久错误)
- Java代码弱点与修复之——Logically dead code-逻辑死代码
- 示例1
- Java代码弱点与修复之——URL manipulation(URL操纵)
- Java代码弱点与修复之——Open redirect(开放式重定向)
- Spring项目Open Redirect漏洞解决
- Java代码弱点与修复之——Dereference null return value(间接引用空返回值)
- Java代码弱点与修复之——Dereference before null check 非空检查前间接引用
- Java代码弱点与修复之——Dereference after null check-空检查后间接引用
- Java代码弱点与修复之——Explicit null dereferenced(显式空间接引用)
- Java非空判断相关的弱点类型汇总与比较
- Java代码弱点与修复之——Copy-paste error(复制粘贴错误)
- Java代码弱点与修复之——Suspicious calls to generic collection methods
- Java代码弱点与修复之——Repeated conditional test(重复的条件测试)
- Java代码弱点与修复之——Masked Field(掩码字段)
- Spring Boot项目之伪Masked Field弱点解决
- Java代码弱点与修复之——STCAL: Static use of type Calendar or DateFormat
- Java代码弱点与修复之——RC: Questionable use of reference equality rather than calling equals
- Java代码弱点与修复之——Unintended regular expression(非期望的正则表达式)
- Java代码弱点与修复之——LI: Unsynchronized Lazy Initialization
- Java代码弱点与修复之——Risky cryptographic hashing function (RISKY_CRYPTO)
- 加密散列示例
- Java代码弱点与修复之——INT: Suspicious integer expression
- NP: Null pointer dereference
- SA: Useless self-operation
- Unguarded read
- SWL: Sleep with lock held
- Use of freed resources
- Stray semicolon
- UG: Unsynchronized get method, synchronized set method (FB.UG_SYNC_SET_UNSYNC_GET)
- Identical code for different branches
- RANGE: Range checks
- Infinite Loop
- Missing authorization check
- Java低风险弱点
- Java代码弱点与修复之——WMI: Inefficient Map Iterator(低效的Map迭代器)
- Java代码弱点与修复之——Dead local store(本地变量存储了闲置不用的对象)
- Java代码弱点与修复之——BC: Bad casts of object references(错误的强制类型转换)
- Java代码弱点与修复之——'Constant' variable guards dead code
- Java代码弱点与修复之——DE: Dropped or ignored exception(无视或忽略异常)
- Useless code - 无用的代码
- Dm: Dubious method used
- 字节转换
- Java代码弱点与修复之——Se: Incorrect definition of Serializable class(可序列化类的定义不正确)
- FS: Format string problem
- IM: Questionable integer math
- Information exposure to log file
- Insecure HTTP firewall
- NS: Suspicious use of non-short-circuit boolean operator
- REC: RuntimeException capture
- Resource leak on an exceptional path
- RV: Bad use of return value
- SBSC: String concatenation in loop using + operator
- SIC: Inner class could be made static
- SS: Unread field should be static
- UC: Useless code
- Unnecessary call to org.hibernate.Session.get method
- Unused value
- UPM: Private method is never called
- UrF: Unread field
- UuF: Unused field
- UwF: Unwritten field
- Audit
- Non-constant SQL
- Log injection (LOG_INJECTION)
- 日志漏洞示例1
- 实际场景
- URL
- 模板
- Web漏洞及修复
- Web开发
- 客户端请求地址
- Medium
- [Web缺陷与修复之]Property access or function call before check for null or undefined
- Bad use of null-like value
- Missing break in switch
- Logically dead code-JavaScript
- Identical code for different branches
- Expression with no effect
- Missing parentheses
- High
- Web之DOM-based cross-site scripting漏洞处理
- Summary
- Web基本知识
- 字符转义
- 工具
- Java静态分析工具之——SpogBugs
- FindBugs
- Synopsys Code Sight
- 使用Eclipse +SpotBugs 检测代码弱点