GHSA-jj95-55cr-9597: Aerospike Java Client vulnerable to unsafe deserialization of server responses
GitHub Security Lab (GHSL) Vulnerability Report: `GHSL-2023-044`The [GitHub Security Lab](https://securitylab.github.com) team has identified a potential security vulnerability in [Aerospike Java Client](https://github.com/aerospike/aerospike-client-java/).We are committed to working with you to help resolve this issue. In this report you will find everything you need to effectively coordinate a resolution of this issue with the GHSL team.If at any point you have concerns or questions about this process, please do not hesitate to reach out to us at `securitylab@github.com` (please include `GHSL-2023-044` as a reference).If you are _NOT_ the correct point of contact for this report, please let us know!SummaryThe Aerospike Java client is a Java application that implements a network protocol to communicate with an Aerospike server. Some of the messages received from the server contain Java objects that the client deserializes when it encounters them without further validation. Attackers that manage to trick clients into communicating with a malicious server can include especially crafted objects in its responses that, once deserialized by the client, force it to execute arbitrary code. This can be abused to take control of the machine the client is running on.ProductAerospike Java ClientTested Version[6.1.7](https://github.com/aerospike/aerospike-client-java/releases/tag/6.1.7)DetailsIssue: Unsafe deserialization of server responses (`GHSL-2023-044`)The Aerospike Java client implements different ways of communicating with an Aerospike server to perform several operations. Asynchronous commands can be executed using the Netty framework using the `NettyCommand` class. This class includes an `InboundHandler` that extends Netty's `ChannelInboundHandlerAdapter`, which handles inbound data coming from the Netty channel established with the server. This is implemented in the `channelRead` method:[`client/src/com/aerospike/client/async/NettyCommand.java:1157`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/async/NettyCommand.java#L1157)The incoming `msg` object is handled by the `NettyCommand.read` method, which behaves differently depending on the `state` variable. Several states produce paths to the vulnerable code — for instance, we will follow the path through `AsyncCommand.COMMAND_READ_HEADER`:[`/client/src/com/aerospike/client/async/NettyCommand.java:489`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/async/NettyCommand.java#L489)Some bytes are read from the message buffer and saved in `command.dataBuffer` in the `readSingleHeader` method, after which `parseSingleBody` is called:[`client/src/com/aerospike/client/async/NettyCommand.java:596`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/async/NettyCommand.java#L596)`parseSingleBody` simply delegates on `AsyncCommand.parseCommandResult`, which unless the message is compressed, directly calls `AsyncCommand.parseResult`. The implementation of this method depends on the command type. For an `AsyncRead` command, we have the following:[`client/src/com/aerospike/client/async/AsyncRead.java:68`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/async/AsyncRead.java#L68)It can be seen that several fields are read from the message's bytes, and then a call to `Command.parseRecord` happens:[`client/src/com/aerospike/client/command/Command.java:2083`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/command/Command.java#L2083)`Buffer.bytesToParticle` converts the remaining bytes in the data buffer depending on the `particleType` field. We're interested in the `JBLOB` case:[`client/src/com/aerospike/client/command/Buffer.java:53`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/command/Buffer.java#L53)In `bytesToObject`, the deserialization of an object from the message bytes happens:[`client/src/com/aerospike/client/command/Buffer.java:300`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/command/Buffer.java#L300)NOTE: Take into account that there exists a similar sink, that can be reached in a similar way, in `Unpacker.unpackBlock`:[`client/src/com/aerospike/client/util/Unpacker.java:227`](https://github.com/aerospike/aerospike-client-java/blob/e40a49b3db0d2b3d45068910e1cb9d917c795315/client/src/com/aerospike/client/util/Unpacker.java#L227)This vulnerability was discovered with the help of [CodeQL](https://codeql.github.com/).ImpactThis issue may lead to Remote Code Execution (RCE) in the Java client.RemediationAvoid deserialization of untrusted data if at all possible. If the architecture permits it then use other formats instead of serialized objects, for example JSON or XML.  However, these formats should not be deserialized into complex objects because this provides further opportunities for attack. For example, XML-based deserialization attacks are possible through libraries such as XStream and XmlDecoder.Alternatively, a tightly controlled whitelist can limit the vulnerability of code but be aware of the existence of so-called Bypass Gadgets, which can circumvent such protection measures.ResourcesTo exploit this vulnerability, a malicious Aerospike server is needed. For the sake of simplicity, we implemented a mock server with hardcoded responses, with the only goal of reaching the vulnerable code of the client. To be able to easily reproduce this, we used the client's examples with the `-netty` flag, specifically the `AsyncPutGet`, which uses an `AsyncRead`. The examples point to `localhost:3000` by default, so we set up a simple Netty TCP server listening on that port, which replicates responses previously intercepted from a real Aerospike server and returns them to the client, until the `AsyncRead` command happens. Then, our server injects the malicious response:`AttackMessage` is a class that hardcodes the necessary data to deliver the payload:And it's finally encoded and delivered to the client through the network using a `MessageToByteEncoder` from Netty:The specific deserialization payload that needs to be used depends on the deserialization gadgets available in the classpath of the application using the Aerospike client. Again, for simplicity, we assumed the victim application uses Apache Commons Collections 4.0, which contains a well-known deserialization gadget:In which case, the malicious payload file could be generated using [`ysoserial`](https://github.com/frohoff/ysoserial) as follows:GitHub Security AdvisoriesWe recommend you create a private [GitHub Security Advisory](https://help.github.com/en/github/managing-security-vulnerabilities/creating-a-security-advisory) for this finding. This also allows you to invite the GHSL team to collaborate and further discuss this finding in private before it is [published](https://help.github.com/en/github/managing-security-vulnerabilities/publishing-a-security-advisory).CreditThis issue was discovered and reported by the GitHub CodeQL team members [@atorralba (Tony Torralba)](https://github.com/atorralba) and [@joefarebrother (Joseph Farebrother)](https://github.com/joefarebrother).ContactYou can contact the GHSL team at `securitylab@github.com`, please include a reference to `GHSL-2023-044` in any communication regarding this issue.Disclosure PolicyThis report is subject to our [coordinated disclosure policy](https://securitylab.github.com/advisories#policy).
CVSS v3.1: 9.8