CVE-2019-17556: Unsafe deserialization in Apache Olingo
Some time ago I wrote about one security issue which I found in the library. This post describes another little vulnerability in Apache Olingo. The issue has been fixed in the 4.7.0 release as well.
By the way, Apache Olingo is a Java library that implements the Open Data Protocol (OData). This protocol allows the creation and consumption of queryable and interoperable RESTful APIs in a simple way.
The issue
Apache Olingo has the AbstractService
class which is part of the public API. According to the Javadoc, the class is an entry point for the proxy mode. It gives access to entity container instances. The constructor of the class takes compressed metadata. More precisely, the constructor expects a serialized XMLMetadata
object which is encoded with Base64 and compressed with GZIP. Which serialization mechanism does it use? It’s the default Java serialization which is known to be vulnerable to deserialization attacks. Here is how the constructor processes the compressed metadata:
protected AbstractService(final String compressedMetadata, final String metadataETag,
final ODataServiceVersion version, final String serviceRoot, final boolean transactional) {
ByteArrayInputStream bais = null;
GZIPInputStream gzis = null;
ObjectInputStream ois = null;
XMLMetadata metadata = null;
try {
// use commons codec's Base64 in this fashion to stay compatible with Android
bais = new ByteArrayInputStream(new Base64().decode(compressedMetadata.getBytes("UTF-8")));
gzis = new GZIPInputStream(bais);
ois = new ObjectInputStream(gzis);
metadata = (XMLMetadata) ois.readObject();
First, it uses theBase64.decode()
method to decode the input data. Then, it converts the decoded data to a byte array and wraps the array into a ByteArrayInputStream
instance. Next, the input stream is wrapped into a GZIPInputStream
object to decompress the data. Finally, the GZIP input stream is wrapped into an ObjectInputStream
to deserialize the metadata.
By default, the ObjectInputStream
doesn’t apply any check when it deserialized data. It means that if an attacker is able to feed malicious metadata to a class that extends the AbstractService
, then in the worse case it can result in executing the attacker’s code.
The severity of the issue highly depends on how a particular application uses the AbstractService
class, and where the metadata comes from.
If an application loads the metadata from a protected resource, the attacker has to find a way how to write malicious metadata to the protected resource. It may be not easy. For example, if the metadata is loaded from a properly configured database, then the attacker has to find a SQL injection or another way that would allow him to inject malicious data into the database. Or, if the application loads the metadata from a file which has proper access rights, then the attacker has to find a way how he can write to the file.
On the other hand, if an application receives the metadata via the network, it may be much easier for the attacker to make the application load his malicious data.
Nevertheless, the project maintainers agreed to make the AbstractService
class a bit more safe to use.
The solution
The problem has been mitigated by implementing a whitelist for classes which are allowed for deserialization. It may be done, for example, by extending the ObjectInputStream
class and implementing the whitelisting in the overridden resolveClass()
method. But it was not necessary because Olingo uses Apache IO which offers the ValidatingObjectInputStream
class for implementing such a whitelist. Another option might be the filters that have been added in JEP 290, but this way would require using newer versions of Java.
The fix was quite simple:
- Added a factory method
createObjectInputStream()
which creates aValidatingObjectInputStream
instance configured with a whitelist of classes which are allowed for deserialization. - The constructor of the
AbstractService
class calls thecreateObjectInputStream()
method to get a deserializer for metadata. - By default, the whitelist contains only classes from the
org.apache.olingo
package. - If a user would like to extend the default whitelist, he can override the
getAllowedClasses()
method to make it return a list of allowed classes.
The patch has been released in Apache Olingo 4.7.0.
Is my application vulnerable?
Definitely, not each and every application which uses older versions of Apache Olingo is vulnerable. There are at least two things which need to be checked in an application which uses Apache Olingo:
- Does it uses the
AbstractService
class? - Is there a way how someone can feed malicious data to the class?
Answers to the questions above depend on a specific application. If both answers are yes, then it’s likely that the application is vulnerable.
Conclusion
This issue is another example of a deserialization vulnerability when an application uses the default Java deserialization mechanism. Not all applications, which use Apache Olingo, are vulnerable by default. But it may be better to stay on the safe side and update the library.