May 1, 2017

For Users of Redis, Running Locally Can Be a Major Security Risk

If you are running Redis locally and, like most people as of this writing, you're using a version older than 3.2.7 (released January 31, 2017), I can most likely copy your entire database, drop an ssh key in your authorized_keys file, overwrite arbitrary files on your computer, and lay a trap that will run arbitrary code next time you open your terminal.


All this can be executed from visiting a malicious web site; or a non-malicious site that's had malicious JavaScript loaded onto it.

Background

I read "How to steal any developer's local database" a while back on Hacker News, and thought it might be a fun project to try out with BugReplay. In the process I’ve learned how powerful DNS rebinding can be as a method of attack and the kind of damage that can be done. This article is an attempt to build on previous works and demonstrate the techniques involved.


When you look at typical authorization mechanisms,


the most basic is restricting the ability to access the resource. For example, you probably spend more time making sure people can't steal or mess around with your laptop than crafting the ultimate password for it. In the case of networked software, access restrictions at the network or server layer (ie above the client software layer) are the most important part to secure and are often sufficient. Having a password for your SQL database is important, but security-wise it is way more important that it isn't open to connections from the internet.


Redis has unusually few security features for a database that has seen such wide adoption, even for software designed to run on secured servers. There are no access levels; any connecting user can execute system commands like FLUSHALL or DEBUG SEGFAULT. From antirez, the original developer of Redis:


The Redis security model is: “it’s totally insecure to let untrusted clients access the system, please protect it from the outside world yourself”. The reason is that, basically, 99.99% of the Redis use cases are inside a sandboxed environment.


That may have been true at the time of writing, but nowadays a whole lot of people are running Redis locally, listening on the default port. That is very far from sandboxed.. it's more like a sewer. Unlike a typical remote server, your personal computer typically is used for browsing the web with a JavaScript enabled browser.


Server Side Request Forgery (SSRF) is a method of tricking a vulnerable server into sending out malicious requests that you've crafted to services that you can not access directly. Say you were probing Facebook's open graph page debugger. If you were testing it for SSRF possibilities, the input box (or the api endpoint it is posting to) would be your attack vector. Maybe you would start by plugging in 127.0.0.1 or whatsmyip.org and seeing what data is exposed.


When you're browsing the web, your browser is virtually as open as that URL input box to any website you visit. Any resource you load, via img tag or script tag or xhr request is a network request originating from your IP address to the wider Internet. That means Redis, sitting there with its default configuration of binding to localhost port 6379 with no password1, is wide open to anyone on the internet.

The Exploit

A combination of factors makes Redis particularly vulnerable to SSRF2. It uses a text based protocol, it won't terminate the connection when fed invalid commands, and the previously mentioned lack of access levels. The combination leads to some interesting possibilities in terms of exploitation. Basically, as has been well known for over 15 years, you can post data to almost any TCP port running on localhost through the browser. Then using a technique known as DNS rebinding, you can sidestep the Same Origin Policy and get data out.


Combined with the lax security model of Redis, it is an interesting exploit. As a developer working on BugReplay, a bug recording tool which tries to capture all the streams of data necessary to understand and reproduce an issue, I'm always looking for new scenarios to test out the software and see how it performs. With all the different moving parts, I was not sure what would come out of recording it in action. I was particularly interested to see how it would record responses from Redis, as Redis isn't a web server and is just incidentally able to communicate back to the browser.


Here's a BugReplay walkthrough of sending arbitrary commands to a local Redis server. In this example we are setting the key “Hi! We’re from the” to the value “Internet.” in Redis using the Redis protocol:


*3 $3 SET $18 Hi! We're from the $9 Internet. *1 $4 QUIT


Redis also supports plain-text notation in its protocol, but using the protocol format makes it easier to transmit over the wire and is more robust (ex. if sending values with newlines).


Notice how the demo shows the response from Redis. Your browser can see that data (and BugReplay is attached to the browser via the Chrome debugging protocol so it also receives the response), but the web page does not thanks to the 'Same Origin Policy.'. I'm actually serving this page locally but because the demo is on a different port than Redis the 'Origin' is different ( http://localhost:5001 and http://localhost:6379 respectively). I can send commands into Redis, but not get data back out.


