一、目錄遍歷漏洞
1、原理介紹
通過用戶輸入,后端接收到參數(shù)直接拼接到指定路徑下讀取用戶的文件名,看似正常,但是用戶輸入的參數(shù)不可控制,黑客將非法的特殊字符作為文件名的一部分,操作到其他路徑下,甚至是跳轉(zhuǎn)到服務(wù)器敏感目錄,讀取敏感的配置文件,例如服務(wù)器的密碼文件,程序數(shù)據(jù)庫,redis等核心配置文件,因此具有一定的風(fēng)險;
2、 人工代碼審計
關(guān)鍵字
1 )new FileInputStream( path
2 )new FileOutputStream( path
3 )new File( path
4 )RandomAccessFile fp = new RandomAccessFile(fname,"r");
5)mkdirs
6 )getOriginalFilename
7 )entry.getName(
...
類和函數(shù)
1)sun.nio.ch.FileChannelImpl
2)java.io.File.list/listFiles
3)java.io.FileInputStream
4)java.io.FileOutputStream
5)java.io.FileSystem/Win32FileSystem/WinNTFileSystem/UnixFileSystem
6)sun.nio.fs.UnixFileSystemProvider/WindowsFileSystemProvider
7)java.io.RandomAccessFile
8)sun.nio.fs.CopyFile
9)sun.nio.fs.UnixChannelFactory
10)sun.nio.fs.WindowsChannelFactory
11)java.nio.channels.AsynchronousFileChannel
12)FileUtil/IOUtil
13)filePath/download/deleteFile/move/getFile
在使用這些函數(shù),關(guān)鍵字,類時,對用戶傳遞來的文件對象/文件名/文件路徑,是否進(jìn)行了正確的處理:
- 是否限制了可操作文件的路徑,文件類型,文件所有者;
- 是否將敏感文件進(jìn)行了排除
- 查找getPath(),getAbsolutePath(),查看是否有錯誤的路徑判斷;
- 在排查程序的安全策略配置文件,全局搜索permission,Java.io.FilePermission,grant的字樣,是否給程序的某部分路徑賦予了讀寫權(quán)限;
3、問題代碼示例
1)文件獲取響應(yīng)文件路徑或者創(chuàng)建響應(yīng)的目錄時,對讀入的文件路徑?jīng)]有進(jìn)行過濾與限制,用戶可控;
//創(chuàng)建讀取 要拷貝的文件
InputStream inStream = new FileInputStream(file1);
//創(chuàng)建 要復(fù)制到的文件,filename未經(jīng)校驗
OutputStream inStream = new FileOutputStream(new File(file2+"\\"+filename));
2)直接獲取文件名稱,未進(jìn)行校驗,創(chuàng)建文件;
String orgName = mf.getOriginalFilename();//獲取文件名 然后
File file = new File(orgName);
file.mkdirs();//創(chuàng)建文件根目錄
3)路徑操作,壓縮項覆蓋,應(yīng)用接收惡意zip壓縮包,會造成受保護(hù)的文件或目錄被覆蓋等危險;
//開始解壓
Enumeration entries = zipFile.entries();
//遍歷entries 獲得entry
while(entries.hasMoreElements()){
ZipEntry entry = (ZipEntry)entries.nextElement();
...
File targetFile = new File(entry.getName());
...
targetFile.getParentFile().mkdirs();
}
4、滲透測試
路徑遍歷漏洞一般隱藏在文件讀取或者展示圖片功能塊這樣的通過參數(shù)提交上來的文件名稱;
例如:http://www.test.com/my.jsp?file=abc.html,
攻擊者就可以假定my.jsp能夠從文件系統(tǒng)中獲取文件并構(gòu)造如下惡意URL:
http://www.test.com/my.jsp?file=.../.../Windows/system.ini
http://www.test.com/my.jsp?file=%2e./...%2fWindows/system.ini
http://192.168.32.163/view.php?page=%20../../../../../../../../../../etc/passwd
1)目錄跳轉(zhuǎn)符可以是.../,也可以是.../的ASCII編碼或者unicode編碼等,或者~/ .%2E /%2F 空格%20 換行符%0a;
在Unix的系統(tǒng)中也可以使用Url編碼的換行符;
例如:.../.../etc/passwd%0a.jpg
如果文件系統(tǒng)在獲取含有換行符的文件名,會截斷為文件名,也可以嘗試%20;
例如:../../../index.jsp%20
2)Java%c0%ae 安全模式繞過漏洞:
Apache Tomcat UTF-8目錄遍歷漏洞,漏洞CVE編號為CVE-2008-2938,Tomcat處理請求中的編碼時存在漏洞,如果在context.xml中將allowLinking設(shè)置為true且連接配置為URLEncoding=UTF-8的話,則向Tomcat提交惡意請求就可以通過目錄遍歷攻擊讀取服務(wù)器上的任意文件,在Java端“%c0%ae”解析為“\uC0AE”,最后轉(zhuǎn)義為ASCII低字符-“-”,通過這個方法可以繞過目錄保護(hù)讀取包配置文件信息,將目錄跳轉(zhuǎn)符里的點編碼%c0%ae,如果服務(wù)器使用的受該漏洞影響的Tomcat版本,則可能攻擊成功;
http://www.target.com/%c0%ae%c0%ae/%c0%ae%c0%ae/foo/bar
3)繞過文件類型白名單過濾,可以借助"%00"截斷符在進(jìn)行嘗試;
/WeBid/loader.php?js=…/…/…/…/…/WINDOWS/SchedLgU.txt%00.js
[
](https://www.cnblogs.com/macter/p/16181588.html)
5、審計案例1
用戶通過form表單輸入文件名稱,通過源碼逐層對文件進(jìn)行非法字符的過濾與處理,導(dǎo)致任意文件讀取(目錄遍歷),漏洞的產(chǎn)生;
1)正常的用戶行為是輸入要讀取的合法文件

