浅析GeoServer property 表达式注入代码执行(CVE-2024-36401)
2024-7-3 16:8:26 Author: y4tacker.github.io(查看原文) 阅读量:15 收藏

可以看到在org.geoserver.wfs.GetPropertyValue#run,红框中的代码从请求中获取了valuereference参数,之后调用工厂类的property方法获取PropertyName对象

接下来再来看看evaluate的调用,在这里会通过PropertyAccessors.findPropertyAccessors获取合适的属性访问器,之后遍历调用其get方法,其中就包括了org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor#get,官方公告列出来的就有这个

以我下载的war为例,先看web.xml,通常而言这就是我们项目的主入口,但是点进去一看,在配置文件中大多只有Servlet的过滤器链的配置,而没有具体接口的配置,当然唯一的可以看到将请求都通过spring的DispatcherServlet派发

因此接下来我们就得看看,spring项目的一些其他配置文件,比如\geoserver\WEB-INF\lib\gs-wfs-2.21.3.jar!\applicationContext.xml,看着这个配置文件就会更为亲切,当然又扯远了,回到正文

在这个项目中,org.geoserver.ows.Dispatcher继承了AbstractController并实现了handleRequestInternal方法

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
protected ModelAndView handleRequestInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws Exception {
this.preprocessRequest(httpRequest);
Request request = new Request();
request.setHttpRequest(httpRequest);
request.setHttpResponse(httpResponse);
Service service = null;

try {
try {
request = this.init(request);
REQUEST.set(request);

Object result;
try {
service = this.service(request);
} catch (Throwable var11) {
this.exception(var11, (Service)null, request);
result = null;
return (ModelAndView)result;
}

if (request.getError() != null) {
throw request.getError();
}

Operation operation = this.dispatch(request, service);
request.setOperation(operation);
if (request.isSOAP()) {
this.flagAsSOAP(operation);
}

result = this.execute(request, operation);
if (result != null) {
this.response(result, request, operation);
return null;
}
} catch (Throwable var12) {
if (isSecurityException(var12)) {
throw (Exception)var12;
}

this.exception(var12, service, request);
}

return null;
} finally {
this.fireFinishedCallback(request);
REQUEST.remove();
}
}

Object execute(Request req, Operation opDescriptor) throws Throwable {
Service serviceDescriptor = opDescriptor.getService();
Object serviceBean = serviceDescriptor.getService();
Object[] parameters = opDescriptor.getParameters();
Object result = null;

try {
if (serviceBean instanceof DirectInvocationService) {
String operationName = opDescriptor.getId();
result = ((DirectInvocationService)serviceBean).invokeDirect(operationName, parameters);
} else {
Method operation = opDescriptor.getMethod();
result = operation.invoke(serviceBean, parameters);
}
} catch (Exception var8) {
if (var8.getCause() != null) {
throw var8.getCause();
}

throw var8;
}

return this.fireOperationExecutedCallback(req, opDescriptor, result);
}

