Struts2 高危漏洞修复方案 (S2-016/S2-017)

时间:2024-02-23 22:53:35

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

分享如下:

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

第1步.右键保存图片,重命名为后缀.zip,解压打开。

 

(右键保存图片,重命名为后缀.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
 

[html] view plaincopy在CODE上查看代码片派生到我的代码片
 
  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解压目录

启动漏洞监听

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

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. package com.htht.commonweb.listener;  
  2.   
  3. import javax.servlet.ServletContextEvent;  
  4. import javax.servlet.ServletContextListener;  
  5.   
  6. import com.htht.commonweb.JavaEEbugRepair;  
  7.   
  8. /** 
  9.  * WEB应用程序初始化监听器 
  10.  */  
  11. public class MyServletContextListener implements ServletContextListener {  
  12.     public void contextDestroyed(ServletContextEvent arg0) {  
  13.        
  14.     }  
  15.   
  16.     public void contextInitialized(ServletContextEvent arg0) {  
  17.         try {  
  18.             JavaEEbugRepair.initRepair_S2_016();  
  19.             JavaEEbugRepair.initRepair_S2_017();  
  20.               
  21.         } catch (Exception e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.     }  
  25. }  



附:ognl.Ognl.java,完整包参见struts2_S016_S017_repair.rar解压目录

ognl调用解决漏洞

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

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. package ognl;  
  2.   
  3. import java.io.StringReader;  
  4. import java.util.Map;  
  5.   
  6. import com.htht.commonweb.JavaEEbugRepair;  
  7.   
  8. public abstract class Ognl  
  9. {  
  10.   public static Object parseExpression(String expression)  
  11.     throws OgnlException  
  12.   {  
  13.       if(JavaEEbugRepair.repair_s2_016(expression)){  
  14.           return null;  
  15.       }  
  16.       try {  
  17.           OgnlParser parser = new OgnlParser(new StringReader(expression));  
  18.           return parser.topLevelExpression();  
  19.       } catch (ParseException e) {  
  20.           throw new ExpressionSyntaxException(expression, e);  
  21.       } catch (TokenMgrError e) {  
  22.           throw new ExpressionSyntaxException(expression, e);  
  23.       }  
  24.   }  
  25.   
  26.   public static Map createDefaultContext(Object root)  
  27.   {  
  28.     return addDefaultContext(root, null, null, null, new OgnlContext());  
  29.   }  
  30.   
  31.   public static Map createDefaultContext(Object root, ClassResolver classResolver)  
  32.   {  
  33.     return addDefaultContext(root, classResolver, null, null, new OgnlContext());  
  34.   }  
  35.   
  36.   public static Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter)  
  37.   {  
  38.     return addDefaultContext(root, classResolver, converter, null, new OgnlContext());  
  39.   }  
  40.   
  41.   public static Map createDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess)  
  42.   {  
  43.     return addDefaultContext(root, classResolver, converter, memberAccess, new OgnlContext());  
  44.   }  
  45.   
  46.   public static Map addDefaultContext(Object root, Map context)  
  47.   {  
  48.     return addDefaultContext(root, null, null, null, context);  
  49.   }  
  50.   
  51.   public static Map addDefaultContext(Object root, ClassResolver classResolver, Map context)  
  52.   {  
  53.     return addDefaultContext(root, classResolver, null, null, context);  
  54.   }  
  55.   
  56.   public static Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, Map context)  
  57.   {  
  58.     return addDefaultContext(root, classResolver, converter, null, context);  
  59.   }  
  60.   
  61.   public static Map addDefaultContext(Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context)  
  62.   {  
  63.     OgnlContext result;  
  64.     if (!(context instanceof OgnlContext)) {  
  65.        result = new OgnlContext();  
  66.       result.setValues(context);  
  67.     } else {  
  68.       result = (OgnlContext)context;  
  69.     }  
  70.     if (classResolver != null) {  
  71.       result.setClassResolver(classResolver);  
  72.     }  
  73.     if (converter != null) {  
  74.       result.setTypeConverter(converter);  
  75.     }  
  76.     if (memberAccess != null) {  
  77.       result.setMemberAccess(memberAccess);  
  78.     }  
  79.     result.setRoot(root);  
  80.     return result;  
  81.   }  
  82.   
  83.   public static void setClassResolver(Map context, ClassResolver classResolver)  
  84.   {  
  85.     context.put("_classResolver", classResolver);  
  86.   }  
  87.   
  88.   public static ClassResolver getClassResolver(Map context)  
  89.   {  
  90.     return (ClassResolver)context.get("_classResolver");  
  91.   }  
  92.   
  93.   public static void setTypeConverter(Map context, TypeConverter converter)  
  94.   {  
  95.     context.put("_typeConverter", converter);  
  96.   }  
  97.   
  98.   public static TypeConverter getTypeConverter(Map context)  
  99.   {  
  100.     return (TypeConverter)context.get("_typeConverter");  
  101.   }  
  102.   
  103.   public static void setMemberAccess(Map context, MemberAccess memberAccess)  
  104.   {  
  105.     context.put("_memberAccess", memberAccess);  
  106.   }  
  107.   
  108.   public static MemberAccess getMemberAccess(Map context)  
  109.   {  
  110.     return (MemberAccess)context.get("_memberAccess");  
  111.   }  
  112.   
  113.   public static void setRoot(Map context, Object root)  
  114.   {  
  115.     context.put("root", root);  
  116.   }  
  117.   
  118.   public static Object getRoot(Map context)  
  119.   {  
  120.     return context.get("root");  
  121.   }  
  122.   
  123.   public static Evaluation getLastEvaluation(Map context)  
  124.   {  
  125.     return (Evaluation)context.get("_lastEvaluation");  
  126.   }  
  127.   
  128.   public static Object getValue(Object tree, Map context, Object root)  
  129.     throws OgnlException  
  130.   {  
  131.     return getValue(tree, context, root, null);  
  132.   }  
  133.   
  134.   public static Object getValue(Object tree, Map context, Object root, Class resultType)  
  135.     throws OgnlException  
  136.   {  
  137.     OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);  
  138.   
  139.     Object result = ((Node)tree).getValue(ognlContext, root);  
  140.     if (resultType != null) {  
  141.       result = getTypeConverter(context).convertValue(context, root, null, null, result, resultType);  
  142.     }  
  143.     return result;  
  144.   }  
  145.   
  146.   public static Object getValue(String expression, Map context, Object root)  
  147.     throws OgnlException  
  148.   {  
  149.     return getValue(expression, context, root, null);  
  150.   }  
  151.   
  152.   public static Object getValue(String expression, Map context, Object root, Class resultType)  
  153.     throws OgnlException  
  154.   {  
  155.     return getValue(parseExpression(expression), context, root, resultType);  
  156.   }  
  157.   
  158.   public static Object getValue(Object tree, Object root)  
  159.     throws OgnlException  
  160.   {  
  161.     return getValue(tree, root, null);  
  162.   }  
  163.   
  164.   public static Object getValue(Object tree, Object root, Class resultType)  
  165.     throws OgnlException  
  166.   {  
  167.     return getValue(tree, createDefaultContext(root), root, resultType);  
  168.   }  
  169.   
  170.   public static Object getValue(String expression, Object root)  
  171.     throws OgnlException  
  172.   {  
  173.     return getValue(expression, root, null);  
  174.   }  
  175.   
  176.   public static Object getValue(String expression, Object root, Class resultType)  
  177.     throws OgnlException  
  178.   {  
  179.     return getValue(parseExpression(expression), root, resultType);  
  180.   }  
  181.   
  182.   public static void setValue(Object tree, Map context, Object root, Object value)  
  183.     throws OgnlException  
  184.   {  
  185.     OgnlContext ognlContext = (OgnlContext)addDefaultContext(root, context);  
  186.     Node n = (Node)tree;  
  187.   
  188.     n.setValue(ognlContext, root, value);  
  189.   }  
  190.   
  191.   public static void setValue(String expression, Map context, Object root, Object value)  
  192.     throws OgnlException  
  193.   {  
  194.     setValue(parseExpression(expression), context, root, value);  
  195.   }  
  196.   
  197.   public static void setValue(Object tree, Object root, Object value)  
  198.     throws OgnlException  
  199.   {  
  200.     setValue(tree, createDefaultContext(root), root, value);  
  201.   }  
  202.   
  203.   public static void setValue(String expression, Object root, Object value)  
  204.     throws OgnlException  
  205.   {  
  206.     setValue(parseExpression(expression), root, value);  
  207.   }  
  208.   
  209.   public static boolean isConstant(Object tree, Map context) throws OgnlException  
  210.   {  
  211.     return ((SimpleNode)tree).isConstant((OgnlContext)addDefaultContext(null, context));  
  212.   }  
  213.   
  214.   public static boolean isConstant(String expression, Map context) throws OgnlException  
  215.   {  
  216.     return isConstant(parseExpression(expression), context);  
  217.   }  
  218.   
  219.   public static boolean isConstant(Object tree) throws OgnlException  
  220.   {  
  221.     return isConstant(tree, createDefaultContext(null));  
  222.   }  
  223.   
  224.   public static boolean isConstant(String expression) throws OgnlException  
  225.   {  
  226.     return isConstant(parseExpression(expression), createDefaultContext(null));  
  227.   }  
  228.   
  229.   public static boolean isSimpleProperty(Object tree, Map context) throws OgnlException  
  230.   {  
  231.     return ((SimpleNode)tree).isSimpleProperty((OgnlContext)addDefaultContext(null, context));  
  232.   }  
  233.   
  234.   public static boolean isSimpleProperty(String expression, Map context) throws OgnlException  
  235.   {  
  236.     return isSimpleProperty(parseExpression(expression), context);  
  237.   }  
  238.   
  239.   public static boolean isSimpleProperty(Object tree) throws OgnlException  
  240.   {  
  241.     return isSimpleProperty(tree, createDefaultContext(null));  
  242.   }  
  243.   
  244.   public static boolean isSimpleProperty(String expression) throws OgnlException  
  245.   {  
  246.     return isSimpleProperty(parseExpression(expression), createDefaultContext(null));  
  247.   }  
  248.   
  249.   public static boolean isSimpleNavigationChain(Object tree, Map context) throws OgnlException  
  250.   {  
  251.     return ((SimpleNode)tree).isSimpleNavigationChain((OgnlContext)addDefaultContext(null, context));  
  252.   }  
  253.   
  254.   public static boolean isSimpleNavigationChain(String expression, Map context) throws OgnlException  
  255.   {  
  256.     return isSimpleNavigationChain(parseExpression(expression), context);  
  257.   }  
  258.   
  259.   public static boolean isSimpleNavigationChain(Object tree) throws OgnlException  
  260.   {  
  261.     return isSimpleNavigationChain(tree, createDefaultContext(null));  
  262.   }  
  263.   
  264.   public static boolean isSimpleNavigationChain(String expression) throws OgnlException  
  265.   {  
  266.     return isSimpleNavigationChain(parseExpression(expression), createDefaultContext(null));  
  267.   }  
  268. }  

 

 

