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漏洞利用工具下载:http://download.csdn.net/detail/mydwr/6921681

验证如下:

图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版本,有能完美解决这两个漏洞的方案,

分享如下:

-------------------------

第1步.下载http://download.csdn.net/detail/mydwr/6921817

第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
 

  1. <listener>  
  2.  <listener-class>org.hdht.commonweb.listener.MyServletContextListener</listener-class>  
  3. </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/jzshmyt/article/details/9842501

        http://software.intel.com/zh-cn/blogs/2013/08/08/struts2-s2-016s2-017/?utm_campaign=CSDN&utm_source=intel.csdn.net&utm_medium=Link&utm_content=others-%20Struts2

网上的多种方案:http://star.baidu.com/forum/forum.php?mod=viewthread&tid=1945

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Struts2是一种基于Java EE的开源Web应用程序框架,用于构建J2EE应用程序。S2-020和S2-021是Struts2框架中的两个安全漏洞S2-020漏洞是由于在处理**参数拦截器(ParameterInterceptor)**时存在安全问题而引起的。攻击者可以通过构造恶意的请求,利用漏洞执行任意的命令或获得未经授权的访问权限。为了解决这个漏洞Struts2的开发者发布了安全补丁,并建议用户及时升级到修复版本。 S2-021漏洞是由于**Cookie凭证拦截器(CookieInterceptor)**中存在安全缺陷导致的。攻击者可以通过特殊构造的Cookie请求来绕过权限验证,以管理员身份执行恶意操作。为了修复这个漏洞Struts2的开发者发布了修复补丁,并建议用户尽快升级到修复版本。 对于开发者来说,了解并理解这些安全漏洞的工作原理非常重要。他们应该密切关注Struts2官方网站和邮件列表,及时了解当前版本的安全情况,并及时升级到修复版本。此外,开发者还应该在应用程序中实施必要的安全措施,如输入验证、输出编码和访问控制等,以防止其他潜在的安全漏洞。 对于系统管理员来说,他们应该密切关注Struts2的安全公告,并及时升级到修复版本,以确保应用程序的安全性。此外,他们还应该采取一些额外的安全措施,如配置防火墙、使用Web应用程序防火墙(WAF)和定期审查日志等,以进一步保护系统免受可能的攻击。 总而言之,了解并及时修复Struts2中的安全漏洞是保护应用程序和系统安全的重要步骤。开发者和系统管理员应该密切关注官方安全公告,并采取相应的措施来预防和修复潜在的风险。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mydwr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值