一、目录遍历漏洞
1、原理介绍
通过用户输入,后端接收到参数直接拼接到指定路径下读取用户的文件名,看似正常,但是用户输入的参数不可控制,黑客将非法的特殊字符作为文件名的一部分,操作到其他路径下,甚至是跳转到服务器敏感目录,读取敏感的配置文件,例如服务器的密码文件,程序数据库,redis等核心配置文件,因此具有一定的风险;
2、 人工代码审计
关键字
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(
...
类和函数
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
在使用这些函数,关键字,类时,对用户传递来的文件对象/文件名/文件路径,是否进行了正确的处理:
是否限制了可操作文件的路径,文件类型,文件所有者;
是否将敏感文件进行了排除
查找getPath(),getAbsolutePath(),查看是否有错误的路径判断;
在排查程序的安全策略配置文件,全局搜索permission,Java.io.FilePermission,grant的字样,是否给程序的某部分路径赋予了读写权限;
3、问题代码示例
1)文件获取响应文件路径或者创建响应的目录时,对读入的文件路径没有进行过滤与限制,用户可控;
//创建读取 要拷贝的文件
InputStream inStream = new FileInputStream(file1);
//创建 要复制到的文件,filename未经校验
OutputStream inStream = new FileOutputStream(new File(file2+"\\"+filename));
2)直接获取文件名称,未进行校验,创建文件;
String orgName = mf.getOriginalFilename();//获取文件名 然后
File file = new File(orgName);
file.mkdirs();//创建文件根目录
3)路径操作,压缩项覆盖,应用接收恶意zip压缩包,会造成受保护的文件或目录被覆盖等危险;
//开始解压
Enumeration entries = zipFile.entries();
//遍历entries 获得entry
while(entries.hasMoreElements()){
ZipEntry entry = (ZipEntry)entries.nextElement();
...
File targetFile = new File(entry.getName());
...
targetFile.getParentFile().mkdirs();
}
4、渗透测试
路径遍历漏洞一般隐藏在文件读取或者展示图片功能块这样的通过参数提交上来的文件名称;
例如:http://www.test.com/my.jsp?file=abc.html,
攻击者就可以假定my.jsp能够从文件系统中获取文件并构造如下恶意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)目录跳转符可以是.../,也可以是.../的ASCII编码或者unicode编码等,或者~/ .%2E /%2F 空格%20 换行符%0a;
在Unix的系统中也可以使用Url编码的换行符;
例如:.../.../etc/passwd%0a.jpg
如果文件系统在获取含有换行符的文件名,会截断为文件名,也可以尝试%20;
例如:../../../index.jsp%20
2)Java%c0%ae 安全模式绕过漏洞:
Apache Tomcat UTF-8目录遍历漏洞,漏洞CVE编号为CVE-2008-2938,Tomcat处理请求中的编码时存在漏洞,如果在context.xml中将allowLinking设置为true且连接配置为URLEncoding=UTF-8的话,则向Tomcat提交恶意请求就可以通过目录遍历攻击读取服务器上的任意文件,在Java端“%c0%ae”解析为“\uC0AE”,最后转义为ASCII低字符-“-”,通过这个方法可以绕过目录保护读取包配置文件信息,将目录跳转符里的点编码%c0%ae,如果服务器使用的受该漏洞影响的Tomcat版本,则可能攻击成功;
http://www.target.com/%c0%ae%c0%ae/%c0%ae%c0%ae/foo/bar
3)绕过文件类型白名单过滤,可以借助"%00"截断符在进行尝试;
/WeBid/loader.php?js=…/…/…/…/…/WINDOWS/SchedLgU.txt%00.js
[
](https://www.cnblogs.com/macter/p/16181588.html)
5、审计案例1
用户通过form表单输入文件名称,通过源码逐层对文件进行非法字符的过滤与处理,导致任意文件读取(目录遍历),漏洞的产生;
1)正常的用户行为是输入要读取的合法文件
2)黑客利用未过滤文件名或目录的漏洞读取了其他目录下的资源文件,通过遍历该目录或者上级路径下的其他文件,例如
6、审计案例2
1)当输入文件名为../1.txt(上级目录需要存在),不存在的话,我们就一直../../../直到存在为止;
2)提交之后发现成功下载到当前目录外的目录文件
3)源码分析
通过用户输入文件名之后,后端直接接收了文件名拼接到需要读取的路径下,进而读取了文件,未对来自前端用户输入的文件名称做非法过滤;
<!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);
//根据文件路径创建输入流
FileInputStream fis = new FileInputStream(file);
//设置响应头,弹出下载框
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);
}
通过源码分析,获取到文件中含有../,当执行下载逻辑的时候,file类底层将会访问该路径读取资源,如同人工通过浏览器读取文件E:\java_project\FileUpload\src\main\webapp\upload..\1.txt
这里通过debug可以清楚看出来过程;
7、目录遍历+文件上传(ofcms审计)
1)我们在idea使用Ctrl+Shift+R,进行全局搜索关键字filename;
2)发现 TemplateController类的save方法中还存在任意文件上传漏洞;
可以看到文件名,文件内容都是可控的,且对用户输入的文件名没有做../的处理,我们可以往服务器上写任意文件;
3)定位漏洞 后台->模板设置->模板文件->模板目录->修改404.html
就爱注释写得好的程序员!!!!
4)抓包进行文件名修改或者添加../跳转目录在static静态资源目录下写js马子,对文件内容进行修改写入恶意的jsp文件;
5)修改file_content为冰蝎马,这里要进过url编码;
6)修改file_name = …/…/…/static/shell.jsp
7)我們在Tomcat的war中,static目录下发现了我们上传的shell.jsp;
8)尝试连接
成功连接!!!!
二、修复方案以及修复代码
1、 全局过滤关键字;
private String fileNameValidate(String str) {
String fileNameListStr ="../|./|/.."; //这里为请求体中不能携带的关键字
if(null!=fileNameListStr && !"".equals(fileNameListStr))
{
str = str.toLowerCase();// 统一转为小写
log.info("sqlFilter===========================>>路径遍历过滤规则:"+ 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、单个字符串过滤关键字;
public class FlieNamefilter {
//private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");
private static Pattern FilePattern = Pattern.compile("[\\s\\.:?<>|]"); //过滤规则
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、代码修复:
private static Pattern FilePattern = Pattern.compile("[\\\\/:*?\"<>|]");
/**
* 路径遍历 漏洞修复
* @param str
* @return
*/
public static String filenameFilter(String str) {
return str==null?null:FilePattern.matcher(str).replaceAll("");
}
建议在所有涉及到文件名的地方使用fileNameFilter过滤一下;
4、使用严格符合规范的可接受输入白名单
使用正则表达式验证文件路径和文件名,如下:
推荐正则代码案例:写一个过滤函数,白名单形式的,
根据业务需求,只允许包含字母、数字、. 、/、-, 且不允许 . 和 / ,或者 . 和 \ 连在一起,直接完全防止 ../ ..\ 等情况的发生
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;
}
}
对文件路径,后缀进行白名单控制,对包含恶意的符号或者空字节进行拒绝,如下,解析可接受字符的白名单输入:从输入中拒绝路径中不需要的任何字符,它可以被删除或者替换;
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、严格进行输入验证
也就是拒绝任何不严格符合规范的输入,获将其转换为符合要求的输入,即程序对非受信任的用户输入数据净化,对网站用户提交过来的文件名进行硬编码或者统一编码,过滤非法字符,黑名单过滤非法字符包括;
/ \ " : ~ | * ? < > % ../ ..\
正则-代码案例:黑名单
public String filter(String data){
Pattern pattern = Pattern.complie("[\\s\\\\/:\\*\\?\\\"<>\\|]");
Matcher matcher = pattern.matcher(data);
data = matcher.replaceAll("");
return data;
}
正则-代码案例:黑名单
”/ \ " : ~ | * ? < >
如果黑名单过滤“ ..”,攻击者可替换为"...."过滤”..“后,还剩下一个"..",从而实现绕过,黑名单的话,很容易过滤不严格,被各种方式绕过,仍然会有风险,建议尽量采用白名单的形式;
6、合理配置web服务器的目录权限
(禁止目录浏览,分配好目录权限等)部署新的业务系统或者安装新的软件或应用后应通过web扫描工具积极查找系统是否存在目录遍历漏洞,尽可能不要在服务器上安装与业务不相关的第三方软件以避免引入目录遍历漏洞。
除此之外,还应该合理配置web服务器(禁止目录浏览,分配好目录权限等)并积极关注所使用的各种软件和应用的版本发布情况,及时升级新的软件版本。
不同web服务器禁止目录浏览方法有所不同。对IIS而言,如果不需要可执行的CGI,可以删除可执行虚拟目录或直接关闭目录浏览;如果确实需要可执行的虚拟目录,建议将可执行的虚拟目录单独放在一个分区。
对于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
ofcms环境搭建:https://forum.butian.net/share/1229