记一次任意文件下载绕过过程

  • A+
所属分类:安全文章

挖掘过程

  一般来说,在文件下载/查看功能处,当文件名参数可控,且系统未对参数进行过滤或者过滤不全时,就可以实现下载服务器上的任何文件,产生任意文件下载漏洞。
  一般可以通过相关的业务关键字(例如download、filename等)或者相关的操作类(例如InputStream、File等)快速定位相关的模块进行审计挖掘。
  前段时间审计某项目时发现一处任意文件下载的绕过。
  系统基于Springboot进行开发,首先是发现任意文件下载接口:
```java
@Value("${CERTIFICATE.PATH}")
private String uploadPath;

@GetMapping("/downloadFile.do")
public void uploadFileDownload(HttpServletResponse response,String fileName) throws IOException {
ServletOutputStream outputStream =response.getOutputStream();
String contentType ="application/octet-stream";
response.setContentType(contentType);
String filePath = uploadPath+fileName;
System.out.println(filePath);
outputStream.write(FileUtils.readFileToByteArray(filePath));
outputStream.flush();
outputStream.close();
}
  相关的接口是下载用户上传的凭证材料的,整个下载过程从配置文件中获取uploadPath,然后与用户输入的fileName进行拼接,最后调用工具类FileUtils的readFileToByteArray()方法进行下载。
  查看工具类readFileToByteArray()方法的具体实现:
java
public static byte[] readFileToByteArray(String path) {
StringBuffer buf = new StringBuffer();
try {
File file = new File(path);
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), "utf-8"));
String row;
while ((row = br.readLine()) != null) {
buf.append(row);
}
} catch (IOException e) {
e.printStackTrace();
}
if (buf.length() == 0) {
buf.append("");
}
return buf.toString().getBytes();
}
```
  可以看到相关的工具类方法直接将传入的地址进行文件内容读取,封装后进行返回,整个文件下载的过程是没有过滤掉"./"、"../"、"/"等特殊字符,防止用户进行目录回溯的,同时也没有限制下载目录。初步判断是存在任意文件下载风险的。
  这里尝试去测试环境访问对应的下载接口进行复现,发现失败了:

图片.png
  回去查看源代码,发现有一个安全过滤器SecurityFilter,里面针对相关的敏感字符进行了过滤处理。上述任意文件下载接口无法利用很大原因是过滤器Filter的防护导致的,下面来看看能不能绕过过滤器进行利用。

绕过分析

  一般来说审计过滤器缺陷主要分以下几个点:
* 获取数据的方式是否覆盖全面
* 过滤的规则内容
* 过滤器的顺序

  首先是定位过滤器了,这里是通过注解的方式进行过滤器注册的:
