從Java的角度談下文件下載漏洞的產生,然后到他的修復方案。這里我的修復方案是白名單,而沒有采用黑名單的方式。
首先先看一段存在文件下載漏洞的代碼code:
HTML視圖頁面 download.html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> <h1>使用a標簽直接指向服務器上的資源</h1> <a href="/WEB14/download/a.flv">a.flv</a><br> <a href="/WEB14/download/a.jpg">a.jpg</a><br> <a href="/WEB14/download/a.mp3">a.mp3</a><br> <a href="/WEB14/download/a.mp4">a.mp4</a><br> <a href="/WEB14/download/a.txt">a.txt</a><br> <a href="/WEB14/download/a.zip">a.zip</a><br> <h1>使用服務器端編碼的方式實現文件下載</h1> <a href="/WEB14/downloadServlet2?filename=a.flv">a.flv</a><br> <a href="/WEB14/downloadServlet2?filename=a.jpg">a.jpg</a><br> <a href="/WEB14/downloadServlet2?filename=a.mp3">a.mp3</a><br> <a href="/WEB14/downloadServlet2?filename=a.mp4">a.mp4</a><br> <a href="/WEB14/downloadServlet2?filename=a.txt">a.txt</a><br> <a href="/WEB14/downloadServlet2?filename=a.zip">a.zip</a><br> <a href="/WEB14/downloadServlet2?filename=美女.jpg">美女.jpg</a><br> </body> </html>
服務器端驗證文件下載代碼:
downloadServlet2.java代碼如下:
package cn.downloadServlet; import java.io.*; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Base64; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.ws.RespectBinding; import com.sun.org.apache.xerces.internal.util.SynchronizedSymbolTable; import com.sun.xml.internal.bind.v2.runtime.output.StAXExStreamWriterOutput; import sun.misc.BASE64Encoder; public class DownloadServlet2 extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 請求filename參數 String filename = request.getParameter("filename"); // System.out.println(filename); // 解決獲得中文參數的亂碼 filename = new String(filename.getBytes("ISO8859-1"), "UTF-8"); // 獲得請求頭中的User-Agent String agent = request.getHeader("User-Agent"); // 根據不同瀏覽器進行不同的編碼 String filenameEncoder = ""; if (agent.contains("MSIE")) { // IE瀏覽器 filenameEncoder = URLEncoder.encode(filename, "utf-8"); filenameEncoder = filenameEncoder.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐瀏覽器 BASE64Encoder base64Encoder = new BASE64Encoder(); filenameEncoder = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes("utf-8")) + "?="; } else { // 其它瀏覽器 filenameEncoder = URLEncoder.encode(filename, "utf-8"); } String requestURI = request.getRequestURI(); System.out.println(requestURI); // 自動判斷文件的數據類型 response.setContentType(this.getServletContext().getMimeType(filename)); // 告訴客戶端該文件不是直接解析 而是以附件形式打開(下載) response.setHeader("Content-Disposition", "attachment;filename=" + filenameEncoder); //獲取請求的文件路徑名稱 String realPath = this.getServletContext().getRealPath("/download/" + filename); ServletOutputStream outputStream = response.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(realPath); int length = 0; byte[] buff = new byte[1024]; while ((length = fileInputStream.read(buff)) != -1) { outputStream.write(buff, 0, length); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
這段代碼是是存在漏洞的,這里獲取filename參數的時候未進行驗證,導致可以任意文件下載。
正常的圖片顯示:
進行繞過下載顯示文件內容:
未深入利用,這里的修復方案有兩種1.黑名單驗證修復 2.白名單驗證修復
這里我選擇2的修復方案。
談修復方案之前先講解下何為黑名單,何為黑名單!
黑名單就是決定阻止的內容,比如我想要過濾一些特殊的字符,如果我的列表中存在這些特殊字符就攔截他們,這種屬於黑名單
白名單就是決定放行的內容,比如我允許你輸入a-z,如果用戶輸入的內容范圍不在a-z以內,我就認為你的行為是非法的,這種屬於白名單。
各自的優缺點:白名單比黑名單更安全,白名單不利於用戶體驗,為了用戶體驗大多數企業還是選擇黑名單,黑名單容易被繞過!
這里的修復思路:允許用戶下載的文件在我的某個列表中,如果用戶下載的內容不在我的文件列表中,那么我認為你的行為是非法的!
具體修復代碼如下:
首先如果需要下載的文件不存在那么將會302跳轉到ErrorInfo.html頁面,代碼如下:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Insert title here</title> </head> <body> 未找到文件!請重試... </body> </html>
java部分文件下載實現代碼:
package cn.downloadServlet; import java.io.*; import java.io.IOException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Base64; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.xml.ws.RespectBinding; import com.sun.org.apache.xerces.internal.util.SynchronizedSymbolTable; import com.sun.xml.internal.bind.v2.runtime.output.StAXExStreamWriterOutput; import sun.misc.BASE64Encoder; public class DownloadServlet2 extends HttpServlet { static String[] arr =null; //獲取文件夾下所有文件名稱 public static void getFileName() { String path = "D:\\JavaWeb\\.metadata\\.plugins\\org.eclipse.wst.server.core\\tmp2\\wtpwebapps\\WEB14\\download"; File f = new File(path); ArrayList<String> arrayList=new ArrayList<>(); if (!f.exists()) { System.out.println(path + " not exists"); return; } File fa[] = f.listFiles(); for (int i = 0; i < fa.length; i++) { File fs = fa[i]; if (fs.isDirectory()) { arrayList.add(fs.getName()); } else { arrayList.add(fs.getName()); } } arr= new String[arrayList.size()]; for (int i = 0; i <arr.length; i++) { arr[i]=arrayList.get(i); } } public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 請求filename參數 String filename = request.getParameter("filename"); // System.out.println(filename); // 解決獲得中文參數的亂碼 filename = new String(filename.getBytes("ISO8859-1"), "UTF-8"); // 獲得請求頭中的User-Agent String agent = request.getHeader("User-Agent"); // 根據不同瀏覽器進行不同的編碼 String filenameEncoder = ""; if (agent.contains("MSIE")) { // IE瀏覽器 filenameEncoder = URLEncoder.encode(filename, "utf-8"); filenameEncoder = filenameEncoder.replace("+", " "); } else if (agent.contains("Firefox")) { // 火狐瀏覽器 BASE64Encoder base64Encoder = new BASE64Encoder(); filenameEncoder = "=?utf-8?B?" + base64Encoder.encode(filename.getBytes ("utf-8")) + "?="; } else { // 其它瀏覽器 filenameEncoder = URLEncoder.encode(filename, "utf-8"); } getFileName(); String requestURI = request.getRequestURI(); System.out.println(requestURI); // 自動判斷文件的數據類型 response.setContentType(this.getServletContext().getMimeType(filename)); // 告訴客戶端該文件不是直接解析 而是以附件形式打開(下載) response.setHeader("Content-Disposition", "attachment;filename=" + filenameEncoder); //獲取請求的文件路徑名稱 String realPath = this.getServletContext().getRealPath("/download/" + filename); boolean key = false; //函數調用 getFileName(); String arrs=null; for (int i = 0; i < arr.length; i++) { arrs=arr[i]; //文件判斷 if (arrs.equals(filename)) { key = true; } } if (key == true) { ServletOutputStream outputStream = response.getOutputStream(); FileInputStream fileInputStream = new FileInputStream(realPath); int length = 0; byte[] buff = new byte[1024]; while ((length = fileInputStream.read(buff)) != -1) { outputStream.write(buff, 0, length); } } else { response.sendRedirect("/WEB14/ErrorInfo.html"); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
我們再次實驗下是否存在文件下載漏洞了。
正常的圖片下載:
嘗試讀取其他文件內容:
說明我們利用這種方法是可以有效防止文件下載漏洞的!
不忘初心,方得始終。