0%

Java内存马缝合笔记

Java内存马缝合笔记

之前渗透的时候遇到了一个java站,tomcat8跑的,能上传jsp,有shiro但是打不通(打通之后看了一眼无敌高版本)
jsp上传之后会急速被删,可以条件竞争去抢,这样子的话打一个内存马就是最稳整的,但是我是java垃圾捏,不会打,幸好天哥最后给了个现成的内存马
还有一个远古tomcat6的站,有一个jndi注入,最后也是靠whj好兄弟缝出来了一个tomcat6的回显打通,所以这波结束之后还是决定稍微学一下,就算不能懂原理,起码要缝出来一套能应对各个版本的内存马。。。以及遇到问题的时候起码有一套调试环境能排错

由于java水平有限,写出来的东西基本上都是从网上东拼西凑出来的,超级缝合怪,但是不得不说我缝合的水平真是越来越好了,因为感觉就大家的内存马理论都学得很好,我学点皮毛能用就行了,所以就叫缝合笔记8

3,2,1,开缝!

回显

实际上要做的操作在本质上区别不大,都是找到tomcat下的某个对象,然后修改其属性之类的。

回显的话就拿到request和response对象就可以了,所以操作起来简单一点,并且payload也短一些,在例如shiro等受到tomcatheader长度7300(听说的)的限制下能使用的几率更大
当然网上现在缩短payload的方法也一搜一堆,用javassist去行号之类的。或者直接反射改tomcat的最大长度限制之类的也行

spring回显

回显似乎存在着更加通用的手法,因为ctf中的环境springboot居多,所以之前大多是用一个springboot的祖传回显进行的,spring下的回显似乎很稳定,从org.springframework.web.context.request.RequestContextHolder类中获取request和response,网上都有现成的,直接复制粘贴就打通一切了。

tomcat回显

tomcat下就稍微麻烦一些。jsp的话本来就有request和response对象,当然这也不能加内存马了,就是直接的jsp木马,更多的是考虑反序列化等代码执行的情况下进行回显

这里有一个注意点,tomcat从10开始讲javax转变为jakarta,并不知道有什么意义,但是对应的包名都需要重新修改一遍。

网上流行的payload是一个经典的线程遍历,直接从currentThread往上拿到全部现场,然后一个个找线程拿到对应的request和response对象,据说这个操作是全版本通用的回显捏。后续的内存马操作有部分和这个类似,所以这里也就不着重讲

不过说到底这些并不是今天的重点,回显用一次打一次还是有些不方便,不如直接一发内存马打进去一直用

内存马

在开始内存马之前,需要先掌握一些内存马相关的知识

spring层面的内存马有controller和interceptor,而tomcat层面的则是serverlet,filter和listener

每一种内存马都有自己的打法,但是打进去了效果都是一样的,网络上关于内存马的研究已经炉火纯青了,所以只需要百度一下疯狂缝合就基本上都能用。所以这篇文章也只是对看过的文章和踩过的小坑做记录,只要肯找网上现成的开箱即用的payload估计也能找到一堆

我这里就研究了一下现在最流行的filter内存马。

因为spring本身内置了tomcat,所以tomcat内存马同样适用于spring应用,就能一键打通了

在开始之前稍微回顾一下打进去一个filter内存马需要的条件

  1. 一个恶意filter类,实现了自己的doFilter方法(用的时候需要把传入的ServletRequest转为HttpServletRequest才能使用,不然会出问题。。。)
  2. 一个当前应用的StandardContext
  3. 装载了恶意类的FilterDefFilterMapApplicationFilterConfig,然后把他们添加进StandardContext

基本上各种操作都是围绕着如何获取StandardContext展开的

spring内存马

讲道理,能打tomcat的内存马应该就能打spring,不过spring有自己的获取StandardContext的方法,所以可以单独写一版

但实际上,获取StandardContext的前一步一般是获取一个HttpServletRequest对象,进而从这个对象里面getSession().getServletContext();,而回显的时候就已经提到了,spring可以直接从一个类中拿到request对象。