```java
@WebFilter(filterName = "SecurityFilter", urlPatterns = "/*" )
public class SecurityFilter implements Filter {

FilterConfig filterConfig = null;

@Override
public void destroy() {
    this.filterConfig = null;
}

@Override
public void doFilter(ServletRequest request, ServletResponse response,
        FilterChain chain) throws IOException, ServletException {
    chain.doFilter(new XssHttpServletRequestWrapper((HttpServletRequest) request), response);
}

@Override
public void init(FilterConfig filterConfig) throws ServletException {
    this.filterConfig = filterConfig;
}

}
  具体实现是通过HttpServletRequestWrapper来获取request的内容,然后通过重写getParameterValues()、getParameter()方法进行处理的。java
public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {

public XssHttpServletRequestWrapper(HttpServletRequest servletRequest) {
    super(servletRequest);
}

@Override
public String[] getParameterValues(String parameter) {
    String[] values = super.getParameterValues(parameter);
    if (values == null) {
        return null;
    }
    int count = values.length;
    String[] encodedValues = new String[count];
    for (int i = 0; i < count; i++) {
        System.out.println("before:"+values[i]);
        encodedValues[i] = JsoupUtil.clean(cleanAnyFileRead((String) values[i]));
        System.out.println("after:"+encodedValues[i]);
    }
    return encodedValues;
}

&emsp;&emsp;这里存在第一种绕过方式,因为对于multipart/form-data这种数据类型上述过滤器明显也是无法获取request提交内容的,那么可以尝试转换multipart的方式进行提交,尝试绕过相关的过滤器:
![图片.png](/img/sin/M00/00/37/wKg0C18fnFqAfHSbAAChd3ThyBA362.png)
&emsp;&emsp;比较可惜的是,这里对任意文件下载接口并不适用,因为其是用@GetMapping("/downloadFile.do")进行注解的,限制了请求方法为GET。
&emsp;&emsp;获取数据的方式虽然不全面,但是仅针对/downloadFile.do接口来说的话是足够的了,那么继续往下看相关的过滤规则是否存在缺陷。
&emsp;&emsp;这里存在两层过滤,一层是通过cleanAnyFileRead()过滤任意文件上传的敏感输入的,第二层是调用工具类JsoupUtil.clean()方法进行XSS输入过滤。
java
@Override
public String getParameter(String parameter) {
String value = super.getParameter(parameter);
if (value == null) {
return null;
}
return JsoupUtil.clean(cleanAnyFileRead((String) value));
}
&emsp;&emsp;首先看任意文件下载的过滤,这里是简单的使用正则进行过滤,出现一次或者多次的.且以/结尾的内容都进行过滤,例如../、./等,这也就解释了为什么前面测试时输入../../../etc/passwd失败了,过滤后返回的内容为/etc/passwd,拼接uploadPath后不存在相关的文件,路径穿越失败:java
private static String cleanAnyFileRead(String value) {
value = value.replaceAll(".+/", "");
return value;
}
&emsp;&emsp;继续往下看xss的过滤,JsoupUtil.clean()方法的具体实现如下:java
public class JsoupUtil {
/*
* 配置白名单为基本使用的标签再加上img标签
/
private static final Whitelist WHITELIST = Whitelist.basicWithImages();

/**
 * 配置过滤的参数,不对代码格式化
 */
private static final Document.OutputSettings OUTPUT_SETTINGS = new Document.OutputSettings().prettyPrint(false);

static {
    //富文本编辑时一些样式是使用style来进行实现的 比如红色字体 style="color:red;" 所以需要给所有标签添加style属性
    WHITELIST.addAttributes(":all","style");
}

public static String clean(String content){
    return Jsoup.clean(content,"",WHITELIST,OUTPUT_SETTINGS);
}

}
&emsp;&emsp;比较简单粗暴,直接使用jsoup组件进行了过滤。这里简单介绍下jsoup的api:
&emsp;&emsp;使用Jsoup的clean方法进行清除HTML标签操作,该方法会清除在你所指定的白名单whitelist中的所有HTML标签。默认的Jsoup提供了5种Whitelist的API
* none()
该API会清除所有HTML标签,仅保留文本节点。
* impleTest()
该API仅会保留b, em, i, strong, u 标签,除此之外的所有HTML标签都会被清除。
* basic()
该API会保留 a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul 和其适当的属性标签,除此之外的所有HTML标签都会被清除,且该API不允许出现图片(img tag)。另外该API中允许出现的超链接中可以允许其指定http, https, ftp, mailto 且在超链接中强制追加rel=nofollow属性。
* basicWithImages()
该API在保留basic()中允许出现的标签的同时也允许出现图片(img tag)和img的相关适当属性,且其src允许其指定 http 或 https。
* relaxed()
该API仅会保留 a, b, blockquote, br, caption, cite, code, col, colgroup, dd, div, dl, dt, em, h1, h2, h3, h4, h5, h6, i, img, li, ol, p, pre, q, small, span, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, u, ul 标签,除此之外的所有HTML标签都会被清除,且在超链接中不会强制追加rel=nofollow属性。
* clean()
此外Jsoup的Whitelist提供了addTags方法,利用该方法可以对whitelist进行自定义扩展:

public Whitelist addTags(String... tags)
&emsp;&emsp;所以JsoupUtil.clean()方法的效果应该是输入<script>类的内容时,会自动转换为null。
&emsp;&emsp;这么一看,相关的恶意数据被过滤了,好像也没啥问题。仔细琢磨下过滤的顺序问题,可以发现其实其中过滤方式是可以绕过进行任意文件下载的。
&emsp;&emsp;要想对任意文件下载的接口进行利用,目的是让最后提交下载的地址为../../../../../etc/passwd的方式,首先../、./这种提交方式是直接被过滤掉了的,但是结合前面的正则表达式(出现一次或者多次的.且以/结尾的内容都进行过滤),..tkswifty/这种格式是可以通过过滤器安全检查的。
&emsp;&emsp;此时再结合xss过滤,jsoup组件会把类似<script>内容替换为空。然后过滤的顺序是:

任意文件下载->xss
```
  那么也就是说,尝试使用..<details>/..<details>/..<details>/etc/passwd进行访问可以达到任意文件下载的效果:

图片.png
  到环境上进行验证,成功绕过相关的过滤器安全限制读取了/etc/passwd内容:

图片.png

拓展与延伸

  上面的案例中一个过滤顺序的问题导致了相关的漏洞bypass,越复杂的设计可能存在的安全隐患就越多,针对文件下载业务,一般可以直接在对应的接口进行相关的检查,例如:
* 对下载的文件类型进行白名单控制,限制下载的文件后缀;
* 使用类似Java的file.getCanonicalPath()判断文件的真实路径,仅允许下载规定路径下的文件。

  此外,在日常的黑盒测试中,也可以结合常见的过滤器设计进行相关的FUZZ,尝试进行相关任意文件下载的绕过,例如可以结合SQL注入的过滤关键字:
..|/..|/..|/..|/..|/..|/..|/..|/etc/passwd #Oracle的拼接符是||
.|./.|./.|./.|./.|./.|./.|./.|./etc/passwd #调整关键字的位置

  又比如上述例子中的结合xss的关键字:
..<details>/..<details>/..<details>/etc/passwd
.<details>./.<details>./.<details>./etc/passwd

  又比如命令执行的关键字nc等。

相关推荐: 一次艰难的TP渗透测试

信息收集由于网站 www.a.com/admin,访问立即跳转到www.a.com/admin/publicer/login发现是TP,5.0.23,在漏洞的版本范围内,尝试使用:index.php?s=index/thinkapp/invokefunctio…