Struts2 高危漏洞修复方案 (S2-016/S2-017)
近期Struts2被曝重要漏洞,此漏洞影响struts2.0-struts2.3所有版本,可直接导致服务器被远程控制从而引起数据泄漏,影响巨大,受影响站点以电商、银行、门户、政府居多.
引发的威胁:
取得网站服务器主机管理权限。
CVSS:(AV:R/AC:L/Au:NR/C:C/A:C/I:C/B:N)score:10.00(最高10分,高危)
即:远程攻击、攻击难度低、不需要用户认证,对机密性、完整性、可用性均构成完全影响。
验证情况:
Struts2漏洞利用工具下载:右键保存图片,重命名为后缀.zip,解压打开。
(右键保存图片,重命名为后缀.zip)
验证如下:
图1 网站目录
图2 成功执行ipconfig命令
图3成功硬盘目录
官方描述:
S2-016:https://cwiki.apache.org/confluence/display/WW/S2-016
S2-017:https://cwiki.apache.org/confluence/display/WW/S2-017
官方建议修复方案:升级到最新版本 struts-2.3.15.1
但通常现有系统升级,可能导致不稳定及与其他框架比如spring等的不兼容,成本较高。
鉴于此csdn网友jzshmyt整理了一种既可以不用升级现有struts版本,有能完美解决这两个漏洞的方案,
分享如下:
-------------------------
(右键保存图片,重命名为后缀.zip)
第2步.解压,将src目录中的所有文件,复制到自己项目的src目录中,编译通过,形成class文件
(本例struts是Struts-core-2.1.6版本_对2.0-2.3版本都有效,实际项目需要根据struts版本做适当调整).
应用服务器会优先加载class目录中的类,自动覆盖jar包中的类.
第3步.web.xml中配置com.htht.commonweb.listener.MyServletContextListener
- <listener>
- <listener-class>org.hdht.commonweb.listener.MyServletContextListener</listener-class>
- </listener>
第4步.重启服务,修复完毕.
附:com.htht.commonweb.listener.MyServletContextListener.java,完整包参见struts2_S016_S017_repair.rar解压目录
启动漏洞监听
-------------------------
- package com.htht.commonweb.listener;
- import javax.servlet.ServletContextEvent;
- import javax.servlet.ServletContextListener;
- import com.htht.commonweb.JavaEEbugRepair;
- /**
- * WEB应用程序初始化监听器
- */
- public class MyServletContextListener implements ServletContextListener {
- public void contextDestroyed(ServletContextEvent arg0) {
- }
- public void contextInitialized(ServletContextEvent arg0) {
- try {
- JavaEEbugRepair.initRepair_S2_016();
- JavaEEbugRepair.initRepair_S2_017();
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
附:ognl.Ognl.java,完整包参见struts2_S016_S017_repair.rar解压目录
ognl调用解决漏洞
-------------------------
- package ognl;
- import java.io.StringReader;
- import java.util.Map;
- import com.htht.commonweb.JavaEEbugRepair;
- public abstract class Ognl
- {
- public static Object parseExpression(String expression)
- throws OgnlException
- {
- if(JavaEEbugRepair.repair_s2_016(expression)){
- return null;
- }
- try {
- OgnlParser parser = new OgnlParser(new StringReader(expression));
- return parser.topLevelExpression();
- } catch (ParseException e) {
- throw new ExpressionSyntaxException(expression, e);
- } catch (TokenMgrError e) {
- throw new ExpressionSyntaxException(expression, e);
- }
- }
- public static Map createDefaultContext(Object root)
- {
- return addDefaultContext(root, null, null, null, new OgnlContext());
- }
- public static Map createDefaultContext(Object root, ClassResolver classResolver)
- {
- return addDefaultContext(root, classResolver, null, null, new OgnlContext());
- }
- public static Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter)
- {
- return addDefaultContext(root, classResolver, converter, null, new OgnlContext());
- }
- public static Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess)
- {
- return addDefaultContext(root, classResolver, converter, memberAccess, new OgnlContext());
- }
- public static Map addDefaultContext(Object root, Map context)
- {
- return addDefaultContext(root, null, null, null, context);
- }
- public static Map addDefaultContext(Object root, ClassResolver classResolver, Map context)
- {
- return addDefaultContext(root, classResolver, null, null, context);
- }
- public static Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, Map context)
- {
- return addDefaultContext(root, classResolver, converter, null, context);
- }
- public static Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context)
- {
- OgnlContext result;
- if (!(context instanceof OgnlContext)) {
- result = new OgnlContext();
- result.setValues(context);
- } else {
- result = (OgnlContext)context;
- }
- if (classResolver != null) {
- result.setClassResolver(classResolver);
- }
- if (converter != null) {
- result.setTypeConverter(converter);
- }
- if (memberAccess != null) {
- result.setMemberAccess(memberAccess);
- }
- result.setRoot(root);
- return result;
- }
- public static void setClassResolver(Map context, ClassResolver classResolver)
- {
- context.put("_classResolver", classResolver);
- }
- public static ClassResolver getClassResolver(Map context)
- {
- return (ClassResolver)context.get("_classResolver");
- }
- public static void setTypeConverter(Map context, TypeConverter converter)
- {
- context.put("_typeConverter", converter);
- }
- public static TypeConverter getTypeConverter(Map context)
- {
- return (TypeConverter)context.get("_typeConverter");
- }
- public static void setMemberAccess(Map context, MemberAccess memberAccess)
- {
- context.put("_memberAccess", memberAccess);
- }
- public static MemberAccess getMemberAccess(Map context)
- {
- return (MemberAccess)context.get("_memberAccess");
- }
- public static void setRoot(Map context, Object root)
- {
- context.put("root", root);
- }
- public static Object getRoot(Map context)
- {
- return context.get("root");
- }
- public static Evaluation getLastEvaluation(Map context)
- {
- return (Evaluation)context.get("_lastEvaluation");
- }
- public static Object getValue(Object tree, Map context, Object root)
- throws OgnlException
- {
- return getValue(tree, context, root, null);
- }
- public static Object getValue(Object tree, Map context, Object root, Class resultType)
- throws OgnlException
- {
- OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);
- Object result = ((Node)tree).getValue(ognlContext, root);
- if (resultType != null) {
- result = getTypeConverter(context).convertValue(context, root, null, null, result, resultType);
- }
- return result;
- }
- public static Object getValue(String expression, Map context, Object root)
- throws OgnlException
- {
- return getValue(expression, context, root, null);
- }
- public static Object getValue(String expression, Map context, Object root, Class resultType)
- throws OgnlException
- {
- return getValue(parseExpression(expression), context, root, resultType);
- }
- public static Object getValue(Object tree, Object root)
- throws OgnlException
- {
- return getValue(tree, root, null);
- }
- public static Object getValue(Object tree, Object root, Class resultType)
- throws OgnlException
- {
- return getValue(tree, createDefaultContext(root), root, resultType);
- }
- public static Object getValue(String expression, Object root)
- throws OgnlException
- {
- return getValue(expression, root, null);
- }
- public static Object getValue(String expression, Object root, Class resultType)
- throws OgnlException
- {
- return getValue(parseExpression(expression), root, resultType);
- }
- public static void setValue(Object tree, Map context, Object root, Object value)
- throws OgnlException
- {
- OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);
- Node n = (Node)tree;
- n.setValue(ognlContext, root, value);
- }
- public static void setValue(String expression, Map context, Object root, Object value)
- throws OgnlException
- {
- setValue(parseExpression(expression), context, root, value);
- }
- public static void setValue(Object tree, Object root, Object value)
- throws OgnlException
- {
- setValue(tree, createDefaultContext(root), root, value);
- }
- public static void setValue(String expression, Object root, Object value)
- throws OgnlException
- {
- setValue(parseExpression(expression), root, value);
- }
- public static boolean isConstant(Object tree, Map context) throws OgnlException
- {
- return ((SimpleNode)tree).isConstant((OgnlContext)addDefaultContext(null, context));
- }
- public static boolean isConstant(String expression, Map context) throws OgnlException
- {
- return isConstant(parseExpression(expression), context);
- }
- public static boolean isConstant(Object tree) throws OgnlException
- {
- return isConstant(tree, createDefaultContext(null));
- }
- public static boolean isConstant(String expression) throws OgnlException
- {
- return isConstant(parseExpression(expression), createDefaultContext(null));
- }
- public static boolean isSimpleProperty(Object tree, Map context) throws OgnlException
- {
- return ((SimpleNode)tree).isSimpleProperty((OgnlContext)addDefaultContext(null, context));
- }
- public static boolean isSimpleProperty(String expression, Map context) throws OgnlException
- {
- return isSimpleProperty(parseExpression(expression), context);
- }
- public static boolean isSimpleProperty(Object tree) throws OgnlException
- {
- return isSimpleProperty(tree, createDefaultContext(null));
- }
- public static boolean isSimpleProperty(String expression) throws OgnlException
- {
- return isSimpleProperty(parseExpression(expression), createDefaultContext(null));
- }
- public static boolean isSimpleNavigationChain(Object tree, Map context) throws OgnlException
- {
- return ((SimpleNode)tree).isSimpleNavigationChain((OgnlContext)addDefaultContext(null, context));
- }
- public static boolean isSimpleNavigationChain(String expression, Map context) throws OgnlException
- {
- return isSimpleNavigationChain(parseExpression(expression), context);
- }
- public static boolean isSimpleNavigationChain(Object tree) throws OgnlException
- {
- return isSimpleNavigationChain(tree, createDefaultContext(null));
- }
- public static boolean isSimpleNavigationChain(String expression) throws OgnlException
- {
- return isSimpleNavigationChain(parseExpression(expression), createDefaultContext(null));
- }
- }
附:com.htht.commonweb.JavaEEbugRepair.java,完整包参见struts2_S016_S017_repair.rar解压目录
拦截攻击关键代码
- OgnlRuntime.setMethodAccessor(Runtime.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(System.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(ProcessBuilder.class,new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(OgnlRuntime.class, new NoMethodAccessor());
- //io敏感操作
- OgnlRuntime.setMethodAccessor(OutputStream.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(InputStream.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(File.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(DataOutput.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(DataInput.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(Reader.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(Writer.class, new NoMethodAccessor());
-------------------------
- package com.htht.commonweb;
- import java.io.DataInput;
- import java.io.DataOutput;
- import java.io.File;
- import java.io.InputStream;
- import java.io.OutputStream;
- import java.io.Reader;
- import java.io.Writer;
- import java.util.Map;
- import ognl.MethodAccessor;
- import ognl.MethodFailedException;
- import ognl.OgnlRuntime;
- /**
- * @author yanjianzhong(yjz_ok@163.com) 2013/08/08
- * @版权所有,转载请标明出处. http://blog.csdn.net/jzshmyt
- * download : http://jskfs.googlecode.com/files/struts2_(016_017)_bug_repair.rar
- */
- public class JavaEEbugRepair{
- /*
- * 官方描述:
- * S2-016:https://cwiki.apache.org/confluence/display/WW/S2-016
- * S2_016 bug repair
- */
- private static S2_0XX s2_016 = new S2_0XX();
- /*
- * 修改 ognl.Ognl#parseExpression,调用 check_s2_016 方法
- * public static Object parseExpression(String expression)throws OgnlException
- * {
- * //modify point begin
- * if(JavaEEBug.check_s2_016(expression)){
- * return null
- * }
- * //modify point end
- * try {
- * OgnlParser parser = new OgnlParser(new StringReader(expression));
- * return parser.topLevelExpression();
- * } catch (ParseException e) {
- * throw new ExpressionSyntaxException(expression, e);
- * } catch (TokenMgrError e) {
- * throw new ExpressionSyntaxException(expression, e);
- * }
- * }
- */
- public static boolean repair_s2_016(String expression){
- return s2_016.check(expression);
- }
- /*
- * 在servlet/struts/spring 任何一个框架的listener中调用
- */
- public static void initRepair_S2_016(){
- OgnlRuntime.setMethodAccessor(Runtime.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(System.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(ProcessBuilder.class,new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(OgnlRuntime.class, new NoMethodAccessor());
- //io敏感操作
- OgnlRuntime.setMethodAccessor(OutputStream.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(InputStream.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(File.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(DataOutput.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(DataInput.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(Reader.class, new NoMethodAccessor());
- OgnlRuntime.setMethodAccessor(Writer.class, new NoMethodAccessor());
- s2_016 = new S2_0XX(){
- public boolean check(String expression){
- String evalMethod[] = {"Runtime", "ProcessBuilder","java.io.File","new File","OutputStream","InputStream"};
- String methodString = null;
- methodString = expression.toLowerCase();
- for (int i = 0; i < evalMethod.length; i++) {
- if (methodString.indexOf(evalMethod[i].toLowerCase()) > -1) {
- System.out.print("|OGNL正在执行恶意语句|" + methodString + "|看到这个消息,请联系安全工程师!!!");
- return true;
- }
- }
- return false;
- }
- };
- }
- /*
- * S2-017:https://cwiki.apache.org/confluence/display/WW/S2-017
- * S2_017 bug repair
- */
- private static S2_0XX s2_017 = new S2_0XX();
- /*
- * Call by org.apache.struts2.dispatcher.mapper.DefaultActionMapper#handleSpecialParameters
- * Repair Example :
- * public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping)
- * {
- * Set uniqueParameters = new HashSet();
- * Map parameterMap = request.getParameterMap();
- * Iterator iterator = parameterMap.keySet().iterator();
- * while (iterator.hasNext()) {
- * String key = (String)iterator.next();
- *
- * if ((key.endsWith(".x")) || (key.endsWith(".y"))) {
- * key = key.substring(0, key.length() - 2);
- * }
- * //modify point begin
- * if (JavaEEBug.check_s2_017(key)) {
- * return;
- * }
- * //modify point end
- * if (!uniqueParameters.contains(key)) {
- * ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key);
- *
- * if (parameterAction != null) {
- * parameterAction.execute(key, mapping);
- * uniqueParameters.add(key);
- * break;
- * }
- * }
- * }
- * }
- */
- public static boolean repair_s2_017(String key){
- return s2_017.check(key);
- }
- /*
- * 在servlet/struts/spring 任何一个框架的listener中调用
- */
- public static void initRepair_S2_017(){
- s2_017 = new S2_0XX(){
- public boolean check(String key){
- return (key.contains("redirect:")) || (key.contains("redirectAction:")) || (key.contains("action:"));
- }
- };
- }
- }
- /**
- * 漏洞验证修复之基类
- * 说明:
- * 漏洞修复代码的实现逻辑,非侵入式设计。
- * 当listener中未调用initRepair_S2_016、initRepair_S2_017进行漏洞调用初始化时,
- * 保持Ognl和DefaultActionMapper修复前源码等价逻辑.
- *
- */
- class S2_0XX {
- public boolean check(String key){
- return false;
- }
- }
- class NoMethodAccessor implements MethodAccessor {
- public NoMethodAccessor() {
- }
- @Override
- public Object callStaticMethod(Map context, Class targetClass,
- String methodName, Object[] args) throws MethodFailedException {
- if(targetClass!=null){
- System.out.println("拦截并拒绝敏感操作: static "+targetClass.getName()+"#"+methodName);
- }
- throw new MethodFailedException("do not run", methodName, null);
- }
- @Override
- public Object callMethod(Map context, Object target, String methodName,
- Object[] args) throws MethodFailedException {
- // TODO Auto-generated method stub
- if(target!=null){
- System.out.println("拦截并拒绝敏感操作:"+target.getClass().getName()+"#"+methodName);
- }
- throw new MethodFailedException("do not run", methodName,null);
- }
- }
附:org.apache.struts2.dispatcher.mapper.DefaultActionMapper.java,完整包参见struts2_S016_S017_repair.rar解压目录
重写Struts2核心包的DefaultActionMapper类。
每个版本的DefaultActionMapper类不一样,可以用反编译工具
取出org.apache.struts2.dispatcher.mapper.DefaultActionMapper.java的全部代码重写编译
-------------------------
修改地方handleSpecialParameters方法上的while循环体里添加:
if (JavaEEbugRepair.repair_s2_017(key)) {
return;
}
这样受攻击就会判断返回,返回的显示是一串html静态代码,漏洞解决成功!
- public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping)
- {
- Set uniqueParameters = new HashSet();
- Map parameterMap = request.getParameterMap();
- Iterator iterator = parameterMap.keySet().iterator();
- while (iterator.hasNext()) {
- String key = (String)iterator.next();
- if ((key.endsWith(".x")) || (key.endsWith(".y"))) {
- key = key.substring(0, key.length() - 2);
- }
- if (JavaEEbugRepair.repair_s2_017(key)) {
- return;
- }
- if (!uniqueParameters.contains(key)) {
- ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key);
- if (parameterAction != null) {
- parameterAction.execute(key, mapping);
- uniqueParameters.add(key);
- break;
- }
- }
- }
- }
参考:
http://blog.csdn.net/mydwr/article/details/18727627
http://blog.csdn.net/jzshmyt/article/details/9842501
网上的多种方案:http://star.baidu.com/forum/forum.php?mod=viewthread&tid=1945