Java代码审计之任意文件下载

  • A+

文件下载原理

  通过超链接下载文件时,如果浏览器可以识别该文件格式(txt、jpg等),浏览器就会直接打开。只有浏览器不能识别该文件格式的时候,才会实现下载。否则前端页面通过超链接方式发起文件下载请求,把要下载的文件名传递给后台,在对应的后台接口实现文件的下载操作(设置响应类型及响应头,输出流写入文件内容
  以下是通过Servlet程序实现下载的过程:
  1.通过File对象进行文件的封装,然后通过输入流InputStream将文件中的数据读取到java程序中;
  2.通过setContentTpye方法设置Content-Type字段的值,设置为application/octet-stream或application/x-msdownload,决定客户端服务器以哪种方式来接受返回的信息;
application/octet-stream:文件拓展名.*(二进制流)
application/x-msdownload:文件拓展名.dll(告诉浏览器这是一个要保存到本地的下载文件)

  3. 通过HttpServletResponse.setHeader方法设置Content-Disposition的值为"attachment:filename=文件名",浏览器通过附件的形式来获取到用户上传的文件;
  4. 读取下载文件,通过OutputStream方法使用输出流将刚刚封装在输入流的文件内容向客户端写入。

定位文件下载业务:

常见业务关键字

  • download
  • fileName
  • filePath
  • write
  • getFile
  • getPath
  • getWriter
  • ......

相关操作类

  • InputStream
  • File
  • OutputStreaam
  • BufferedInputStream
  • FileInputStream

  除了上述方法以外,还可以直接定位源代码中的工具类,为了降低耦合,文件操作相关的代码都会通过封装成一个工具类,不通的需求会通过重载或者重写进行实现。
  例如以下关键字:
* FileUtil
* IOUtil

审计要点

  通过相关方法定位到关键字后,一般检查点如下:
* 路径相关参数是否用户可控
* 是否限定了可下载的文件目录范围
* 是否配置了全局过滤器或者额外进行了路径处理(例如过滤“../”和“/”等)(如果使用了过滤器,需要检查数据获取方式、规则、顺序等)

相关案例

  相关代码如下:
java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DownloadServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
//定义文件下载路径
String path =getServletContext().getRealPath("/")+"image/";
//获取要下载的文件名称
String filename = request.getParameter("filename");
File file = new File(path+filename);
if(file.exists()) {
//实现文件的下载
//设置响应类型application/octet-stream
response.setContentType("application/x-msdownload");
//设置头信息(以附件形式打开)
response.setHeader("Content-Disposition", "attachment;filename=""+filename+""");
//读取要下载的文件
InputStream inputStream = new FileInputStream(file);
ServletOutputStream outputStream = response.getOutputStream();
byte b[] =new byte[1024];
int n;
while((n = inputStream.read(b))!=-1) {
outputStream.write(b,0,n);
}
//关闭流,释放资源
outputStream.close();
inputStream.close();
}else {
request.setAttribute("errorResult", "文件不存在下载失败!");
//重定向
RequestDispatcher dispatcher = request.getRequestDispatcher("jsp/Filedownload.jsp");
dispatcher.forward(request, response);
}
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}

  首先从客户端获取filename,然后拼接路径封装到File对象中:
java
String filename = request.getParameter("filename");
File file = new File(path+filename);

  然后将File对象转换成字节流,通过OutputStream输出流输出到客户端中。整个过程没有对目录穿越符号../或者下载的路径进行处理限制。假设传入的filename参数为../../../../../../../../../../etc/passwd即可穿越路径达到任意文件下载的效果。

具体修复

  • 对用户输入的数据进行过滤,过滤掉"./"、"../"、"%"、"/"
  • 对文件类型进行白名单控制,可以限定允许下载的文件后缀
  • 文件路径保存至数据库,让用户提交文件名对应ID进行文件下载(注意SQL注入问题)
    java
    // 获取要下载的文件名称
    String filename = SqlQuery.PathURL(request.getParameter("id"));//通过id从数据库获取文件的值
  • 公开文件可使用超链接方式放置在web应用程序下载目录中通过链接直接下载即可
  • 使用file.getCanonicalPath()判断文件的真实路径,仅允许下载规定路径下的文件.对于getCanonicalPath()函数,“.”就表示当前的文件夹,而”..“则表示当前文件夹的上一级文件夹,函数处理返回的就是将符号完全解析的完整路径。

相关推荐: 如何优雅的在Linux上使用Powershell]

译文声明 本文是翻译文章,文章原作者 TJ Null,文章来源:https://www.offensive-security.com 原文地址:https://www.offensive-security.com/offsec/kali-linux-power…