浅谈 Springboot 中的文件上传

sugar2021-02-03 17:08:08

引言

在JavaWeb应用中,任意文件上传一直是关注的重点,攻击者通过上传恶意jsp文件,可以获取服务器权限。但是在Springboot框架对JSP解析存在一定的限制。

Spring官方原文如下,大概意思是jsp对内嵌的容器的支持不太友好,推荐使用thymeleaf这类的模版引擎进行渲染。

图片

那么针对Springboot应用,即使存在任意文件上传缺陷,按照传统的思路直接上传jsp文件,也是无法达到理想的效果的。

下面通过查看其具体的实现方式来看看有没有相关的利用思路,同时在日常项目开发中应该注意些什么。

Springboot文件上传的实现

首先看看在Springboot中如何实现文件上传功能,在网上找了个教程,Controller的代码如下,Spring会自动解析multipart/form-data请求,将multipart中的对象封装到MultipartRequest对象中:

@RequestMapping(value={"/uploadFile"},method={RequestMethod.POST})
public String uploadFile(MultipartFile file,String type,HttpServletResponse
response) throws
Exception{
        String UPLOADED_FOLDER="/resource/upload/";
        if(!file.isEmpty()){
                String
path = UPLOADED_FOLDER + file.getOriginalFilename();
                File
targetFile = new
File(path);
                FileUtils.inputStreamToFile(file.getInputStream(),targetFile);
                        ......
                        ......
}
}

大致是通过getOriginalFilename()方法获取文件名,然后使用File对象创建对应的文件。接下来看看Springboot是如何解析multipart请求并封装OriinalFilename的。

SpringBoot在

MultipartAutoConfiguration自动装配了MultipartResolver来对multipart请求进行解析:

@Configuration
@ConditionalOnClass({ Servlet.class,StandardServletMultipartResolver.class,
        MultipartConfigElement.class})
@ConditionalOnProperty(prefix \= "spring.http.multipart", name= "enabled", matchIfMissing =true)
@EnableConfigurationProperties(MultipartProperties.class) public classMultipartAutoConfiguration { private final MultipartPropertiesmultipartProperties; public MultipartAutoConfiguration(MultipartPropertiesmultipartProperties) { this.multipartProperties =multipartProperties;
    }
    @Bean
    @ConditionalOnMissingBeanpublic MultipartConfigElement multipartConfigElement() { returnthis.multipartProperties.createMultipartConfig();
    }
    @Bean(name \=DispatcherServlet.MULTIPART\_RESOLVER\_BEAN\_NAME)
   @ConditionalOnMissingBean(MultipartResolver.class) publicStandardServletMultipartResolver multipartResolver() {
        StandardServletMultipartResolvermultipartResolver \= new StandardServletMultipartResolver();
       multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());return multipartResolver;
   }
}

可以看到其默认装配的解析器是org.springframework.web.multipart.support。StandardServletMultipartResolver。查看对应的实现。

StandardServletMultipart Resolver的处理方式

对应的spring-web组件版本为5.3.3。使用StandardServletMultipartResolver解析multipart请求的关键过程如下:

关键multipart请求的解析方法parseRequest

private void parseRequest(HttpServletRequestrequest)
  {
    try
    {
      Collection<Part>parts = request.getParts();
      this.multipartParameterNames = newLinkedHashSet(parts.size());
      MultiValueMap<String,MultipartFile> files = new LinkedMultiValueMap(parts.size());
      for (Part part : parts)
      {
        String headerValue =part.getHeader("Content-Disposition");
        ContentDisposition disposition =ContentDisposition.parse(headerValue);
        String filename =disposition.getFilename();
        if (filename != null)
        {
          if((filename.startsWith("=?")) && (filename.endsWith("?="))){
            filename =MimeDelegate.decode(filename);
          }
          files.add(part.getName(), newStandardMultipartFile(part, filename));
        }
        else
        {
         this.multipartParameterNames.add(part.getName());
        }
      }
      setMultipartFiles(files);
    }
    catch (Throwable ex)
    {
      handleParseFailure(ex);
    }
  }

主要的解析方法在

org.springframework.http.ContentDisposition的parse方法,在这里对相关的http内容进行了处理,获取文件名的关键内容如下。

