Unprotected Redis Instances in the Wild

If you follow programming blogs, it is not uncommon to come across articles that mention how MongoDB exposes your private information without any protection on default settings. But Mongo is not alone in this. Even with sane defaults, it is possible to find that a lot of people have misconfigured their databases for their convenience. In this list of exposed servers is our beloved Redis.

Redis is normally highly praised among developers. But even this doesn’t stop it from being used incorrectly. The server can accept a plaintext protocol, which will make it easier for us to use. To test; we can download Redis, create a server and insert some dummy keys into it (like key1, key2, key3).

If we look at the Redis documentation, we will find a function called RANDOMKEY. The name is pretty self-explanatory, it returns the name of a random key from the database. If we open a TCP connection to the server and send RANDOMKEY\r\n, the server might respond with key2.

In order to find Redis servers that allow anonymous access, we will write a script to scan the internet. The script will

  1. Generate random IP addresses
  2. Try to connect to them on the Redis port 6379
  3. If the connection is successful, send the RANDOMKEY command and check if there is any output.

Sending the RANDOMKEY command will let us check if the server we connected to is actually Redis. It also allows us to filter empty servers if we want to.

We can use Go to write a short script for this purpose.

ip := fmt.Sprintf("%d.%d.%d.%d:6379", rand.Intn(256), rand.Intn(256),
                    rand.Intn(256), rand.Intn(256))
fmt.Println(ip)

This code will generate a random IP address with the port 6379, and print it on the screen. When we’re scanning for lots of random IP addresses, it is likely that we will attempt to connect to lots of addresses that have no servers listening. In order to prevent slowdowns, we will set a timeout of 2 seconds.

conn, err := net.DialTimeout("tcp", ip, timeout)

If our connection is successful, we set a new timeout of 5 seconds and send the command.

conn.SetDeadline(time.Now().Add(time.Second * 5))
fmt.Fprintf(conn, "RANDOMKEY\r\n")

After this, we try to read a line from the socket and print it along with the IP. This will allow us to see which servers are empty and which ones have content. It also lets us filter potential false positives that aren’t actually Redis servers.

var line string
_, err := fmt.Fscanln(conn, &line)
fmt.Println(ip)
fmt.Println(line)

Let’s wrap all this in a function along with error handling, and spawn 200 coroutines to scan the internet for unprotected Redis servers.

import (
    "net"
    "fmt"
    "math/rand"
    "time"
)

func scanner() {
    for {
        timeout := time.Second * 2
        ip := fmt.Sprintf("%d.%d.%d.%d:6379", rand.Intn(256),
                           rand.Intn(256), rand.Intn(256), rand.Intn(256))
        conn, err := net.DialTimeout("tcp", ip, timeout)
        if err == nil {
            conn.SetDeadline(time.Now().Add(time.Second * 5))
            fmt.Fprintf(conn, "RANDOMKEY\r\n")
            var line string
            _, err := fmt.Fscanln(conn, &line)
            if err == nil {
                fmt.Println(ip)
                fmt.Println(line)
            }
        }
    }
}

func main() {
    for i := 0;i<300;i++ {
        go scanner()
    }
    fmt.Scanln()
}

A script like this can be used to find servers. While possible uses for these servers include data storage and transfer, or just dumping all the data; the responsible thing to do would be running a reverse DNS on the IP and contacting the website and letting them know about the issue.



Comments BETA