Red Team Diary, Entry #2: Stealthily Backdooring CMS Through Redis’ Memory Space

Dimitrios Bougioukas
9 min readOct 23, 2019

--

Hi there,

This is Dimitrios Bougioukas, Director of IT Security Training Services at eLearnSecurity.

Our series of (red and blue) posts continues with the second entry of the Red Team Diary.

In this post I will guide you through a stealthy CMS backdooring technique, that I came up with when developing eLearnSecurity’s WAPTv3 course. The technique uses Redis’ memory space as its transfer medium.

Before going through the details of the attack, I will leverage the opportunity to also talk about Redis and its security posture in general.

Redis Overview

Redis is an in-memory data structure store, used as a database, cache and message broker. The data structure store term actually means it is a key-value store. These keys can be strings, hashes, lists and sets.

Default ports used by Redis:

❑ 6379

❑ 16379

❑ 26379

Redis’ Security Posture:

  1. Securing a Redis database via a password is optional
  2. No built-in TLS
  3. Redis features an interesting mode called “protected mode”. As of Redis 3.2.0, whenever Redis is executed with the default configuration (binding all the interfaces) and without any password in order to access it, it enters a special mode called protected mode. While in this mode, Redis only replies to queries from the loopback interfaces only.
  4. Redis’ documentation states that “Redis is designed to be accessed by trusted clients inside trusted environment”. This pretty much summarizes Redis’ threat model.
  5. As of Redis version 2.8, the LUA scripting ability was introduced. The LUA engine is properly sandboxed and offers enough security. Global variables are protected. EVAL and EVALSHA are used to evaluate scripts using the Lua interpreter built into Redis. In addition to that, the number of available libraries to leverage is limited. Interesting commands, include SCRIPT KILL, SCRIPT LOAD and SCRIPT EXISTS . When a script is running, no other functions can be accessed or any operations can be performed.
  6. According to Redis’ documentation “It is possible to disable commands in Redis or to rename them into an unguessable name, so that normal clients are limited to a specified set of commands.”
  7. Arbitrary file rewrite commands have occurred in the past, as follows.
    CONFIG GET (Gives the Current set of Configuration), CONFIG SET (Sets the configuration of the default command), CONFIG SET dir /var/www
  8. File name enumeration has also occurred in the past, due to dofile (open file in LUA) being allowed. For example:
    eval “dofile(‘/var/www’)” 0 (Directory exists but can’t open file), eval “dofile(‘/var/test’)” 0 (No such directory exists)

Redis Pentesting Basics

As you already know, I love covering attacking (or defending) techniques through realistic scenarios. Suppose we are performing a penetration test against the 175.12.97.0/24 range and we come across the 175.12.97.98 machine.

We can scan the 175.12.97.98 machine for any exposed Redis instances, as follows.

# nmap -p 6379 175.12.97.98

It looks like a Redis instance is exposed!

Let’s interact with it… First, let’s check if the exposed Redis instance requires authentication, as follows.

# telnet 175.12.97.98 6379

The exposed Redis instance doesn’t require authentication!

To better interact with a Redis instance, we can use the redis-cli tool. This tool can be installed as follows.

# apt-get install redis-tools

# redis-cli -h 175.12.97.98

Interesting commands to try, after the above command is executed, are:

175.12.97.98:6379> INFO
175.12.97.98:6379> CONFIG GET pidfile
175.12.97.98:6379> CONFIG GET dir
175.12.97.98:6379> CONFIG GET dbfilename
175.12.97.98:6379> CONFIG GET logfile

Redis can execute sandboxed Lua scripts through the “EVAL” command. dofile() is a command that can be used to enumerate files and directories. dofile() is allowed by the sandbox in older Redis versions. We identified that the exposed Redis instance’s version is 2.6.14. dofile() is allowed by the sandbox in this version. We can leverage this for information gathering and enumeration purposes, as follows.

EVAL dofile(‘/var/’) 0
EVAL dofile(‘/var/www’) 0

In the first case we received a “Is a directory” message, because /var actually exists in Redis’ host

In the second case we received a “No such file or directory” message, because /var/www doesn’t exist in Redis’ host.

Note1: If the Lua script is syntactically invalid or tries to set global variables, some content of the target file will be leaked through the error messages. Find examples below.

EVAL dofile(‘/etc/issue’) 0

In our case, it looks like we are dealing with an Ubuntu 16.04-based system.

You can also try…

EVAL dofile(‘/etc/lsb-release’) 0

Note2: Always keep in mind, that dofile() can and should be called on valid LUA files in order to return defined variables that could contain critical information. Example:

# telnet 175.12.97.98 6379

EVAL dofile(‘/var/data/appplication/db.conf’);return(database.pass); 0

Note3: Another way to interact with Redis is through curl and the gopher protocol. You can do so, as follows.

