MySQL Connector/J - Unexpected deserialisation of Java objects
A malicious MySQL database or a database containing malicious contents can obtain remote code execution in applications connecting using MySQL Connector/J."
A malicious MySQL database or a database containing malicious contents can obtain remote code execution in applications connecting using MySQL Connector/J.
Technical Background
MySQL Connector/J is a driver for MySQL adhering to the Java JDBC interface. One of the features offered by MySQL Connector/J is support for automatic serialization and deserialization of Java objects, to make it easy to store arbitrary objects in the database.
When deserializing objects, it is important to never deserialize objects received from untrusted sources. As certain functions are automatically called on objects during deserialization and destruction, attackers can combine objects in unexpected ways to call specific functions, eventually leading to the execution of arbitrary code (depending on which classes are loaded). As the code is often executed as soon as the object is constructed or destructed, additional type-checking on the constructed object is not enough to protect against this.
MySQL Connector/J requires the flag autoDeserialize
to be set to true
before
objects are automatically deserialized, which should only be set when the
database and its contents are fully trusted by the application.
Vulnerability
During a short evaluation of the MySQL Connector/J source code, a method was found to deserialize objects from the database when this flag is not set and when API functions are used which do not imply the deserialization of objects at all.
The conditions are the following:
-
The flag
useServerPrepStmts
is set to true. With this flag enabled, the server caches prepared SQL statements and arguments are sent to it separately. As this allows statements to be reused, it is often enabled for increased performance. -
The application is reading from a column having type
BLOB
, or the similarTINYBLOB
,MEDIUMBLOB
orLONGBLOB
. -
The application is reading from this column using
.getString()
or one of the functions reading numeric values (which are first read as strings and then parsed as numbers). Notably not.getBytes()
or.getObject()
.
When these conditions are met, MySQL Connector/J will check if the data
starts with 0xAC 0xED
(the magic bytes of a serialized Java object) and if so,
attempt to deserialize it and try to convert it to a string.
The vulnerable code:
case Types.LONGVARBINARY:
if (!field.isBlob()) {
return extractStringFromNativeColumn(columnIndex, mysqlType);
} else if (!field.isBinary()) {
return extractStringFromNativeColumn(columnIndex, mysqlType);
} else {
byte[] data = getBytes(columnIndex);
Object obj = data;
if ((data != null) && (data.length >= 2)) {
if ((data[0] == -84) && (data[1] == -19)) {
// Serialized object?
try {
ByteArrayInputStream bytesIn = new ByteArrayInputStream(data);
ObjectInputStream objIn = new ObjectInputStream(bytesIn);
obj = objIn.readObject();
objIn.close();
bytesIn.close();
} catch (ClassNotFoundException cnfe) {
throw SQLError.createSQLException(Messages.getString("ResultSet.Class_not_found___91") + cnfe.toString()
+ Messages.getString("ResultSet._while_reading_serialized_object_92"), getExceptionInterceptor());
} catch (IOException ex) {
obj = data; // not serialized?
}
}
return obj.toString();
}
src/com/mysql/jdbc/ResultSetImpl.java#L3422-L3450
The combination of a column of type BLOB
and the vulnerable functions does not
follow common best practices for using a database: BLOB
columns are meant to
store arbitrary binary data, which should be read using .getBytes()
. However,
there are many scenarios where this can still be exploited by an attacker:
-
An application does not follow best practices and stores text (or numbers) in a column of type
BLOB
and an attacker can insert arbitrary binary data into this column. -
An application is configured to connect to a remote untrusted database or over an unencrypted connection which is intercepted by an attacker.
-
An application has an SQL injection vulnerability which allows an attacker to change the type of a columm to
BLOB
.
Impact
An attacker who is able to abuse this vulnerability, can have the application deserialize arbitrary objects. The direct impact is that the attacker can call into any loaded classes. Often, but depending on the application, this can be leveraged to gain code execution by calling into loaded classes that perform actions on files or system commands.
Resolution
The vulnerability can be resolved by updating MySQL Connector/J to version
5.1.41 and ensuring the flag autoDeserialize
is not set.
Mitigation
This vulnerability can be mitigated on older versions by ensuring the flags
autoDeserialize
and useServerPrepStmts
are not set.
Conclusion
MySQL Connector/J will (under specific conditions) unexpectedly deserialize objects from a MySQL database, allowing remote code execution. This could be used by attackers to escalate access to a database into remote code execution or possibly allow remote code execution by any user who can insert data into a database.
Timeline
2017-01-23: Issue reported to secalert_us@oracle.com.
2017-01-23: Received a confirmation that the bug was under investigation.
2017-01-27: Publicly fixed in commit 6189e718de5b6c6115aee45dd7a480081c129d68
2017-02-24: Received an automatic email that a fix is ready and that an advisory will be published in a future Critical Patch Update.
2017-02-28: Fix released in version 5.1.41.
2017-03-24: Received an automatic email that a fix is ready and that an advisory will be published in a future Critical Patch Update.
2017-04-18: Oracle published Critical Patch Update April 2017, without this issue.
2017-04-19: Contacted Oracle to ask if a CVE number has been assigned to this issue.
2017-04-19: Received a reply from Oracle that they were verifying which versions are vulnerable.
2017-04-21: Oracle published revision 2 of the Critical Patch Update of April 2017, including this issue.