Operation dispatch(Request req, Service serviceDescriptor) throws Throwable {
if (req.getRequest() == null) {
String msg = "Could not determine geoserver request from http request " + req.getHttpRequest();
throw new ServiceException(msg, "MissingParameterValue", "request");
} else {
boolean exists = this.operationExists(req, serviceDescriptor);
if (!exists && req.getKvp().get("request") != null) {
req.setRequest(normalize(KvpUtils.getSingleValue(req.getKvp(), "request")));
exists = this.operationExists(req, serviceDescriptor);
}

Object serviceBean = serviceDescriptor.getService();
Method operation = OwsUtils.method(serviceBean.getClass(), req.getRequest());
if (operation != null && exists) {
Object[] parameters = new Object[operation.getParameterTypes().length];

for(int i = 0; i < parameters.length; ++i) {
Class<?> parameterType = operation.getParameterTypes()[i];
if (parameterType.isAssignableFrom(HttpServletRequest.class)) {
parameters[i] = req.getHttpRequest();
} else if (parameterType.isAssignableFrom(HttpServletResponse.class)) {
parameters[i] = req.getHttpResponse();
} else if (parameterType.isAssignableFrom(InputStream.class)) {
parameters[i] = req.getHttpRequest().getInputStream();
} else if (parameterType.isAssignableFrom(OutputStream.class)) {
parameters[i] = req.getHttpResponse().getOutputStream();
} else {
Object requestBean = null;
Throwable t = null;
boolean kvpParsed = false;
boolean xmlParsed = false;
if (req.getKvp() != null && req.getKvp().size() > 0) {
try {
requestBean = this.parseRequestKVP(parameterType, req);
kvpParsed = true;
} catch (Exception var14) {
t = var14;
}
}

if (req.getInput() != null) {
requestBean = this.parseRequestXML(requestBean, req.getInput(), req);
xmlParsed = true;
}

if (requestBean == null) {
if (t != null) {
throw t;
}

if ((!kvpParsed || !xmlParsed) && (kvpParsed || xmlParsed)) {
if (kvpParsed) {
throw new ServiceException("Could not parse the KVP for: " + parameterType.getName());
}

throw new ServiceException("Could not parse the XML for: " + parameterType.getName());
}

throw new ServiceException("Could not find request reader (either kvp or xml) for: " + parameterType.getName() + ", it might be that some request parameters are missing, please check the documentation");
}

Method setBaseUrl = OwsUtils.setter(requestBean.getClass(), "baseUrl", String.class);
if (setBaseUrl != null) {
setBaseUrl.invoke(requestBean, ResponseUtils.baseURL(req.getHttpRequest()));
}

if (requestBean != null) {
if (req.getService() == null) {
req.setService(this.lookupRequestBeanProperty(requestBean, "service", false));
}

if (req.getVersion() == null) {
req.setVersion(normalizeVersion(this.lookupRequestBeanProperty(requestBean, "version", false)));
}

if (req.getOutputFormat() == null) {
req.setOutputFormat(this.lookupRequestBeanProperty(requestBean, "outputFormat", true));
}

parameters[i] = requestBean;
}
}
}

if (this.citeCompliant) {
if (!"GetCapabilities".equalsIgnoreCase(req.getRequest())) {
if (req.getVersion() == null) {
throw new ServiceException("Could not determine version", "MissingParameterValue", "version");
}

if (!req.getVersion().matches("[0-99].[0-99].[0-99]")) {
throw new ServiceException("Invalid version: " + req.getVersion(), "InvalidParameterValue", "version");
}

boolean found = false;
Version version = new Version(req.getVersion());
Iterator var20 = this.loadServices().iterator();

while(var20.hasNext()) {
Service service = (Service)var20.next();
if (version.equals(service.getVersion())) {
found = true;
break;
}
}

if (!found) {
throw new ServiceException("Invalid version: " + req.getVersion(), "InvalidParameterValue", "version");
}
}

if (req.getService() == null) {
throw new ServiceException("Could not determine service", "MissingParameterValue", "service");
}
}

Operation op = new Operation(req.getRequest(), serviceDescriptor, operation, parameters);
return this.fireOperationDispatchedCallback(req, op);
} else {
String msg = "No such operation " + req;
throw new ServiceException(msg, "OperationNotSupported", req.getRequest());
}
}
}

从上面的代码中我们很容易发现,通过dispatch的代码我们很容易发现会通过这个request对象查找对应的方法,获取到后之后再通过execute执行,因此答案也就有了

相比较其他利用还是觉得GetProperty的利用比较舒服,不像GetFeature之类的里面到处都是触发点,会导致xpath被解析很多次,当然poc就不贴了学习思路为主,在GetProperty中也有一个比较好用的对抗流量设备的点

在这里可以看到在获取参数时会把[]中的内容替换为空,但很可惜是贪婪匹配(至少我这个老代码是这样的),不过也可以拿来做一些利用,比如我们的java.lang.Runtime可以写成java.lang.Ru[Hacked By Y4]ntime


文章来源: https://y4tacker.github.io/2024/07/03/year/2024/7/%E6%B5%85%E6%9E%90GeoServer-property-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C-CVE-2024-36401/
如有侵权请联系:admin#unsafe.sh