附:com.htht.commonweb.JavaEEbugRepair.java,完整包参见struts2_S016_S017_repair.rar解压目录

拦截攻击关键代码

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. OgnlRuntime.setMethodAccessor(Runtime.class, new NoMethodAccessor());  
  2. OgnlRuntime.setMethodAccessor(System.class, new NoMethodAccessor());  
  3. OgnlRuntime.setMethodAccessor(ProcessBuilder.class,new NoMethodAccessor());  
  4. OgnlRuntime.setMethodAccessor(OgnlRuntime.class, new NoMethodAccessor());  
  5.           
  6. //io敏感操作    
  7. OgnlRuntime.setMethodAccessor(OutputStream.class, new NoMethodAccessor());    
  8. OgnlRuntime.setMethodAccessor(InputStream.class, new NoMethodAccessor());    
  9. OgnlRuntime.setMethodAccessor(File.class, new NoMethodAccessor());    
  10. OgnlRuntime.setMethodAccessor(DataOutput.class, new NoMethodAccessor());    
  11. OgnlRuntime.setMethodAccessor(DataInput.class, new NoMethodAccessor());    
  12. OgnlRuntime.setMethodAccessor(Reader.class, new NoMethodAccessor());    
  13. OgnlRuntime.setMethodAccessor(Writer.class, new NoMethodAccessor());   



 

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

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. package com.htht.commonweb;  
  2.   
  3. import java.io.DataInput;  
  4. import java.io.DataOutput;  
  5. import java.io.File;  
  6. import java.io.InputStream;  
  7. import java.io.OutputStream;  
  8. import java.io.Reader;  
  9. import java.io.Writer;  
  10. import java.util.Map;  
  11.   
  12. import ognl.MethodAccessor;  
  13. import ognl.MethodFailedException;  
  14. import ognl.OgnlRuntime;  
  15.   
  16.   
  17. /** 
  18.  * @author yanjianzhong(yjz_ok@163.com) 2013/08/08 
  19.  * @版权所有,转载请标明出处. http://blog.csdn.net/jzshmyt 
  20.  * download : http://jskfs.googlecode.com/files/struts2_(016_017)_bug_repair.rar 
  21.  */  
  22. public class JavaEEbugRepair{  
  23.     /* 
  24.      * 官方描述: 
  25.      * S2-016:https://cwiki.apache.org/confluence/display/WW/S2-016 
  26.      * S2_016 bug repair 
  27.      */  
  28.     private static S2_0XX s2_016 = new S2_0XX();  
  29.       
  30.   
  31.     /* 
  32.      *  修改 ognl.Ognl#parseExpression,调用 check_s2_016 方法 
  33.      *  public static Object parseExpression(String expression)throws OgnlException 
  34.      *  { 
  35.      *        //modify point begin 
  36.      *        if(JavaEEBug.check_s2_016(expression)){  
  37.      *              return null  
  38.      *        } 
  39.      *        //modify point end 
  40.      *        try { 
  41.      *            OgnlParser parser = new OgnlParser(new StringReader(expression)); 
  42.      *            return parser.topLevelExpression(); 
  43.      *        } catch (ParseException e) { 
  44.      *            throw new ExpressionSyntaxException(expression, e); 
  45.      *        } catch (TokenMgrError e) { 
  46.      *            throw new ExpressionSyntaxException(expression, e); 
  47.      *        } 
  48.      *    } 
  49.      */  
  50.     public static boolean repair_s2_016(String expression){  
  51.         return s2_016.check(expression);  
  52.     }  
  53.     /* 
  54.     * 在servlet/struts/spring 任何一个框架的listener中调用 
  55.     */  
  56.     public static void initRepair_S2_016(){  
  57.         OgnlRuntime.setMethodAccessor(Runtime.class, new NoMethodAccessor());  
  58.         OgnlRuntime.setMethodAccessor(System.class, new NoMethodAccessor());  
  59.         OgnlRuntime.setMethodAccessor(ProcessBuilder.class,new NoMethodAccessor());  
  60.         OgnlRuntime.setMethodAccessor(OgnlRuntime.class, new NoMethodAccessor());  
  61.           
  62.         //io敏感操作    
  63.         OgnlRuntime.setMethodAccessor(OutputStream.class, new NoMethodAccessor());    
  64.         OgnlRuntime.setMethodAccessor(InputStream.class, new NoMethodAccessor());    
  65.         OgnlRuntime.setMethodAccessor(File.class, new NoMethodAccessor());    
  66.         OgnlRuntime.setMethodAccessor(DataOutput.class, new NoMethodAccessor());    
  67.         OgnlRuntime.setMethodAccessor(DataInput.class, new NoMethodAccessor());    
  68.         OgnlRuntime.setMethodAccessor(Reader.class, new NoMethodAccessor());    
  69.         OgnlRuntime.setMethodAccessor(Writer.class, new NoMethodAccessor());   
  70.           
  71.         s2_016 = new S2_0XX(){  
  72.             public boolean check(String expression){  
  73.                 String evalMethod[] = {"Runtime", "ProcessBuilder","java.io.File","new File","OutputStream","InputStream"};  
  74.                 String methodString = null;  
  75.                 methodString = expression.toLowerCase();  
  76.                 for (int i = 0; i < evalMethod.length; i++) {  
  77.                     if (methodString.indexOf(evalMethod[i].toLowerCase()) > -1) {  
  78.                         System.out.print("|OGNL正在执行恶意语句|" + methodString + "|看到这个消息,请联系安全工程师!!!");  
  79.                         return true;  
  80.                     }  
  81.                 }  
  82.                 return false;  
  83.             }  
  84.         };  
  85.           
  86.     }  
  87.       
  88.     /* 
  89.      * S2-017:https://cwiki.apache.org/confluence/display/WW/S2-017 
  90.      * S2_017 bug repair 
  91.      */  
  92.     private static S2_0XX s2_017 = new S2_0XX();  
  93.       
  94.     /* 
  95.     * Call by org.apache.struts2.dispatcher.mapper.DefaultActionMapper#handleSpecialParameters  
  96.     * Repair Example : 
  97.     * public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) 
  98.     * { 
  99.     *       Set uniqueParameters = new HashSet(); 
  100.     *       Map parameterMap = request.getParameterMap(); 
  101.     *       Iterator iterator = parameterMap.keySet().iterator(); 
  102.     *       while (iterator.hasNext()) { 
  103.     *         String key = (String)iterator.next(); 
  104.     *    
  105.     *         if ((key.endsWith(".x")) || (key.endsWith(".y"))) { 
  106.     *           key = key.substring(0, key.length() - 2); 
  107.     *         } 
  108.     *         //modify point begin 
  109.     *         if (JavaEEBug.check_s2_017(key)) { 
  110.     *             return; 
  111.     *         } 
  112.     *         //modify point end 
  113.     *         if (!uniqueParameters.contains(key)) { 
  114.     *           ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key); 
  115.     *    
  116.     *           if (parameterAction != null) { 
  117.     *             parameterAction.execute(key, mapping); 
  118.     *             uniqueParameters.add(key); 
  119.     *             break; 
  120.     *           } 
  121.     *         } 
  122.     *       } 
  123.     *     } 
  124.     */  
  125.     public static boolean repair_s2_017(String key){  
  126.         return s2_017.check(key);  
  127.     }  
  128.       
  129.     /* 
  130.     * 在servlet/struts/spring 任何一个框架的listener中调用 
  131.     */  
  132.     public static void initRepair_S2_017(){  
  133.         s2_017 = new S2_0XX(){  
  134.             public boolean check(String key){  
  135.                 return (key.contains("redirect:")) || (key.contains("redirectAction:")) || (key.contains("action:"));  
  136.             }  
  137.         };  
  138.     }  
  139. }  
  140.   
  141. /** 
  142.  *  漏洞验证修复之基类 
  143.  *  说明: 
  144.  *  漏洞修复代码的实现逻辑,非侵入式设计。 
  145.  *  当listener中未调用initRepair_S2_016、initRepair_S2_017进行漏洞调用初始化时, 
  146.  *  保持Ognl和DefaultActionMapper修复前源码等价逻辑. 
  147.  *  
  148.  */  
  149. class S2_0XX {  
  150.     public boolean check(String key){  
  151.         return false;  
  152.     }  
  153. }  
  154.   
  155.   
  156. class NoMethodAccessor implements MethodAccessor {  
  157.     public NoMethodAccessor() {  
  158.     }  
  159.   
  160.     @Override  
  161.     public Object callStaticMethod(Map context, Class targetClass,  
  162.             String methodName, Object[] args) throws MethodFailedException {  
  163.         if(targetClass!=null){    
  164.             System.out.println("拦截并拒绝敏感操作: static "+targetClass.getName()+"#"+methodName);    
  165.         }    
  166.         throw new MethodFailedException("do not run", methodName, null);  
  167.     }  
  168.   
  169.     @Override  
  170.     public Object callMethod(Map context, Object target, String methodName,  
  171.             Object[] args) throws MethodFailedException {  
  172.         // TODO Auto-generated method stub  
  173.         if(target!=null){    
  174.             System.out.println("拦截并拒绝敏感操作:"+target.getClass().getName()+"#"+methodName);    
  175.         }    
  176.         throw new MethodFailedException("do not run", methodName,null);  
  177.     }  
  178. }  

 

 

附: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静态代码,漏洞解决成功!

 

 

[java] view plaincopy在CODE上查看代码片派生到我的代码片
 
  1. public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping)  
  2.   {  
  3.     Set uniqueParameters = new HashSet();  
  4.     Map parameterMap = request.getParameterMap();  
  5.     Iterator iterator = parameterMap.keySet().iterator();  
  6.     while (iterator.hasNext()) {  
  7.       String key = (String)iterator.next();  
  8.   
  9.       if ((key.endsWith(".x")) || (key.endsWith(".y"))) {  
  10.         key = key.substring(0, key.length() - 2);  
  11.       }  
  12.         
  13.       if (JavaEEbugRepair.repair_s2_017(key)) {  
  14.           return;  
  15.       }  
  16.   
  17.       if (!uniqueParameters.contains(key)) {  
  18.         ParameterAction parameterAction = (ParameterAction)this.prefixTrie.get(key);  
  19.   
  20.         if (parameterAction != null) {  
  21.           parameterAction.execute(key, mapping);  
  22.           uniqueParameters.add(key);  
  23.           break;  
  24.         }  
  25.       }  
  26.     }  
  27.   }  



 

 

参考:

http://blog.csdn.net/mydwr/article/details/18727627

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