To get the data back out (exfiltration), we need to use DNS Rebinding. This BugReplay recording demonstrates an attack where I'm injecting a Lua script that retrieves the entire contents of the database.


The script injects a base64 encoding function (with comments crediting the author!), along with a function that will encode the entire database as a JSON hash with the keys corresponding to the keys from the database, and the values corresponding to base64(DUMP(key)). The DUMP command outputs a binary format, so base64 helps for transmitting the data over the wire. You can see the request here.


DNS rebinding is a way of getting around the Same Origin Policy for services where you know the IP address of the service you're trying to access. In the Redis case we are trying to get to 127.0.0.1 (localhost), but other attacks may target your wifi router or something else only accessible on your local network.


To execute a DNS rebinding attack, you set a domain that toggles between a remote address where you are serving up malicious content and the IP address you are trying to access.


With this hack I'm trying to POST data to Redis, and send the results back out to an endpoint I control, but because of the Same Origin Policy I can't just send a request from your computer to 127.0.0.1:6379 and then send back the results. The workaround is serving up some content on port 6379 of a server I control (bugreplaydemo-dns-rebinding.samuelkaufman.com) that is supposedly posting data to itself. The catch is that I am switching the DNS A Record record behind the scenes to point to 127.0.0.1, so while the browser is treating it as the same Origin, bugreplaydemo-dns-rebinding.samuelkaufman.com is switching every 20 seconds from pointing at my server to your local computer.

So I Just Copied Your Database and Added a Key to It, Who Cares?

The demo here is purposely pretty harmless. Following along with Antirez's security writeup, you can see how you can drop files around the OS anywhere the user running Redis has permission. Including your ssh keys file, but that's not the worst of it. This demo goes through overwriting ~/.inputrc, which can trigger a payload from a terminal key binding; ex: you go to your shell and hit the letter "a" and that fires off a background process which installs a backdoor. I was very easily able to bind a key which downloaded an arbitrary url and executed it via the shell.

What Can I Do?

If you are running Redis locally and haven't upgraded to 3.2.7 (stable as of Jan 31 2017), there's a number of things you can do to keep yourself safe.

Upgrade!

This one's pretty obvious. Upgrade to latest stable, which aliases Host to QUIT, keeping web browsers from talking to Redis. Upgrading production software is always tricky because of compatibility concerns, plus the hassle of taking the database out of service, but you don't have that excuse if you are running a local database.

Set a Password

The Redis project has always recommended setting a strong password - Redis is fast enough that brute forcing an easy password is not hard.

Rename Sensitive Commands


Even on older versions of Redis, you have the ability to rename sensitive commands. This means you can effectively do what later versions do by default, alias HOST to QUIT so browsers get the boot. You may also just want to do away with being able to delete the entire database while you're in there. And do you really need EVAL?

Bind to a Unix socket Instead for Local Development


Nothing is more secure than being unreachable, so switching to a Unix socket instead of a port closes that vector of attack.

Conclusion

This was a fun blog entry to write. My goal of making BugReplay the best piece of software for recording and diagnosing web application based issues gave me a great excuse to try and hack the crap out of my computer while learning some classic yet still current web based exploits.


As you could probably tell I am in no way a security expert, so any corrections please send my way via twitter.


If you want to check out BugReplay and record some examples of exploits (or bugs) yourself we'd love to have you try it out! Here's a bonus demo of an Ebay info leakage issue that people seemed to enjoy on Hacker News :)


1: Technically the default configuration, what happens when you run redis-server with no arguments, is bound on every interface (0.0.0.0) with no password but the config that ships with Redis binds only to 127.0.0.1 as a more sensible default.


2: There does not seem to be an overwhelming consensus as to the classification of this exploit as an example of SSRF. There's a case to be made for classifying it as a CSRF (Cross Site Request Forgery) attack as it is here, because like a typical CSRF attack your browser is the attack vector. However Redis is not a 'Site' in the typical sense; it just happens to be vulnerable to payloads delivered over HTTP.