如果传入的multipart请求无法直接使用filename=解析出文件名,Spring还会使用content-disposition解析一次(使用filename*=解析文件名):

public static ContentDisposition parse(StringcontentDisposition)
  {
    ......
    for (int i = 1; i < parts.size();i++)
    {
      String part =(String)parts.get(i);
      int eqIndex =part.indexOf('=');
      if (eqIndex!= -1)
      {
        String attribute =part.substring(0, eqIndex);

        String value =(part.startsWith("\"", eqIndex + 1)) &&(part.endsWith("\"")) ? part.substring(eqIndex + 2,part.length() - 1) : part.substring(eqIndex + 1);
        if(attribute.equals("name"))
        {
          name = value;
       }
        else if(attribute.equals("filename*"))
        {
          int idx1 =value.indexOf('\'');
          int idx2 = value.indexOf('\'',idx1 + 1);
          if ((idx1 != -1) &&(idx2 != -1))
          {
            charset =Charset.forName(value.substring(0, idx1).trim());
           Assert.isTrue((StandardCharsets.UTF_8.equals(charset)) ||(StandardCharsets.ISO_8859_1.equals(charset)), "Charset should be UTF-8 orISO-8859-1");

            filename =decodeFilename(value.substring(idx2 + 1), charset);
          }
          else
          {
            filename =decodeFilename(value, StandardCharsets.US_ASCII);
          }
        }
        else if((attribute.equals("filename")) && (filename ==null))
        {
          filename = value;
        }

这里发现一个点,整个过程没有对类似…/的路径进行检查/过滤,获取文件名后会实例化StandardMultipartFile方便后续程序调用:

private static classStandardMultipartFile
    implements MultipartFile,Serializable
  {
    ......

    public StringgetOriginalFilename()
    {
      return this.filename;
    }
    ......

实例化方法同样也没有对原始上传的filename进行检查/过滤

相关接口可以通过getOriginalFilename()方法获得对应的上传文件名,然后进行文件创建。

未做安全处理的文件上传

由于获取的fileName未进行安全处理,在使用File创建文件时,若路径处path写入…/…/穿越符号,是可以跨目录新建文件的:

File file = new File("path")

浅谈Springboot中的文件上传

那么也就是说即使Springboot对jsp存在一定的支持限制,在特定情况下那么可以尝试上传定时任务进行权限获取。

上传文件名为…/…/…/…/…/…/…/…/…/var/spool/cron/root,结合前面百度到的demo成功反弹shell:

浅谈Springboot中的文件上传

进行了后缀安全检查的文件上传

到这里针对Springboot任意文件上传的缺陷利用已经有一些眉目了。这里发现一个有趣的点。

针对任意文件上传,在进行业务开发的时候,常常会对后缀进行相关的白名单检查,如果上传非法后缀,那么拒绝对应的业务请求,例如如下代码:

if(!file.isEmpty()){
        String Filename =
file.getOriginalFilename();
        String suffix =
originalFilename.substring(Filename.lastIndexOf("."));
        if(!".xlsx".equals(suffix)&&!".xls".equals(suffix)){
                throw
new Exception("非法请求,请导入excel文件");
        }
byte[] bytes =
file.getBytes();
        String path =ULOADED_FOLDER +
Filename;
}

在进行文件上传时进行了后缀检查,如果不是xlsx或者xls后缀的话,拒绝请求。因为/etc/cron.d/目录下的文件可以任意后缀命名,那么此时可以上传文件名为“…/…/…/…/…/…/etc/cron.d/test.xls”绕过对应的安全检查:

浅谈Springboot中的文件上传

浅谈Springboot中的文件上传

上传成功后,本地监听端口等待定时任务执行,成功反弹 shell,获取服务器权限:

浅谈Springboot中的文件上传

其他版本的spring-web组件也是大同小异,例如4版本会通过extractFilename()方法进行multipart请求的处理再封装,同样没有处理目录穿越符的问题:

private String extractFilename(StringcontentDisposition, String key)
  {
    if (contentDisposition == null){
      return null;
    }
    int startIndex =contentDisposition.indexOf(key);
    if (startIndex == -1) {
      return null;
    }
    String filename =contentDisposition.substring(startIndex + key.length());
    if(filename.startsWith("\""))
    {
      int endIndex =filename.indexOf("\"", 1);
      if (endIndex != -1) {
        return filename.substring(1,endIndex);
      }
   }
    else
    {
      int endIndex =filename.indexOf(";");
     if (endIndex != -1) {
        return filename.substring(0,endIndex);
      }
    }
    return filename;
  }

综上,关于Springboot的文件上传可以通过结合目录遍历的方式尝试利用,当然也需要满足一定的利用条件,例如对跨目录具有写权限、未重命名文件名等。

同样的SpringMVC也加载了默认的解析器,一般是CommonsMultipartResolver,查看其对应的处理方式进行对比。

CommonsMultipartResolver的处理方式

解析部分就不细看了,直接查看getOriginalFilename()

https://github.com/spring-projects/spring-... 95-121行

可以看到其针对linux和windows的情况对multipart请求中的原始文件名进行了截断处理,防止了…/…/带来的目录穿越风险:

浅谈Springboot中的文件上传

结语

上述的问题已经报告给了

security@pivotal.io,官方回复如下:

图片

因为某些原因暂不打算在

StandardServletMultipartResolver上做更多的处理了。

建议根据owasp提供的建议,在实现上传业务时进行更多的安全检查。

https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html

原创:tkswifty SecIN技术平台
原文链接:https://mp.weixin.qq.com/s/wPgdnyv57qBwkHV...

文件上传string
本作品采用《CC 协议》,转载必须注明作者和本文链接
无意中看到ch1ng师傅的文章觉得很有趣,不得不感叹师傅太厉害了,但我一看那长篇的函数总觉得会有更骚的东西,所幸还真的有,借此机会就发出来一探究竟,同时也不得不感慨下RFC文档的妙处,当然本文针对的技术也仅仅只是在流量层面上waf的绕过。Pre很神奇对吧,当然这不是终点,接下来我们就来一探究竟。前置这里简单说一下师傅的思路部署与处理上传war的servlet是?
这个标签内 解析xml实体的位置也是如此0x02 lets xxe 1第一个位点就是我们刚才提到的地方我们只需要做的是在xml声明处添加如下第一行代码 在第二行标签进行修改]>
引言 在JavaWeb应用中,任意文件上传一直是关注的重点,攻击者通过上传恶意jsp文件,可以获取服务器权限。但是在Springboot框架对JSP解析存在一定的限制。 Spring官方原文如下,大概意思是jsp对内嵌的容器的支持不太...
在Web系统中,允许用户上传文件作为一个基本功能是必不可少的,如论坛允许用户上传附件,多媒体网站允许用户上传图片,视频网站允许上传头像、视频等。但如果不能正确地认识到上传带来的风险,不加防范,会给整个系统带来毁灭性的灾难。
前言前几天对自己学校进行的一次渗透测试,由于深信服过于变态,而且拦截会直接封ip,整个过程有点曲折期间进行了后缀名绕过,jspx命名空间绕过、获取网站根目录、base64五层编码写入shell等操作0x01 获取网站接口主界面:上传点:由于该应用是内嵌企业微信的套皮Html,所以我们首先用Burp Suite抓包获取接口和cookie任意文件上传:文件名强制命名为code+学号,后缀为最后一次点号出现之后的字母0x02 后缀名绕过代码不限制后缀名,但是waf限制呀!
记一次5000美金的文件上传漏洞挖掘过程大家好,最有趣的功能之一是文件上传文件上传中的漏洞通常会导致您进入关键或高严重性,所以让我们从我在bug bunting时遇到的这个场景开始假设我们的目标域是 target.com在寻找我们的目标时,我遇到了 edu.target.com 子域,该程序提供的服务是一个教学平台,因为有不同类型的用户,如学生和教师,旨在帮助学生学习与技术相关的主题,如软件工程机器人等…
Zoho ManageEngine Admanager Plus 任意文件上传漏洞可GetShell。
前言:渗透测试的时候往往会遇到盲注这类的繁杂的手工测试,所以需要编写半自动化脚本去进行测试减少时间浪费并快速
sugar
暂无描述