漏洞:CVE-2021-31805——升级struts版本2.0.6-2.5.30
漏洞描述
Apache struts2部分版本存在漏洞,对不受信任的用户输入使用强制OGNL可能导致远程代码执行,黑客可利用该漏洞控制服务器。
一、切换jar包更改配置
切换jar包:
struts2-core-2.5.30.jar
struts2-spring-plugin-2.5.30.jar
删除 xwork-2.0.4 这个包已经包含在了struts2-core-2.5.30.jar中
commons-fileupload-1.4.jar
commons-io-2.6.jar
修改web.xml配置
<filter>
<filter-name>struts2</filter-name>
<filter-class>
org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter
</filter-class>
</filter>
启动报错:java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager
引入jar包:log4j-api-2.12.4.jar
启动报错:java.lang.NoClassDefFoundError: org/apache/commons/lang3/StringUtils
引入jar包:commons-lang3-3.8.1.jar
启动报错:java.lang.NoSuchMethodError: ognl.SimpleNode.isEvalChain(Lognl/OgnlContext;)Z
升级jar包:ognl-2.6.11.jar ---> ognl-3.1.29.jar
启动报错:java.lang.NoClassDefFoundError: freemarker/template/Version
升级jar包:freemarker-2.3.10.jar ---> freemarker-2.3.31.jar
启动报错:Caused by: java.lang.ClassNotFoundException: javassist.ClassPool
引入jar包:javassist-3.20.0-GA.jar
访问主页面报错,时而好使时而不好使
ERROR DefaultDispatcherErrorHandler Exception occurred during processing request: null
java.lang.NullPointerException
at com.opensymphony.xwork2.validator.DelegatingValidatorContext.makeTextProvider(DelegatingValidatorContext.java:212)
at com.opensymphony.xwork2.validator.DelegatingValidatorContext.<init>(DelegatingValidatorContext.java:65)
at com.opensymphony.xwork2.validator.AnnotationActionValidatorManager.validate(AnnotationActionValidatorManager.java:127)
at com.opensymphony.xwork2.validator.AnnotationActionValidatorManager.validate(AnnotationActionValidatorManager.java:123)
at com.opensymphony.xwork2.validator.ValidationInterceptor.doBeforeInvocation(ValidationInterceptor.java:227)
<!--修改validators.xml 的头信息-->
<!DOCTYPE validators PUBLIC
"-//OpenSymphony Group//XWork Validator Config 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-validator-config-1.0.dtd">
---改为--->
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator Definition 1.0//EN"
"http://struts.apache.org/dtds/xwork-validator-definition-1.0.dtd">
如果还不好使,检查validator配置的自定义类中是否引入了某些已被删除的包
<!-- 修改struts.xml 开启允许动态方法调用 -->
<constant name="struts.enable.DynamicMethodInvocation" value="true" />
<!-- 修改struts 配置文件 -->
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
"http://struts.apache.org/dtds/struts-2.0.dtd">
改为:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
"http://struts.apache.org/dtds/struts-2.5.dtd">
<!-- 修改struts 配置文件 加入配置 -->
<!-- strict-method-invocation="false" 拒绝所有未通过method属性配置或者<allowed-methods>标签标明的方法 -->
<!-- <global-allowed-methods>regex:.*</global-allowed-methods> 允许使用通配符调用方法-->
<package name="xx" extends="struts-default" strict-method-invocation="false" namespace="/xxx">
<global-allowed-methods>regex:.*</global-allowed-methods>
<action name="xxx" class="xxx" method="xxx">
<result name="success">
/pages/xxx/xxx.jsp
</result>
</action>
</package>
如果页面访问有问题可以尝试进行下面修改
<!-- 修改web.xml -->
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/struts/*</url-pattern>
</filter-mapping>
<filter-mapping>
改为:
<filter-name>struts2</filter-name>
<url-pattern>/struts/*.action</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/struts/*.jsp</url-pattern>
</filter-mapping>
菜单无法加载或显示html标签问题
<s:property value="menu" escape="false" /></td>
改为:
<s:property value="menu" escapeHtml="false" /></td>
<!--
escapeHtml默认为true, menu按照文本格式原样输出,带有标签
escapeHtml为false, menu的内容会解析为HTML内容,在显示的时候有换行效果
-->
二、页面标签修改
两个版本的ognl标签有很多改动,需要调整页面
<!--没什么用暂时删掉-->
<%@page import="org.apache.derby.impl.sql.catalog.SYSCHECKSRowFactory"%>
常用标签用法
<s:hidden name="editType" id="editType" value="%{editType}"></s:hidden>
<!--grade.id 后面不要加空格,否则生成的id会多一个下划线-->
<s:hidden name="grade.id"/>
<s:if test="editType=='insert'"></s:if>
<s:if test="%{editType=='insert' }"></s:if>
<c:if test="${editType=='insert' }"></c:if>
<c:set var="index" value="0" />
<s:if test="%{index+1==1}">不好使</s:if>
<a href="#tab${index+1 }"/>
<div id="tab${indexi+1 }"></div>
<iframe id="taskIframe${indexi+1 }"></iframe>
<c:set var="index" value="${index+1 }" />
<s:set var="index" value="0"/>
<div style="display: none">${index+1 }</div>
<s:property value='%{#index + 1}' />
<s:set var="index" value="%{#index+1 }" />
<s:iterator value="systemMap.keySet()" id="system">
--改为-->
<s:iterator value="systemMap.keySet()" var="system">
<s:iterator value="systemTasks" status="stuts">
<!--id 复制拼接-->
<div id="tab<s:property value="#stuts.count" />"></div>
<s:hidden name="userGrades[%{#stuts.index}].gradeCode" value="%{#aaa.gradeCode}" /></td>
<s:if test="%{#stuts.index +1 ==1 }"></s:if>
<s:property value="%{systemTasks[#stuts.index].taskCName}" />
<iframe id="taskIframe%{#stuts.index + 1 }"
src="${ctx}/xxxx/xxxx.do?xxx=<s:property value="%{xxx[#stuts.index].xxx}"/>
&editType=${editType}&gradeID=${grade.id}"
frameborder="0" width="100%" height="580"></iframe>
</s:iterator>
<s:iterator 中的status 使用方法
<s:iterator 中的status 使用方法
1:#status.odd 是否奇数行
2:#status.count 当前行数
3:#status.index 当前行的序号,从0开始『#status.count=#status.index+1』
4:#status.first 是否第一行
5:#status.last 是否最后一行
6:#status.modules(int) 当前行数取模
三、异常原因分析
ERROR DefaultDispatcherErrorHandler Exception occurred during processing request: null
跟源码发现:AnnotationActionValidatorManager
中的属性textProviderFactory
为null
继续跟进发现:
- 项目启动时,strut2 使用@Inject注入依赖,而且属性注入顺序完全随机且在for循环中处理,
- 当某个属性注入失败时,会停止该类的属性注入,也就是说,这个类只有注入失败前的属性有值,后面的属性直接初始化为null,而且没有异常抛出
//源码:construct
class ContainerImpl implements Container {
...
Object construct(InternalContext context, Class<? super T> expectedType) {
...
//injectors: @Inject注解标识的属性
for (Injector injector : injectors) {
injector.inject(context, t);
}
...
}
...
}
-
反复debug发现
AnnotationActionValidatorManager
在 ```validatorFactory``这个属性注入时总会莫名其妙的跳出循环,开始处理加载其他类信息 -
ValidatorFactory
这个类是一个接口,只有一个实现类DefaultValidatorFactory
,这个类有个比较坑的初始化方法init
public class DefaultValidatorFactory implements ValidatorFactory, Initializable { ... protected ValidatorFileParser validatorFileParser; @Override public void init() { parseValidators(); } private void parseValidators() { ... // Parse default validator configurations String resourceName = "com/opensymphony/xwork2/validator/validators/default.xml"; retrieveValidatorConfiguration(resourceName); // Overwrite and extend defaults with application specific validator configurations resourceName = "validators.xml"; retrieveValidatorConfiguration(resourceName); ... } private void retrieveValidatorConfiguration(String resourceName) { InputStream is = ClassLoaderUtil.getResourceAsStream(resourceName, DefaultValidatorFactory.class); if (is != null) { validatorFileParser.parseValidatorDefinitions(validators, is, resourceName); } } } public class DefaultValidatorFileParser implements ValidatorFileParser { ... public void parseValidatorDefinitions(Map<String, String> validators, InputStream is, String resourceName) { InputSource in = new InputSource(is); in.setSystemId(resourceName); Map<String, String> dtdMappings = new HashMap<>(); dtdMappings.put("-//Apache Struts//XWork Validator Config 1.0//EN", "xwork-validator-config-1.0.dtd"); dtdMappings.put("-//Apache Struts//XWork Validator Definition 1.0//EN", "xwork-validator-definition-1.0.dtd"); /*********************/ Document doc = DomHelper.parse(in, dtdMappings); if (doc != null) { NodeList nodes = doc.getElementsByTagName("validator"); for (int i = 0; i < nodes.getLength(); i++) { Element validatorElement = (Element) nodes.item(i); String name = validatorElement.getAttribute("name"); String className = validatorElement.getAttribute("class"); try { // catch any problems here objectFactory.buildValidator(className, new HashMap<String, Object>(), ActionContext.getContext().getContextMap()); validators.put(name, className); } catch (Exception e) { throw new ConfigurationException("Unable to load validator class " + className, e, validatorElement); } } } } ... }
-
这个方法中加载了两个配置文件
com/opensymphony/xwork2/validator/validators/default.xml
和validators.xml
第一个文件是jar包中自己的配置文件,没问题
第二个文件就比较坑了,他在系统的
resources
目录下查找文件,找到则加载因为我的文件是老版本的配置文件,因此在
Document doc = DomHelper.parse(in, dtdMappings);
这一步格式化时,格式化报错了,这个时候抛出一个有意思的异常java.net.ConnectException: Connection timed out: connect
接着一路上抛,在这个位置打印了一个
LOG.warn
, 而我的后台风平浪静。。。。public class InterceptorBuilder { ... public static List<InterceptorMapping> constructInterceptorReference(InterceptorLocator interceptorLocator, String refName, Map<String,String> refParams, Location location, ObjectFactory objectFactory) throws ConfigurationException { ... if (referencedConfig instanceof InterceptorConfig) { InterceptorConfig config = (InterceptorConfig) referencedConfig; Interceptor inter; try { inter = objectFactory.buildInterceptor(config, refParams); result.add(new InterceptorMapping(refName, inter, refParams)); } catch (ConfigurationException ex) { LOG.warn(new ParameterizedMessage("Unable to load config class {} at {} probably due to a missing jar, which might be fine if you never plan to use the {} interceptor", config.getClassName(), ex.getLocation(), config.getName()), ex); } } ... } ... }
-
struts2-core-2.5.30
版本依赖了log4j-api-2.12.4
, 这与老版本的log4j-1.2.13
貌似不兼容,引入log4j-core-2.12.4.jar
是否会打印,有待验证,log4j-core
在2.16版本前的都有漏洞,暂时不处理 -
对比
com/opensymphony/xwork2/validator/validators/default.xml
和validators.xml
两个文件发现头部标签不一致,修改后加载正常 -
如果还不好使,检查validator配置的自定义类中是否引入了某些已被删除的包
-