2)黑客利用未過濾文件名或目錄的漏洞讀取了其他目錄下的資源文件,通過遍歷該目錄或者上級路徑下的其他文件,例如

6、審計案例2
1)當(dāng)輸入文件名為../1.txt(上級目錄需要存在),不存在的話,我們就一直../../../直到存在為止;

2)提交之后發(fā)現(xiàn)成功下載到當(dāng)前目錄外的目錄文件

3)源碼分析
通過用戶輸入文件名之后,后端直接接收了文件名拼接到需要讀取的路徑下,進(jìn)而讀取了文件,未對來自前端用戶輸入的文件名稱做非法過濾;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="/file1/directroyTranersal02" method="get">
<input type="text" name="filename"/>
<input type="submit" value="uploaddown"/>
</form>
</body>
</html>
//目錄遍歷2
@GetMapping("/directroyTranersal02")
public void directoryTraversal02(HttpServletRequest request, HttpServletResponse response) throws IOException {
//獲取項目部署絕對路徑下的upload文件夾路徑,下載upload目錄下面的文件
String root = request.getServletContext().getRealPath("/upload");
//獲取文件名
String filename = request.getParameter("filename");
File file = new File(root + "/" + filename);
//根據(jù)文件路徑創(chuàng)建輸入流
FileInputStream fis = new FileInputStream(file);
//設(shè)置響應(yīng)頭,彈出下載框
response.addHeader("Content-Disposition", "attachment;filename=" + new String(filename.getBytes()));
response.addHeader("Content-Length", "" + file.length());
byte[] b = new byte[fis.available()];
fis.read(b);
response.getOutputStream().write(b);
}
通過源碼分析,獲取到文件中含有../,當(dāng)執(zhí)行下載邏輯的時候,file類底層將會訪問該路徑讀取資源,如同人工通過瀏覽器讀取文件E:\java_project\FileUpload\src\main\webapp\upload..\1.txt

這里通過debug可以清楚看出來過程;
7、目錄遍歷+文件上傳(ofcms審計)
1)我們在idea使用Ctrl+Shift+R,進(jìn)行全局搜索關(guān)鍵字filename;

2)發(fā)現(xiàn) TemplateController類的save方法中還存在任意文件上傳漏洞;

可以看到文件名,文件內(nèi)容都是可控的,且對用戶輸入的文件名沒有做../的處理,我們可以往服務(wù)器上寫任意文件;
3)定位漏洞 后臺->模板設(shè)置->模板文件->模板目錄->修改404.html

