未經許可,不得轉載。
文章目錄
- 文件操作漏洞
- 文件讀取漏洞
- 基于 InputStream 的讀取
- 基于 FileReader 的讀取
- 文件下載漏洞
- 文件刪除漏洞
- 防范
文件操作漏洞
分為文件讀取漏洞、文件下載漏洞與文件刪除漏洞。
文件讀取漏洞
在Java中,文件讀取通常有兩種常見方式:一種是基于InputStream
,另一種是基于FileReader
。
漏洞成因:未對用戶輸入做過濾,導致讀取敏感文件并返回至客戶端。
以下兩種代碼形式都存在路徑遍歷問題。
基于 InputStream 的讀取
String filename = request.getParameter("filename"); // 假設這是用戶輸入的文件名
File file = new File(filename); // 創建文件對象,未進行任何路徑驗證
InputStream inputStream = new FileInputStream(file); // 創建輸入流
int len;
while (-1 != (len = inputStream.read())) { // 循環讀取文件內容outputStream.write(len); // 將讀取的字節寫入輸出流
}
基于 FileReader 的讀取
String filename = request.getParameter("filename"); // 假設這是用戶輸入的文件名
String fileContent = ""; // 存儲文件內容
FileReader fileReader = new FileReader(filename); // 創建FileReader對象,未進行任何路徑驗證
BufferedReader bufferedReader = new BufferedReader(fileReader); // 包裝為BufferedReader
String line;
while (null != (line = bufferedReader.readLine())) { // 逐行讀取文件fileContent += (line + "\n"); // 拼接每一行內容
}
文件下載漏洞
漏洞成因:未對用戶輸入做過濾,導致用戶端可下載敏感文件。
filename 參數未經過任何驗證或過濾,攻擊者可以通過構造惡意路徑下載系統敏感文件。
String filename = request.getParameter("filename"); // 獲取文件名
File file = new File(filename); // 創建文件對象
response.reset(); // 重置響應
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes("utf-8"))); // 設置下載文件名
response.addHeader("Content-Length", "" + file.length()); // 設置文件大小
response.setContentType("application/octet-stream; charset=utf-8"); // 設置響應內容類型為二進制流InputStream inputStream = new FileInputStream(file); // 創建輸入流讀取文件
OutputStream outputStream = new BufferedOutputStream(response.getOutputStream()); // 獲取輸出流
int len;
while (-1 != (len = inputStream.read())) { // 讀取文件并寫入響應outputStream.write(len);
}
inputStream.close(); // 關閉輸入流
outputStream.close(); // 關閉輸出流
文件刪除漏洞
漏洞成因:未對用戶輸入做過濾,導致敏感文件被刪除。
filename 參數未經過任何驗證或過濾,攻擊者可以通過構造惡意路徑刪除系統關鍵文件。
String filename = request.getParameter("filename"); // 獲取文件名
File file = new File(filename); // 創建文件對象
if (file != null && file.exists() && file.delete()) { // 檢查文件存在并刪除response.getWriter().println("Delete success"); // 刪除成功
} else {response.getWriter().println("Delete failed"); // 刪除失敗
}
防范
1、使用 getCanonicalPath() 方法獲取文件的規范路徑,并與預期的基路徑進行比較,確保文件路徑在允許的范圍內。
String basePath = "/allowed/directory/"; // 允許訪問的基路徑
File file = new File(basePath, filename); // 拼接文件路徑
if (!file.getCanonicalPath().startsWith(new File(basePath).getCanonicalPath())) {throw new SecurityException("非法文件路徑"); // 路徑不在允許范圍內,拋出異常
}
對以下語句進行解讀:
if (!file.getCanonicalPath().startsWith(new File(basePath).getCanonicalPath())) {
file.getCanonicalPath():這個方法返回文件的規范化路徑,即將路徑中的 .(當前目錄)和 …(上級目錄)等符號解析成實際的絕對路徑。
1、假設:basePath = “/allowed/directory/”
2、用戶輸入 filename = “…/…/etc/passwd”
3、new File(basePath):創建 /allowed/directory/ 的 File 對象。
4、file = new File(basePath, filename):用戶提供的路徑是 …/…/etc/passwd,所以拼接后的 file 實際路徑為 /allowed/directory/…/…/etc/passwd,這是一個潛在的路徑遍歷。
5、file.getCanonicalPath():獲取文件的規范化路徑,經過解析后變為 /etc/passwd(因為路徑 …/…/ 會使路徑跳轉到根目錄)。
6、new File(basePath).getCanonicalPath():獲取 /allowed/directory/ 的規范化路徑,即 /allowed/directory/。
7、在這種情況下,file.getCanonicalPath()(即 /etc/passwd)不會以 /allowed/directory/ 開頭,因此會進入 if 語句塊,阻止訪問該文件。
其次是檢查文件后綴是否在允許的后綴白名單中,舉個例子:
// 定義允許的文件后綴白名單
Set<String> allowedExtensions = new HashSet<>(Arrays.asList("txt", "pdf", "jpg", "png", "doc", "xls"
));String filename = request.getParameter("filename"); // 獲取文件名
String fileExtension = filename.substring(filename.lastIndexOf(".") + 1).toLowerCase(); // 提取后綴并轉為小寫// 檢查后綴是否在白名單中
if (!allowedExtensions.contains(fileExtension)) {throw new SecurityException("非法文件類型"); // 文件類型不在白名單中,拋出異常
}File file = new File(filename); // 創建文件對象// 使用 try-with-resources 讀取文件
try (InputStream inputStream = new FileInputStream(file);OutputStream outputStream = new BufferedOutputStream(response.getOutputStream())) {int len;while (-1 != (len = inputStream.read())) {outputStream.write(len);}
} catch (IOException e) {e.printStackTrace();
}