# curl gopher://175.12.97.98:6379/_CONFIG%20GET%20dir — max-time 1

Stealthily Backdooring CMS Through Redis’ Memory

Having covered Redis’ pentesting basics, let’s cut to the chase. Once again suppose that we are performing a penetration test against the 175.12.97.0/24 range and we come across the 175.12.97.97 machine this time.

We can scan the 175.12.97.97 machine for any exposed Redis instances, as follows.

# nmap -p 6379 175.12.97.97

It looks like a Redis instance is exposed! Let’s interact with it…

First, let’s check if the exposed Redis instance requires authentication, as follows.

The exposed Redis instance is unprotected!

Let’s get some more information about this Redis instance with the help of the redis-cli tool, as follows.

175.12.9797:6379> INFO

We are dealing with a newer version of Redis this time.

In addition, let’s be more thorough this time around and scan all ports of the 175.12.97.97 machine, as follows.

# nmap -sS -p- 175.12.97.97

We will have to interact with each one of the identified ports to be sure we didn’t miss something important.

Surprisingly, port 1330 is actually a port of an SSH server, residing in an Ubuntu-based machine. We identified that, as follows.

# telnet 175.12.97.97 1330

Now, if we read Redis’ security page, we will come across the following statement.

“Internally, Redis uses all the well known practices for writing secure code, to prevent buffer overflows, format bugs and other memory corruption issues. However, the ability to control the server configuration using the CONFIG command makes the client able to change the working dir of the program and the name of the dump file. This allows clients to write RDB Redis files at random paths, that is a security issue that may easily lead to the ability to compromise the system and/or run untrusted code as the same user as Redis is running.”

To simplify things, the above means that we can “write” random files on Redis’ host (since we are dealing with an unprotected Redis instance). During our reconnaissance activities we identified that 175.12.97.97 features an SSH server. We can try writing our own SSH authorized key into the authorized_keys file in order to gain access, as follows.

As discussed, we want to write our own authorized key into the authorized_keys file.

To do so, we will need to identify the correct path. During port scanning, we identified that we are dealing with an Ubuntu-based machine. On Ubuntu-based machines the file we are looking for usually resides on /home/<username>/.ssh.

If you recall, we are dealing with a newish version of Redis. This means, that the dofile() enumeration technique will probably not help a lot. Fear not through, we can still use Redis as an oracle to identify an existing path, as follows.

  • Execute the below to install a Python interface to the Redis key-value store.

# pip install redis

  • Create a usernames.txt file, containing various usernames. For example:
  • Then, save the below as a .py script in the same directory where you saved usernames.txt.
https://github.com/psmiraglia/ctf/blob/master/kevgir/scripts/redis-oracle.py
  • Execute the script you just saved.

# python how_you_named_the_script.py

We identified the correct path! Let’s continue writing our own authorized key.

  • Crafting the blob to be dropped via Redis
  • Getting Redis’ current configuration (already set through the script we executed previously)
  • Updating Redis’ configuration.
  • Writing/dropping our own authorized key.
  • Connecting through SSH using our own authorized key.

We are in as user!

Let’s think about persisting on that host.

Leaving an SSH authorized key behind is too noisy. In addition, we don’t want to issue too many commands through the Secure Shell, to avoid being detected. What we can do to persist on that host is leverage Redis, once again.

Remember, that any blob we sent so far, first goes into Redis’ memory and it is then written to the specified path. So, we are talking about some quite stealthy payload transferring process.

Unfortunately, we are actually using the RDB format under the hood and this fact causes an issue. The output will be in binary format and subsequently some strings may “break” (common webshells or MSF payloads can’t be easily transferred through Redis). Thankfully, at eLearnSecurity, we identified that one can successfully write (drop) JavaScript code through Redis (BeEF anyone?).

First, let’s find a safe place for our backdoor. During our reconnaissance activities, we identified port 8081 to be open. By browsing http://175.12.97.97:8081/administrator/ , we came across a Joomla installation. Let’s backdoor it ! A nice place to write (drop) our backdoor through Redis is /var/www/html/joomla/templates/beez , where Joomla’s beez template resides (detailed reconnaissance/enumeration helped us identify that).

The backdooring procedure, consists of the following steps.

  • Spin up BeEF.
  • Write the HTML backdoor and transfer it through Redis’ memory, as follows.

If you now browse to http://175.12.97.97:8081/templates/beez/index.html you will see yourself being hooked through BeEF’s hook.

From now on, every user visiting a web page that loads the backdoored template (almost all of them do, this is why templates exist), will get hooked by BeEF!

You can now delete your dropped authorized key and go away.

You could have actually placed that HTML backdoor in the first place (without creating and dropping your own authorized key). You would just need to find an existing path as we covered (bash script using Redis as an oracle) and then drop the HTML backdoor as we did above.

--

--