OpenRefine vulnerable to arbitrary file read in project import with mysql jdbc url attack
Description
OpenRefine is a powerful free, open source tool for working with messy data. Prior to version 3.7.5, an arbitrary file read vulnerability allows any unauthenticated user to read a file on a server. Version 3.7.5 fixes this issue.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
OpenRefine before 3.7.5 allows unauthenticated arbitrary file read via a crafted MySQL JDBC URL in database import.
Vulnerability
CVE-2023-41886 is an arbitrary file read vulnerability in OpenRefine, a data cleaning tool. The root cause lies in the insecure construction of database URLs within the MySQL connection manager. Prior to version 3.7.5, the JDBC URL was built by concatenating configuration parameters without proper sanitization, allowing an attacker to inject additional connection properties such as allowLoadLocalInfile. [2][4]
Exploitation
An unauthenticated attacker can exploit this by setting up a malicious MySQL server and crafting a database import request with a specially crafted JDBC URL. The attacker controls parameters like the database name, which can include ?allowLoadLocalInfile=true# to enable local file loading. When OpenRefine connects to the attacker's server, it reads arbitrary files from the OpenRefine server (e.g., /etc/passwd) and sends them to the malicious server. [4]
Impact
Successful exploitation allows an unauthenticated remote attacker to read any file on the OpenRefine server. This can lead to disclosure of sensitive information, including configuration files, credentials, or other data. [1][4]
Mitigation
The vulnerability is fixed in OpenRefine version 3.7.5. The fix changes the URL construction to use a URI object, which properly encodes parameters and prevents injection. [2] Users are strongly advised to update to the latest version.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
org.openrefine:databaseMaven | < 3.7.5 | 3.7.5 |
Affected products
2- Range: <= 3.7.4
Patches
12de1439f5be6Merge pull request from GHSA-qqh2-wvmv-h72m
6 files changed · +48 −26
extensions/database/src/com/google/refine/extension/database/DatabaseConfiguration.java+16 −0 modified@@ -29,6 +29,9 @@ package com.google.refine.extension.database; +import java.net.URI; +import java.net.URISyntaxException; + public class DatabaseConfiguration { private String connectionName; @@ -123,4 +126,17 @@ public String toString() { + databaseSchema + ", useSSL=" + useSSL + "]"; } + public URI toURI() { + try { + return new URI( + "jdbc:" + databaseType.toLowerCase(), + databaseHost + ((databasePort == 0) ? "" : (":" + databasePort)), + "/" + databaseName, + useSSL ? "useSSL=true" : null, + null + ); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } }
extensions/database/src/com/google/refine/extension/database/mariadb/MariaDBConnectionManager.java+1 −9 modified@@ -134,7 +134,7 @@ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boo Class.forName(type.getClassPath()); DriverManager.setLoginTimeout(10); - String dbURL = getDatabaseUrl(databaseConfiguration); + String dbURL = databaseConfiguration.toURI().toString(); connection = DriverManager.getConnection(dbURL, databaseConfiguration.getDatabaseUser(), databaseConfiguration.getDatabasePassword()); @@ -162,14 +162,6 @@ public void shutdown() { logger.warn("Non-Managed connection could not be closed. Whoops!", e); } } - } - private static String getDatabaseUrl(DatabaseConfiguration dbConfig) { - - int port = dbConfig.getDatabasePort(); - return "jdbc:" + dbConfig.getDatabaseType().toLowerCase() + "://" + dbConfig.getDatabaseHost() - + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName(); - - } }
extensions/database/src/com/google/refine/extension/database/mysql/MySQLConnectionManager.java+1 −8 modified@@ -129,7 +129,7 @@ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boo return connection; } } - String dbURL = getDatabaseUrl(databaseConfiguration); + String dbURL = databaseConfiguration.toURI().toString(); Class.forName(type.getClassPath()); // logger.info("*** type.getClassPath() ::{}, {}**** ", type.getClassPath()); @@ -166,11 +166,4 @@ public void shutdown() { } - private String getDatabaseUrl(DatabaseConfiguration dbConfig) { - - int port = dbConfig.getDatabasePort(); - return "jdbc:" + dbConfig.getDatabaseType() + "://" + dbConfig.getDatabaseHost() - + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName() + "?useSSL=" + dbConfig.isUseSSL(); - - } }
extensions/database/src/com/google/refine/extension/database/pgsql/PgSQLConnectionManager.java+1 −8 modified@@ -136,7 +136,7 @@ public Connection getConnection(DatabaseConfiguration databaseConfiguration, boo Class.forName(type.getClassPath()); DriverManager.setLoginTimeout(10); - String dbURL = getDatabaseUrl(databaseConfiguration); + String dbURL = databaseConfiguration.toURI().toString(); connection = DriverManager.getConnection(dbURL, databaseConfiguration.getDatabaseUser(), databaseConfiguration.getDatabasePassword()); @@ -165,11 +165,4 @@ public void shutdown() { } - private static String getDatabaseUrl(DatabaseConfiguration dbConfig) { - - int port = dbConfig.getDatabasePort(); - return "jdbc:" + dbConfig.getDatabaseType().toLowerCase() + "://" + dbConfig.getDatabaseHost() - + ((port == 0) ? "" : (":" + port)) + "/" + dbConfig.getDatabaseName(); - - } }
extensions/database/src/com/google/refine/extension/database/sqlite/SQLiteConnectionManager.java+8 −1 modified@@ -35,6 +35,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.net.URI; +import java.net.URISyntaxException; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; @@ -66,7 +68,12 @@ public static SQLiteConnectionManager getInstance() { } public static String getDatabaseUrl(DatabaseConfiguration dbConfig) { - return "jdbc:" + dbConfig.getDatabaseType().toLowerCase() + ":" + dbConfig.getDatabaseName(); + try { + URI uri = new URI("jdbc:" + dbConfig.getDatabaseType().toLowerCase(), dbConfig.getDatabaseName(), null); + return uri.toASCIIString(); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } } /**
extensions/database/tests/src/com/google/refine/extension/database/DatabaseConfigurationTest.java+21 −0 added@@ -0,0 +1,21 @@ +package com.google.refine.extension.database; + +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class DatabaseConfigurationTest { + + @Test + public void testToURI() { + DatabaseConfiguration config = new DatabaseConfiguration(); + config.setDatabaseType("mysql"); + config.setDatabaseHost("my.host"); + // maliciously crafted database name which attempts to enable local file reads for an exploit + config.setDatabaseName("test?allowLoadLocalInfile=true#"); + + String url = config.toURI().toString(); + // the database name is escaped, preventing the exploit + assertEquals(url, "jdbc:mysql://my.host/test%3FallowLoadLocalInfile=true%23"); + } +}
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4- github.com/advisories/GHSA-qqh2-wvmv-h72mghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-41886ghsaADVISORY
- github.com/OpenRefine/OpenRefine/commit/2de1439f5be63d9d0e89bbacbd24fa28c8c3e29dghsax_refsource_MISCWEB
- github.com/OpenRefine/OpenRefine/security/advisories/GHSA-qqh2-wvmv-h72mghsax_refsource_CONFIRMWEB
News mentions
0No linked articles in our index yet.