S2-046分析(CVE-2017-5638)

0x00 漏洞简介

今天出现了新的S2-46漏洞,看了下和S2-045漏洞很相似。最终的利用函数也是一样的,具体请参考我之前的分析http://paper.seebug.org/241/。 这里,我记录下这个漏洞调试的几个点,和关键地方。 官方公告,请看http://struts.apache.org/docs/s2-046.html 仔细阅读漏洞描述:

Problem
It is possible to perform a RCE attack with a malicious Content-Disposition value or with improper Content-Length header. If the Content-Dispostion / Content-Length value is not valid an exception is thrown which is then used to display an error message to a user. This is a different vector for the same vulnerability described in S2-045 (CVE-2017-5638).

最后一句话很有意思,说是:相同的漏洞,不同的向量。 POC和国外原始文章,请看最后的参考文章。重要信息和原理都在里面了,可以详细阅读下~ 漏洞描述明确了以下方面:

  • 通过Content-Dispostion / Content-Length这两个header头,注入OGNL语言,进而执行命令。
  • 最后的利用点和S2-045一样

0x01 漏洞复现

经过我的重现加上官方描述,得到第一种利用方法:

  • 同样不需要找个上传的地方,直接找个action即可~~

  • 利用有条件,配置struts.xml里面的解析器为jakarta-stream

    <constant name="struts.multipart.parser" value="jakarta-stream" />
  • 设置Content-Length大于可上传的maxSize。 用来触发错误~

  • filename处,添加OGNL语句,执行命令~~

简单说说复现,Github上下载POC:https://github.com/pwntester/S2-046-PoC~ 使用maven运行:mvn jetty:run。使用POC测试即可,返回header中会出现:X-Test: Kaboom 另一种利用方法(空字节),请看参考3,很详细~ 这个就厉害了,没有限制条件和S2-045一致~ 个人觉得非常好,值得研究学习~ 使用和S2-045一样方便,幸好影响版本也一致,用户还是尽快升级到最新版~~

0x01 漏洞关键点调试分析

以下源码版本均为:Struts 2.3.20 第一种方法,调试如下: 通过配置,可以知道在JakartaStreamMultiPartRequest类里面的parse函数下断点~
判断长度大于maxSize后,requestSizePermitted被设置为false,将fileName放到了错误信息中:
看到了buildMessage函数,当然跟进发现又执行了这个函数:LocalizedTextUtil.findText(...); 第二种空字节的利用方法,在commons-fileupload控件的Streams.class中~ 获取上传文件的fileName时,它会判断文件名是否有\x00的空字节。如果有,则抛出InvalidFileNameException异常,并且在异常信息中包含了fileName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static String checkFileName(String fileName) {
if (fileName != null && fileName.indexOf('\u0000') != -1) {//存在空字节,抛出异常~
// pFileName.replace("\u0000", "\\0")
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < fileName.length(); i++) {
char c = fileName.charAt(i);
switch (c) {
case 0:
sb.append("\\0");
break;
default:
sb.append(c);
break;
}
}
throw new InvalidFileNameException(fileName, //抛出异常,带有fileName
"Invalid file name: " + sb);
}
return fileName;
}

parse函数捕获异常后,调用了buildErrorMessage函数~~

1
2
3
4
5
6
7
8
9
10
11
12
public void parse(HttpServletRequest request, String saveDir)
throws IOException {
try {
setLocale(request);
processUpload(request, saveDir);
} catch (Exception e) {
e.printStackTrace();
String errorMessage = buildErrorMessage(e, new Object[]{}); //触发
if (!errors.contains(errorMessage))
errors.add(errorMessage);
}
}

对于默认配置JakartaMultiPartRequest,同样也捕获异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void parse(HttpServletRequest request, String saveDir) throws IOException {
try {
setLocale(request);
processUpload(request, saveDir);
} catch (FileUploadBase.SizeLimitExceededException e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Request exceeded size limit!", e);
}
String errorMessage = buildErrorMessage(e, new Object[]{e.getPermittedSize(), e.getActualSize()});
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
} catch (Exception e) {
if (LOG.isWarnEnabled()) {
LOG.warn("Unable to parse request", e);
}
String errorMessage = buildErrorMessage(e, new Object[]{});//触发
if (!errors.contains(errorMessage)) {
errors.add(errorMessage);
}
}
}

0x02 总结

每次漏洞原理分析,都可以学到许多新的知识~~

0x03 参考

https://community.hpe.com/t5/Security-Research/Struts2-046-A-new-vector/ba-p/6949723#.WNC2Rfl95QK
https://github.com/pwntester/S2-046-PoC
https://gist.github.com/frohoff/a3e24764561c0c18b6270805140e7600

文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2017/03/21/s2046-analysis-cve20175638/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog