Blog

Redis with no password: what the agent got, and what it refused to claim

An unauthenticated Redis 7.4.10 instance assessed autonomously: 9 findings, 5 exploited. The classic RDB-write RCE was demoted to mitigated because Redis 7.x blocked it, and we published that rather than the bigger number.

· 6 min read

An open Redis is the most reliably over-claimed finding in infrastructure testing. The write-up almost always ends with remote code execution, because for years you could set the working directory and the dump filename, write an RDB file into a cron directory or an SSH authorized_keys path, and get a shell. Redis 7 changed that. Most tooling did not.

We ran a full autonomous campaign against Redis 7.4.10 on 127.0.0.1:6379 with no password, protected mode off and a wildcard bind. The interesting result is not the compromise. It is which claim the agent dropped.

What the agent found

The messaging-cache specialist agent produced 9 findings. Five are marked exploited, meaning the effect was produced and captured, and four are confirmed from configuration evidence.

SeverityCount
Critical3 (1 exploited, 2 confirmed)
High5 (4 exploited, 1 confirmed)
Medium1 (confirmed)
Low0
Total9

The access itself took one command, and the ACL made the scope explicit:

bash -c 'timeout 15 redis-cli -h 127.0.0.1 -p 6379 PING'
bash -c 'timeout 15 redis-cli -h 127.0.0.1 -p 6379 CONFIG GET requirepass'
bash -c 'timeout 15 redis-cli -h 127.0.0.1 -p 6379 ACL LIST'

PING            -> PONG
requirepass     -> ""
ACL LIST        -> "user default on nopass sanitize-payload ~* &* +@all"
protected-mode  -> "no"
bind            -> "* -::*"

nopass ~* &* +@all is the whole risk in one line: no password, every key pattern, every channel, every command.

What it did with that access

  • Read the seeded application data. KEYS "*" returned session:admin, and GET session:admin returned "role=admin;token=abcd1234". An unauthenticated client reading a live admin session token is a session hijack, without touching the application.
  • Wrote and cleaned up. SET, GET, then DEL on a test key, so the write capability is proven and the instance is left as it was found.
  • Changed runtime configuration. CONFIG SET loglevel debug and CONFIG SET maxmemory 0 both returned OK, then the log level was reverted.
  • Executed server side Lua. EVAL ran and returned the full INFO server block, which is code execution inside the Redis sandbox.
  • Enumerated the dangerous command surface. SLAVEOF, REPLICAOF, DEBUG, SHUTDOWN and FLUSHALL are all available, and CONFIG GET rename-command returned an empty array, so nothing had been renamed away.

The claim the agent gave up

Demoted: RDB-write remote code execution

The agent tried the classic path and recorded the failure verbatim:

CONFIG SET dir /tmp
-> (error) ERR CONFIG SET failed (possibly related to argument 'dir')
   - can't set protected config

CONFIG SET dbfilename darkmoon_test.rdb
-> (error) ERR CONFIG SET failed (possibly related to argument 'dbfilename')
   - can't set protected config

Redis 7.x treats dir and dbfilename as protected configuration, so the file write primitive that the whole RDB technique depends on is gone. The executive summary of the report says it in one sentence: classic RDB-write RCE is mitigated by Redis 7.x protections, and MODULE LOAD is disabled. The Lua sandbox held too: os.execute was reported unavailable and io raised ERR Script attempted to access nonexistent global variable.

So the campaign closed with data-layer compromise and denial of service, not host compromise. That is a smaller headline and it is the accurate one. A report that claims RCE here would fail the first time a customer tried to reproduce it.

SLAVEOF and REPLICAOF are still recorded as high severity, because an attacker can point the instance at a rogue master and force it to load a replacement dataset, which is a data integrity and exfiltration vector even without file writes. The report is explicit that module loading is disabled at config level while replication remains usable.

Remediation

  • Set requirepass or, better, define real ACL users and remove the permissive default user.
  • Re-enable protected-mode and bind to a specific interface instead of * -::*.
  • Rename or disable SHUTDOWN, FLUSHALL, DEBUG, REPLICAOF and CONFIG for application users.
  • Enable TLS. CONFIG GET tls-port returned 0, so every command and every session token crossed the network in cleartext.
  • Treat any session token that lived in this instance as burned and rotate it.

A bug this run found in our own agent

The first Redis campaign we launched hung with zero findings. The cause was not the target. The model had been told by an internal tool check that redis-cli was unavailable, so it improvised a raw socket with bash /dev/tcp and cat. Redis never closes the connection, so cat blocked forever. We fixed it by adding an execution safety rule to all 14 expansion agents: never read a live socket that way, always use the dedicated client wrapped in bash -c, always with a timeout. Every command in this report has that shape, which is why they all start with bash -c 'timeout 15 ...'.

What this proves about autonomous pentesting

The value of an autonomous run is not that it finds the open port. Any scanner does that. It is that the agent attempts the escalation, records the exact refusal from the target, and then downgrades its own claim. A finding that says exploited has a raw response behind it. A finding that says mitigated has the error message behind it. Both are more useful than a severity label with nothing under it.

Darkmoon is GPL-3.0 and self hosted. Run it against a Redis you own and compare: source, docs.

Run it against your own lab

Darkmoon is open source (GPL-3.0) and self hosted. Clone it, point it at a target you own, and read every line.