就愛注釋寫得好的程序員!?。?!

4)抓包進(jìn)行文件名修改或者添加../跳轉(zhuǎn)目錄在static靜態(tài)資源目錄下寫js馬子,對文件內(nèi)容進(jìn)行修改寫入惡意的jsp文件;

5)修改file_content為冰蝎馬,這里要進(jìn)過url編碼;

6)修改file_name = …/…/…/static/shell.jsp

7)我們在Tomcat的war中,static目錄下發(fā)現(xiàn)了我們上傳的shell.jsp;

8)嘗試連接

成功連接?。。?!
二、修復(fù)方案以及修復(fù)代碼
1、 全局過濾關(guān)鍵字;
private String fileNameValidate(String str) {
String fileNameListStr ="../|./|/.."; //這里為請求體中不能攜帶的關(guān)鍵字
if(null!=fileNameListStr && !"".equals(fileNameListStr))
{
str = str.toLowerCase();// 統(tǒng)一轉(zhuǎn)為小寫
log.info("sqlFilter===========================>>路徑遍歷過濾規(guī)則:"+ fileNameListStr);
String[] badStrs = fileNameListStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
return badStrs[i];
}
}
}
return null;
}
2、單個字符串過濾關(guān)鍵字;
public class FlieNamefilter {
//private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");
private static Pattern FilePattern = Pattern.compile("[\\s\\.:?<>|]"); //過濾規(guī)則
public static String filenameFilter(String str) {
return str==null?null:FilePattern.matcher(str).replaceAll("");
}
public static void main(String[] args) {
String str="home/.. <>|logs/../:edata?";
//String filenameFilter = filenameFilter(str);
String filenameFilter = fileNameValidate(str);
System.out.println(filenameFilter);
}
}
private static String fileNameValidate(String str) {
String strInjectListStr ="../|./|/..| |<|>|:|?";
if(null!=strInjectListStr && !"".equals(strInjectListStr))
{
str = str.toLowerCase();
String[] badStrs = strInjectListStr.split("\\|");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
str= str.replace(badStrs[i], "");
}
}
}
return str;
}
3、代碼修復(fù):
private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");
/**
* 路徑遍歷 漏洞修復(fù)
* @param str
* @return
*/
public static String filenameFilter(String str) {
return str==null?null:FilePattern.matcher(str).replaceAll("");
}
建議在所有涉及到文件名的地方使用fileNameFilter過濾一下;
4、使用嚴(yán)格符合規(guī)范的可接受輸入白名單
- 使用正則表達(dá)式驗證文件路徑和文件名,如下:
- 推薦正則代碼案例:寫一個過濾函數(shù),白名單形式的,
- 根據(jù)業(yè)務(wù)需求,只允許包含字母、數(shù)字、. 、/、-, 且不允許 . 和 / ,或者 . 和 \ 連在一起,直接完全防止 ../ ..\ 等情況的發(fā)生
public class CommUtils{
private static final String patternString = "^[a-zA-Z\\d-/.]+$";
private static final String patternString1 = "./|/.";
public static String filePathFilter(String filepath){
final String[] split = patternString1 .split("\\|");
for(String s : split){
filepath = filepath.replace(s,"");
}
if(filepath.matches(patternString)){
return filepath;
}
return null;
}
}
- 對文件路徑,后綴進(jìn)行白名單控制,對包含惡意的符號或者空字節(jié)進(jìn)行拒絕,如下,解析可接受字符的白名單輸入:從輸入中拒絕路徑中不需要的任何字符,它可以被刪除或者替換;
1 public class CleanPath {
2
3 public static String cleanString(String aString) {
4 if (aString == null) return null;
5 String cleanString = "";
6 for (int i = 0; i < aString.length(); ++i) {
7 cleanString += cleanChar(aString.charAt(i));
8 }
9 return cleanString;
10 }
11
12 private static char cleanChar(char aChar) {
13
14 // 0 ‐ 9
15 for (int i = 48; i < 58; ++i) {
16 if (aChar == i) return (char) i;
17 }
1
19 // 'A' ‐ 'Z'
20 for (int i = 65; i < 91; ++i) {
21 if (aChar == i) return (char) i;
22 }
23
24 // 'a' ‐ 'z'
25 for (int i = 97; i < 123; ++i) {
26 if (aChar == i) return (char) i;
27 }
28
29 // other valid characters
30 switch (aChar) {
31 case '/':
32 return '/';
33 case '.':
34 return '.';
35 case '‐':
36 return '‐';
37 case '_':
38 return '_';
39 case ' ':
40 return ' ';
41 }
42 return '%';
43 }
44 }
2 public static void main(String[] args) {
3 File file=new File(args[0]);
4 if (!isInSecureDir(file)) {
5 throw new IllegalArgumentException();
6 }
7 String canonicalPath = file.getCanonicalPath();
8 if (!canonicalPath.equals("/img/java/file1.txt") &&
9 !canonicalPath.equals("/img/java/file2.txt")) {
10 // Invalid file; handle error
11 }
12
13 FileInputStream fis = new FileInputStream(f);
14 } }
5、嚴(yán)格進(jìn)行輸入驗證
- 也就是拒絕任何不嚴(yán)格符合規(guī)范的輸入,獲將其轉(zhuǎn)換為符合要求的輸入,即程序?qū)Ψ鞘苄湃蔚挠脩糨斎霐?shù)據(jù)凈化,對網(wǎng)站用戶提交過來的文件名進(jìn)行硬編碼或者統(tǒng)一編碼,過濾非法字符,黑名單過濾非法字符包括;
/ \ " : ~ | * ? < > % ../ ..\
public String filter(String data){
Pattern pattern = Pattern.complie("[\\s\\\\/:\\*\\?\\\"<>\\|]");
Matcher matcher = pattern.matcher(data);
data = matcher.replaceAll("");
return data;
}
如果黑名單過濾“ ..”,攻擊者可替換為"...."過濾”..“后,還剩下一個"..",從而實現(xiàn)繞過,黑名單的話,很容易過濾不嚴(yán)格,被各種方式繞過,仍然會有風(fēng)險,建議盡量采用白名單的形式;
6、合理配置web服務(wù)器的目錄權(quán)限
(禁止目錄瀏覽,分配好目錄權(quán)限等)部署新的業(yè)務(wù)系統(tǒng)或者安裝新的軟件或應(yīng)用后應(yīng)通過web掃描工具積極查找系統(tǒng)是否存在目錄遍歷漏洞,盡可能不要在服務(wù)器上安裝與業(yè)務(wù)不相關(guān)的第三方軟件以避免引入目錄遍歷漏洞。
除此之外,還應(yīng)該合理配置web服務(wù)器(禁止目錄瀏覽,分配好目錄權(quán)限等)并積極關(guān)注所使用的各種軟件和應(yīng)用的版本發(fā)布情況,及時升級新的軟件版本。
不同web服務(wù)器禁止目錄瀏覽方法有所不同。對IIS而言,如果不需要可執(zhí)行的CGI,可以刪除可執(zhí)行虛擬目錄或直接關(guān)閉目錄瀏覽;如果確實需要可執(zhí)行的虛擬目錄,建議將可執(zhí)行的虛擬目錄單獨放在一個分區(qū)。
對于Apache而言,管理員需要修改配置文件,禁止瀏覽列出目錄和文件列表,如可通過修改conf目錄下的httpd.conf文件來禁止使用目錄索引。以Apache 2.2.25版本為例,打開httpd.conf文件將“Options Indexes FollowSymLinks”中的“Indexes”刪除,這樣web目錄下的所有目錄都不再生成索引。
三、參考鏈接:
https://blog.csdn.net/qq_41085151/article/details/113525348
https://www.cnblogs.com/zhangruifeng/p/16077916.html
https://www.cnblogs.com/jayus/p/11423769.html
https://www.cnblogs.com/macter/p/16181588.html#8-%E4%BB%BB%E6%84%8F%E6%96%87%E4%BB%B6%E8%AF%BB%E5%86%99%E5%88%A0%E9%99%A4%E5%A4%8D%E5%88%B6%E7%A7%BB%E5%8A%A8%E9%81%8D%E5%8E%86
ofcms環(huán)境搭建:https://forum.butian.net/share/1229