路徑操作(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 檢測代碼弱點