《Java编码指南:编写安全可靠程序的75条建议》—— 指南5:防止任意文件上传...

    xiaoxiao2023-12-03  153

    本节书摘来异步社区《Java编码指南:编写安全可靠程序的75条建议》一书中的第1章,第1.5节,作者:【美】Fred Long(弗雷德•朗), Dhruv Mohindra(德鲁•莫欣达), Robert C.Seacord(罗伯特 C.西科德), Dean F.Sutherland(迪恩 F.萨瑟兰), David Svoboda(大卫•斯沃博达),更多章节内容可以访问云栖社区“异步社区”公众号查看。

    指南5:防止任意文件上传

    Java应用程序,包括Web应用程序,在接受文件上传的同时,必须确保攻击者不能上传或者传输恶意文件。如果被限制的文件中包含了可以在目标系统上执行的代码,那么应用程序层的防御将会受到威胁。例如,如果一个应用程序允许了HTML文件的上传,那么也就相当于间接允许了恶意代码的执行——攻击者可以提交一个有效的HTML文件,如果该文件包含了跨站脚本(XSS)攻击的代码片段,在程序缺乏输出过滤的情况下这些攻击脚本将会被有效执行。出于这个原因,许多应用程序都对可上传文件的类型做了限制。

    也有可能出现上传的文件具有危险的后缀这种情况,如.exe(可执行文件后缀)和.sh(可执行脚本后缀),这种情况会导致服务器端应用程序执行任意代码。如果一个上传文件的应用程序,只对HTTP报头中的Content-Type(内容类型)字段进行了限制,那么该程序就容易遭受到这种攻击。

    一个典型的支持文件上传的Java Server Page(JSP)页面可能包含以下代码:

    <s:form action="doupload" method="POST"   enctype="multipart/form-data">  <s:file name="uploadFile" label="Choose File" size="40" />  <s:submit value="Upload" name="submit" /> </s:form>``` 许多Java企业级框架都提供了相应的配置设置,旨在防止任意文件被上传。遗憾的是,大多数企业级框架都无法提供足够的保护。为了弥补这个不足,需要检查元数据属性中的文件大小、内容类型和文件内容。 ####违规代码示例 下面的违规代码示例展示了Struts 2应用程序中实现上传功能的XML代码,其中拦截器(interceptor)代码的职责是允许文件上传。

        10240      text/plain,image/JPEG,text/html   负责文件上传的代码在UploadAction类中:

    public class UploadAction extends ActionSupport { private File uploadedFile; // setter and getter for uploadedFile

     public String execute() {  try {   // File path and file name are hardcoded for illustration   File fileToCreate = new File("filepath", "filename");   // Copy temporary file content to this file   FileUtils.copyFile(uploadedFile, fileToCreate);   return "SUCCESS";  } catch (Throwable e) {   addActionError(e.getMessage());   return "ERROR";  } }}`参数maximumSize确保了指定的Action不会接收到一个非常大的文件。参数allowedTypes定义了可以被接受的文件类型。然而,这种方法不能完全确保上传的文件符合安全要求,因为拦截器检查可以被轻易地绕过。如果攻击者使用一个代理工具在网络传输过程中改变原始HTTP请求的内容类型,那么该框架将无法防止文件的上传。因此,攻击者可以上传一个恶意文件,如扩展名为.exe的可执行文件。

    合规解决方案

    只有当文件的内容类型严格匹配文件的实际内容时,文件才能成功上传。例如,一个具有image头信息的文件,只能包含一个图像,不能包含可执行代码。下面的这个合规解决方案,使用Apache Tika库[Apache 2013],通过现有的解析器库检测和提取文档中的元数据信息和结构化文本内容。在调用负责上传文件的execute()方法之前,必须先调用checkMetaData()方法。

    public class UploadAction extends ActionSupport {  private File uploadedFile;  // setter and getter for uploadedFile  public String execute() {   try {    // File path and file name are hardcoded for illustration    File fileToCreate = new File("filepath", "filename");    boolean textPlain = checkMetaData(uploadedFile,     "text/plain");    boolean img = checkMetaData(uploadedFile, "image/JPEG");    boolean textHtml = checkMetaData(uploadedFile,     "text/html");    if (!textPlain || !img || !textHtml) {     return "ERROR";    }    // Copy temporary file content to this file    FileUtils.copyFile(uploadedFile, fileToCreate);    return "SUCCESS";   } catch (Throwable e) {    addActionError(e.getMessage());    return "ERROR";   }  }  public static boolean checkMetaData(   File f, String getContentType) {   try (InputStream is = new FileInputStream(f)) {    ContentHandler contenthandler = new BodyContentHandler();    Metadata metadata = new Metadata();    metadata.set(Metadata.RESOURCE_NAME_KEY, f.getName());    Parser parser = new AutoDetectParser();    try {     parser.parse(is, contenthandler,          metadata, new ParseContext());    } catch (SAXException | TikaException e) {     // Handle error     return false;    }    if (metadata.get(Metadata.CONTENT_TYPE).equalsIgnoreCase(       getContentType)) {     return true;    } else {     return false;    }   } catch (IOException e) {    // Handle error    return false;   }  } }``` AutoDetectParser会基于需要解析的文件的内容类型,选择最佳的可用解析器。 ###适用性 相关资源:java 实现文件MD5 加密比较,防止上传重复文件
    最新回复(0)