接下来需要对ServletContext的context对象进行迭代,有可能直接是一个StandardContext,也可能还是一个ServletContext,如果是ServletContext的话就继续访问其context属性直到其是一个StandardContext,这里对StandardContext需要反复迭代,文末会讲到

Object requestAttributes = Class.forName("org.springframework.web.context.request.RequestContextHolder").getMethod("getRequestAttributes", new Class[0]).invoke(null, new Object[0]);
HttpServletRequest request = (HttpServletRequest) requestAttributes.getClass().getMethod("getRequest", new Class[0]).invoke(requestAttributes, new Object[0]);
response = (HttpServletResponse) requestAttributes.getClass().getMethod("getResponse", new Class[0]).invoke(requestAttributes, new Object[0]);
final String name = "AutomneGreet";
ServletContext servletContext = request.getSession().getServletContext();

tomcat内存马

tomcat8-9

10也许也能用,只要把所有javax换成jakarta?但是感觉实战根本遇不上,也懒得下就没测
tomcat的话稍微麻烦一点点,不过如果是高版本其实会很简单,也可以直接从一个类中拿到

WebappClassLoaderBase webappClassLoaderBase = (WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardRoot resources = (StandardRoot)getField(webappClassLoaderBase, "resources");
StandardContext standardContext = (StandardContext) resources.getContext();

这里的反射在低版本的tomcat下是不必要的,可以直接webappClassLoaderBase.getResouces().getContext(),印象里是之前出了个啥洞(好像是那个被吹得沸沸扬扬的tomcat+java9以上用module改日志配置写jsp的)好像用到了getResouces(),这个函数本身就是被废弃的函数,然后tomcat就顺手把这个函数的返回值变成null了,就没法用了,但是反射一下还是能拿到

后续同spring部分

tomcat7

这个就变得很麻烦。。。因为上述步骤中的这个StandardRoot在tomcat7里面是不存在的,也不能像jsp一样直接获取。使用的手法和上述tomcat回显操作一致,直接遍历线程,从线程里面拿request对象,再从request对象里面拿到context

拿到context之后后续仍同。

但tomcat7和8-9又有一个区别,他filterMap那一套的类名不一样,需要单独引入依赖编译。。。如果有耐心写一个超级全反射可能也行,但是我没这个耐心。。。

// tomcat7
import org.apache.catalina.deploy.FilterDef;
import org.apache.catalina.deploy.FilterMap;

// tomcat 8-10
import org.apache.tomcat.util.descriptor.web.FilterDef;
import org.apache.tomcat.util.descriptor.web.FilterMap;

说起来当时写这个东西的时候就在怀疑,如果是反序列化之类的我这边类编译起来他那边会不会缺这个类进而失败呢?或者说有那种反序列化serializeUID之类的?后来想了一下,反序列化应该是直接templatesImpl执行类字节码,首先不会有反序列化的版本问题,然后这里用的都是对应版本的核心类,应该也不会出现缺类的问题。。
如果是scriptEngine或者JNDI注入的打法应该也是一个原理,无影响

tomcat6

我本来也不会考虑这种二十年前的玩意,但是这把就是遇到了,那也就只能考虑他了,他和7又有一点不同,因为他的filterDef的写法也不一样,内部的逻辑也有区别,由于只是缝合,所以没有太细看原理,缝起来能用就行,前半段还是用tomcat通用的request获取,后半段缝了别人的代码,测验通过可用。

所有文章都会放文末参考链接

踩过的怪坑

IDEA调试环境

之前一直只是手动建立一个webapp然后配好tomcat开始跑,但是由于没有引入依赖,IDEA里面一片红,调试也很麻烦,专门找了一下怎么调试,记录一下

建立JSP项目

右键左侧边栏最顶上的项目名,有一个Add Frameworks Support,选web Application就能天加一个web app项目,然后直接在右上角的config里面添加tomcat,会有一个报错,点fix就能解决

但是这个时候红字是拉满的,request对象都不认识的那种。虽然能跑了,但是IDEA不认识,只能在jsp代码里下断点,其他的地方都去不了。为了调试就需要引入依赖,并且要测反序列化能不能打肯定也要引入CC链,在Project Structure里面选择modules,右边dependencies添加,第一个是引入jar包,可以手动引入CC之类的依赖,第二个是library,就是能引入代码让IDEA调试,添加你的tomcat就能用了,基本上红字消除一半,剩下的一半是因为idea默认的tomcat的lib里只有两个jar包,双击添加的tomcat依赖,直接把tomcat下的lib文件夹整个作为依赖就没有红字了,并且接下来的调试也是为所欲为所欲为所欲为,吴迪。好像配的有问题的时候problem那里也会显示,没事多点点也许就解决了。idea真不错啊

springboot调试

这个简单,springboot本身就内嵌了tomcat,maven项目一把梭就行了,但是感觉想更改内嵌的tomcat版本就很玄幻。。。从网上找的pom里面添加一个tomcat.version,只能把小版本变一下,把tomcat9换成8就歇逼了。

好像能改springboot版本更改其内嵌的tomcat,不过估计也就换到8,懒得整了

TemplatesImpl实现细节

TemplatesImpl是通过defineClass加载类字节码执行里面的static字段实现命令执行的,然而我们在这里定义了一个自己构造的Filter内部类,直接用javassist获取字节码的时候是会忽略这个内部类的。。。。所以没有这个内部类的话payload跑起来会报一个类找不到的错误,怎么打都打不通。专门搜了下,javassist有专门的获取内部类字节码的方法,而TemplatesImpl的bytecode本身就是一个二维数组,支持多个类的定义,这里先把内部类放前面先定义,再触发恶意类的payload即可

        ClassPool pool = ClassPool.getDefault();
        CtClass ctClazz = pool.get(TomcatFilter6.class.getName());
        CtClass[] nestedClazz = ctClazz.getNestedClasses();
        byte[][] targetBytes = new byte[1 + nestedClazz.length][];
        // 内存马里面定义了自己写的类,内部类得单独拿出来处理,应该还得在payload实例化前先定义,不然到时候要用的时候找不到定义
        for (int i = 0; i < nestedClazz.length; i++) {
            targetBytes[i] = nestedClazz[i].toBytecode();
        }
        byte[] classBytes = ctClazz.toBytecode();
        targetBytes[targetBytes.length - 1] = classBytes;

另一种写法是让自己的payload类同时extends AbstractTransletimplement Filter,同时实现两个类的方法,然后Filter实例化的时候直接实例化自己。不过两个方法payload长度没啥变化,都可行吧

类字段获取不到

因为tomcat和spring和jsp环境不同 ,不同情况下拿到的对象好像基础套的层数也不一样,反射getDeclaredField会经常拿不到对应对象,看了下文档才知道,getField可以获得公开的字段,包括继承下来的,而getDeclaredField虽然可以拿私有字段,但是不能拿到继承的字段,这样子就只能getSuperClass往上找,但是你也不知道要找几层,主要还是不同环境下套的层数可能有差异,所以还是得专门写个方法来处理一下

    public static Object getField(Object object, String fieldName) {
        Field declaredField;
        Class clazz = object.getClass();
        while (clazz != Object.class) {
            try {
                declaredField = clazz.getDeclaredField(fieldName);
                declaredField.setAccessible(true);
                return declaredField.get(object);
            } catch (Exception e) {
            }
            clazz = clazz.getSuperclass();
        }
        return null;
    }

StandardContext获取不到

也是上述类似问题,从ServletContext中不知道要几层才能拿到StandardContext,所以也套一个for循环

StandardContext standardContext = null;
while (standardContext == null) {
    Field field = servletContext.getClass().getDeclaredField("context");
    field.setAccessible(true);
    Object o = field.get(servletContext);
    if (o instanceof ServletContext) {
        servletContext = (ServletContext) o;
    } else if (o instanceof StandardContext) {
        standardContext = (StandardContext) o;
    }
}

参考链接

tomcat6、7、8、9内存马
Java内存马:一种Tomcat全版本获取StandardContext的新方法
JavaWeb 内存马一周目通关攻略
Tomcat Servlet-Api内存马总结及代码实现