-
Notifications
You must be signed in to change notification settings - Fork 31
任意文件上传
任意文件上传漏洞是一种Web应用程序安全漏洞,攻击者可以利用此漏洞将任意文件上传到服务器上,从而实现攻击目的。攻击者通常可以上传包含恶意代码的Web Shell、病毒、木马程序等恶意文件,通过这些文件进行远程控制、信息窃取、篡改网站内容、网站挂马等攻击行为。 任意文件上传漏洞通常是由于Web应用程序的开发人员没有对上传的文件类型和文件大小进行充分的验证和过滤所致。攻击者可以通过修改上传的文件类型、伪造上传的文件头等方式绕过验证,上传任意类型和大小的文件。一旦攻击者上传了恶意文件,他们就可以在服务器上执行任意的命令,并获得系统权限,这将给Web应用程序带来严重的安全威胁。
考察:绕过前端校验方式
我们准备了一个JSP文件,名字叫做PrintTime.jsp
:
<%@ page session="false" %>Now time is: <%=new java.util.Date()%>
使用该文件上传提示我们文件不符合要求。
如果正常上传png图片是可以的。
此时我们直接修改数据包中的png为jsp发现可以上传成功。
我们先进行手动劫持,然后正常在页面中点击上传图片。
然后在拦截到的修改数据包中的文件名和文件内容,点击提交数据。
此时返回了JSP文件的访问地址,点击访问后成功打印了时间。
考察:绕过MIME TYPE校验
MIME TYPE其实就是上传文件数据包内的Content-Type。
直接上传JSP文件当然是不行的。
我们只需要修改Content-Type为普通图片的类型可以了。
考察:文件后缀黑名单校验
文件后缀的检测一般使用白名单,如果存在黑名单的情况就有可能会被绕过。
一般常见的思路是通过尝试上传多种可以被解析的后缀,但遗憾的是目前默认只解析jsp和jspx。
任意修改后缀发现后缀a
,能够上传成功,说明是黑名单拦截。
如在Windows环境下运行,可以使用空文件名的方式,为文件名添加一个点PrintTime.jsp.
,这样后端校验时因为没有获取到黑名单里的后缀名放行。
此时我们再去访问/upload/PrintTime.jsp
发现写入成功。
其他情况可以使用大小写绕过,但需要注意的是,除了jsp和jspx,其他后缀文件都不会被解析成servlet。
考察:制作路径穿越压缩包上传
在任意目录下创建文件夹test
,在文件夹内创建PrintTime.jsp
,内容为:
<%@ page session="false" %>Now time is: <%=new java.util.Date()%>
将该文件打包压缩成PrintTime.zip
上传后返回了访问地址,但我们访问发现被禁止了。你也可以尝试路径校验绕过。
根据之前发现,upload 目录是可以被访问的。我们希望文件名携带..\upload\
。统计..\upload\
一个为10个字符,我们将PrintTime
替换为aaaaaaaaaaa
共11个字符。
使用notepad++打开aaaaaaaaaaa.zip
将文件名的前10位修改成..\upload\
,保存。
重新上传压缩包,发现文件名已经改成我们想要的了。
点击后能够正确访问,路径是upload
,说明我们成功通过这种方式路径穿越上传了恶意文件。
通常我们上传文件会特别关注以下几个方面:
- 文件类型校验:根据文件后缀名或者文件头(magic number)判断文件类型,只接受安全的文件类型(白名单),如图片、PDF、文本等,拒绝危险的文件类型,如可执行文件等。
- 文件大小校验:限制文件大小,避免上传过大的文件。
- 文件名校验:防止上传包含危险字符的文件名,如 ../ 等。对文件进行重命名。
- 文件内容校验:对于上传的文件,可以对其内容进行检查,如通过杀毒软件进行检查,避免上传带有病毒的文件。
除此之外还有其他防范的操作。
- 文件下载时不提供文件名,只提供文件随机生成的ID。
- 全站不解析JSP、JSPX等可以解析对象的文件。
- 上传文件目录低权限、网站运行权限低权限等。
题外话,如果不校验文件内容,那么必须考虑文件包含漏洞存在的情况。
在开发时,我列举了前端检测后缀、后端检测MIME Type、后端检测后缀黑名单的情况。Tomcat10版本已经不使用ServletFileUpload
而是使用request.getPart
即可根据 name 获取文件。
代码来源:com/pika/electricrat/unsafeupload/dto/UploadServlet.java
// 后端检测MIME Type
@Api({RequestMethodType.POST})
public Map<?, ?> imageMIME(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part file = request.getPart("image_file");
for(String i : FileServerImpl.IMAGE_FILE_TYPE){
if (file.getContentType().equals("image/"+i)){
return uploadFile(file, uploadPath(request));
}
}
HashMap<String, Object> data= new HashMap<>();
data.put("uploadStatus", false);
return data;
}
// 后端检测后缀黑名单
@Api({RequestMethodType.POST})
public Map<?, ?> imageBlackList(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
Part file = request.getPart("image_file");
String fileName = file.getSubmittedFileName();
// String suffixName = fileName.substring(fileName.lastIndexOf(".")).toLowerCase();
String suffixName = fileName.substring(fileName.lastIndexOf("."));
System.out.println(suffixName);
for(String i : FileServerImpl.BLACK_FILE_TYPE) {
if (suffixName.equals(i)){
HashMap<String, Object> data= new HashMap<>();
data.put("uploadStatus", false);
return data;
}
}
return uploadFile(file, uploadPath(request));
}
// 上传文件
private HashMap<String, Object> uploadFile(Part imageFile,String filePath){
HashMap<String, Object> data= new HashMap<>();
try {
String fileName = imageFile.getSubmittedFileName();
long fileSize = imageFile.getSize();
if(fileSize > FileServerImpl.MAX_FILE_SIZE){data.put("uploadStatus", false);return data;}
String fileType = imageFile.getContentType();
File file = new File(filePath);
if (!file.exists() && !file.isDirectory()){
file.mkdir();
}
imageFile.write(filePath+"\\"+fileName);
HashMap<String, Object> fileObject = fsi.uploadFile(new FileEntity(fileName, fileType, (filePath+"\\"+fileName),
System.currentTimeMillis(), fileSize, (new ImageVerificationCode()).GetRandom(8)));
if (fileObject.isEmpty()){
data.put("uploadStatus", false);
return data;
}
data.put("file", fileObject);
data.put("uploadStatus", true);
} catch (Exception e){
data.put("uploadStatus", false);
data.put("msg", e.getMessage());
}
return data;
}
后端检测MIME Type,可以抓包轻松修改。后端检测后缀黑名单出问题的是忽略了大小写。
String suffixName = fileName.substring(fileName.lastIndexOf("."));
这句话本身是获取后缀,但对比时没有考虑到大小写。从下方的BLACK_FILE_TYPE
可以看出,我们只需要修改后缀为.jSp
就能绕过。
代码来源:com/pika/electricrat/unsafeupload/bo/Impl/FileServerImpl.java
public static final String[] IMAGE_FILE_TYPE = {"png", "jpg", "gif"};
public static final String[] BLACK_FILE_TYPE = {".html", ".htm", ".phtml", ".jsp", ".jspa", ".jspx", ".jsw", ".jsv", ".jspf", ".jtml"};
还有一种经典的文件上传后缀黑名单检测不严格造成的任意文件上传。它的代码和上面的很相似,只是将lastIndexOf
换成了IndexOf
String suffixName = fileName.substring(fileName.IndexOf("."));
也就是说我们只需要将后缀改成.jpg.jsp
即可绕过黑名单的检测。