zoukankan      html  css  js  c++  java
  • 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版本,有能完美解决这两个漏洞的方案,

    分享如下:

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

    第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

  • 相关阅读:
    IOS UI NavigationController结构
    IOS UI 自定义navigationBar布局
    IOS UI 代码界面跳转常用方式
    IOS OC 多态(白话)
    IOS OC NSArray&NSMutableArray
    IOS OC NSString基础知识
    NSTimer做一个小计时器
    IOS UI 代码创建UIButton,UITextField,UILabel
    [HNOI2010]平面图判定
    [SDOI2017]树点涂色
  • 原文地址:https://www.cnblogs.com/phpdragon/p/4225534.html
Copyright © 2011-2022 走看看