SSRF漏洞实战:从宝塔靶场搭建到内网渗透与安全加固

📅 2026/6/23 17:27:09
SSRF漏洞实战:从宝塔靶场搭建到内网渗透与安全加固
1. 项目概述从靶场搭建到漏洞原理的实战闭环最近在复盘一些经典的Web安全漏洞特别是SSRFServer-Side Request Forgery服务端请求伪造发现很多朋友对原理的理解停留在概念层面一到实战环境就无从下手。正好手头有个闲置的服务器我决定用宝塔面板快速搭建一个名为“ssrfme”的靶场环境并完整复现一个经典的SSRF漏洞利用链。这个过程的重点不在于“搭建”本身而在于通过亲手操作把“请求伪造”、“协议利用”、“内网探测”这些抽象概念变成看得见摸得着的HTTP请求和服务器响应。对于安全研究人员、CTF选手或是想深入理解服务端安全开发的工程师来说自己搭建并攻破一个靶场是理解漏洞成因和危害最直接有效的方式。宝塔面板因其图形化操作的便捷性成为了我们快速部署Web应用的利器。本次实战我们将基于CentOS 7系统使用宝塔部署一个包含SSRF漏洞点的PHP应用ssrfme并模拟攻击者利用该漏洞进行内网服务探测、读取本地文件甚至尝试攻击内网其他脆弱服务的完整过程。整个过程会涉及宝塔的基础配置、Nginx/Apache的差异处理、漏洞代码审计、以及多种利用工具的实战演示。无论你是想为团队搭建一个内部训练环境还是单纯想深化对SSRF的理解这篇从零开始的记录都能提供一条清晰的路径。2. 环境准备与宝塔面板部署2.1 服务器系统选择与初始化搭建靶场的第一步是准备一台干净的服务器。我选择的是CentOS 7.9 Minimal版本这是一个在生产和实验环境中都非常稳定且常见的Linux发行版。之所以没选最新的CentOS Stream或Ubuntu主要是考虑到宝塔面板对CentOS 7的支持最为成熟相关的教程和问题解决方案也最丰富。在云服务商那里创建实例时确保开放了80HTTP、443HTTPS以及一个用于SSH管理的端口如22端口。系统初始化后首先通过SSH连接到服务器。我习惯做的第一件事是更新系统并安装基础工具这能为后续操作减少很多麻烦。执行以下命令yum update -y yum install -y wget curl vim net-tools注意在云服务器上yum update可能会更新内核部分云厂商建议不要更新内核以避免与虚拟化驱动不兼容。如果不确定可以跳过-y参数手动选择不更新内核相关的包或者直接咨询云服务商的文档。接下来我们需要设置服务器的防火墙。CentOS 7默认使用firewalld。为了方便实验我们可以暂时关闭防火墙并禁用SELinux但在生产环境中这是绝对不允许的。对于实验环境可以执行systemctl stop firewalld systemctl disable firewalld setenforce 0 sed -i s/SELINUXenforcing/SELINUXdisabled/g /etc/selinux/config2.2 宝塔面板的安装与基础配置宝塔面板的安装极其简单。访问宝塔官网获取针对CentOS 7的一键安装脚本。这里有一个关键点官网的安装脚本可能会随时间变化最稳妥的方式是直接从官网复制最新版的安装命令。我安装时使用的命令是yum install -y wget wget -O install.sh http://download.bt.cn/install/install_6.0.sh sh install.sh执行后脚本会自动安装依赖并完成面板的部署。安装过程大约需要5-10分钟取决于服务器带宽和性能。安装成功后控制台会显示面板的访问地址、用户名和随机生成的密码。务必立即保存这些信息这是你进入管理界面的唯一凭证。通过浏览器访问http://你的服务器IP:8888进入宝塔面板登录页。首次登录会弹出“推荐安装套件”的窗口。对于我们的Web靶场环境LNMPLinux, Nginx, MySQL, PHP和LAMPLinux, Apache, MySQL, PHP都是可选的。我个人的偏好是选择LNMP因为Nginx在处理静态资源和并发方面通常更有优势而且其配置结构对于理解请求转发过程也更清晰。在软件选择界面我做了如下选择Nginx 1.22 选择稳定版本。MySQL 5.7 对于靶场5.7版本足够且兼容性好。务必记录安装后弹出的数据库root密码。PHP 7.4 许多遗留的漏洞靶场代码对PHP 7.x系列兼容性最好。我们需要额外安装一些PHP扩展在面板的“软件商店”找到已安装的PHP 7.4点击“设置”在“安装扩展”选项卡中安装curl和fileinfo扩展。curl扩展是SSRF漏洞利用的核心因为它允许PHP代码通过curl函数发起网络请求。实操心得安装过程中宝塔会编译安装这些软件耗时较长可能超过30分钟。可以放在后台运行期间不要关闭SSH连接。如果安装失败通常是服务器内存不足建议1GB以上或网络超时导致可以查看/tmp/panelBoot.pl日志文件排查。2.3 网站创建与关键目录权限设定套件安装完成后我们开始创建承载ssrfme靶场的网站。在宝塔面板左侧点击“网站”然后“添加站点”。域名 由于是本地测试我们可以直接填写服务器IP地址或者为了更贴近真实场景在本地hosts文件解析一个测试域名如ssrfme.test。这里我直接用了IP。根目录 默认会在/www/wwwroot/下创建一个以域名命名的文件夹例如/www/wwwroot/your_server_ip。记住这个路径。FTP和数据库 可以暂时不创建因为我们直接上传文件。PHP版本 选择我们刚才安装的PHP 7.4。创建站点后宝塔会自动在Nginx中生成一个站点配置文件并创建好网站根目录。接下来是至关重要的一步权限设置。不恰当的权限是很多Web漏洞能够扩大影响的关键。为了模拟一个存在配置缺陷的真实环境我们故意设置一个“不太安全”但常见的权限。点击网站列表对应站点的“根目录”图标进入文件管理。目录权限 选中网站根目录点击上方的“权限”按钮。将目录权限设置为755所有者设为www这是宝塔面板下Nginx/PHP-FPM进程默认的运行用户。文件权限 未来我们上传的PHP脚本文件权限应设置为644。这允许www用户读取和执行文件但不能直接写入。注意事项在实际生产环境中应该遵循“最小权限原则”。例如上传目录如果有应该单独设置并禁止执行PHP文件。我们这里为了方便将根目录权限放宽是为了后续漏洞利用时能够模拟攻击者写入或读取文件的情景。请明确这仅用于实验环境。3. ssrfme靶场应用部署与漏洞代码解析3.1 获取与部署ssrfme漏洞代码“ssrfme”并不是一个官方标准的靶场名称它通常是安全社区对一类演示SSRF漏洞的简单PHP应用的统称。我们可以从一些开源漏洞靶场项目如Vulhub、DVWA的扩展关卡中找到类似代码或者自己编写一个。这里我以一个经典的、功能清晰的SSRF漏洞演示代码为例进行部署。假设我们有以下这段名为ssrf.php的漏洞代码?php highlight_file(__FILE__); if(isset($_GET[url])){ $url $_GET[url]; $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_HEADER, 0); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); // 关键漏洞点未对$url进行任何过滤和限制 $result curl_exec($ch); curl_close($ch); echo $result; } else { echo Please input url parameter.; } ?这段代码的逻辑非常简单它接收一个GET参数url然后使用PHP的cURL函数毫无戒备地向该url发起请求并将响应内容直接回显到页面上。这就是一个最“纯净”的SSRF漏洞模型。我们将这段代码保存为index.php或者ssrf.php通过宝塔面板的文件管理器上传到之前创建的网站根目录如/www/wwwroot/your_server_ip下。上传后通过浏览器访问http://你的服务器IP/ssrf.php如果能看到代码高亮显示说明部署成功。3.2 漏洞原理深度拆解为什么cURL会成为突破口表面上看这段代码只是一个“网络请求代理”。但它的危险性在于完全信任了用户输入的url参数。攻击者可以控制这个参数让服务器端的应用以www用户权限去向任意地址发起请求。这带来了几个维度的威胁攻击内网服务 服务器通常处于内网环境可能访问一些外部无法直接到达的服务如数据库Redis:6379, MySQL:3306、缓存服务Memcached:11211、内部管理后台如宝塔面板的8888端口等。攻击者可以构造urlhttp://127.0.0.1:3306尝试与MySQL交互虽然MySQL协议可能无法直接返回Web响应但可以探测端口开放状态。读取本地文件 利用file://协议。cURL支持多种协议包括file://。攻击者可以构造urlfile:///etc/passwd服务器上的PHP cURL就会去读取本地的/etc/passwd文件并将内容输出到网页上导致敏感信息泄露。协议滥用 除了http://和file://还可能利用dict://,gopher://等协议。例如dict://127.0.0.1:6379/info可以用来探测Redis信息gopher://协议功能更强大可以构造任意格式的TCP数据包常用于攻击内网的Redis、FastCGI等服务实现远程代码执行。关键点在于权限这个cURL请求是以Web服务进程www用户的身份发起的。因此它能访问到的资源受限于www用户的系统权限。如果服务器配置不当如以root权限运行PHP-FPM那么危害将是灾难性的。3.3 宝塔环境下的特殊配置与潜在风险点在默认的宝塔LNMP环境下我们的漏洞利用可能会遇到一些“阻碍”理解这些阻碍有助于我们更真实地模拟攻击和防御。Nginx的权限隔离 宝塔默认使用www用户运行Nginx和PHP-FPM。这个用户权限较低无法读取/root/、/etc/shadow等关键文件。但像/etc/passwd这类全局可读文件依然可以读取。PHP的cURL限制 默认情况下PHP的cURL支持file://、http://、https://等协议。但gopher://和dict://协议可能需要在编译cURL库时开启特定支持。在宝塔安装的PHP中这些协议通常是支持的但最好验证一下。内网访问 服务器能否访问到其他内网机器取决于网络拓扑。在单台VPS上我们主要攻击“本地回环地址”127.0.0.1上的服务。宝塔面板本身运行在8888端口如果PHP-FPM进程能访问到这个端口就可能成为攻击目标。我们可以写一个简单的PHP脚本来测试当前环境的cURL能力?php $protocols curl_version()[protocols]; echo Supported protocols: . implode(, , $protocols); ?上传并访问这个文件查看输出中是否包含gopher、dict。4. SSRF漏洞手工复现与利用链实战环境就绪漏洞代码就位现在我们开始扮演攻击者对亲手搭建的靶场发起攻击。我们将由浅入深演示几种典型的SSRF利用方式。4.1 基础利用探测内网端口与读取本地文件首先进行最基本的端口探测。我们的漏洞页面地址是http://your_server_ip/ssrf.php。1. 探测本地开放端口我们尝试探测服务器本机上哪些端口是开放的。构造请求http://your_server_ip/ssrf.php?urlhttp://127.0.0.1:22如果端口22SSH开放且服务是SSH我们可能会收到一个类似“SSH-2.0-OpenSSH”的banner信息或者连接被拒绝/超时的不同响应。尝试urlhttp://127.0.0.1:3306(MySQL)urlhttp://127.0.0.1:6379(Redis)urlhttp://127.0.0.1:8888(宝塔面板)。通过对比响应时间、响应内容或错误信息可以判断端口状态。例如连接被立即拒绝Connection refused通常表示端口关闭或无服务连接超时可能表示有防火墙拦截。2. 读取本地敏感文件利用file://协议。构造请求http://your_server_ip/ssrf.php?urlfile:///etc/passwd如果成功页面上会直接显示/etc/passwd文件的内容。同样可以尝试读取Web应用本身的配置文件、日志文件等例如urlfile:///www/wwwroot/your_server_ip/index.phpurlfile:///www/server/panel/data/default.db(宝塔面板的默认数据库路径通常无权访问)实操心得在浏览器中直接测试file://协议读取/etc/passwd时可能会因为Nginx/PHP的路径安全限制或open_basedir配置而失败。宝塔默认会设置open_basedir将PHP限制在网站目录内。这是宝塔做的一层安全加固。为了实验我们可以临时修改PHP配置。在宝塔面板的PHP设置中找到“禁用函数”和“配置修改”。将putenv、symlink等函数从禁用列表移除实验后务必恢复并在“配置修改”中找到open_basedir一行将其注释掉或改为none。这是一个非常危险的操作仅限实验环境完成后立即恢复4.2 进阶利用利用dict与gopher协议攻击内网服务当基础的文件读取和HTTP探测满足不了需求时dict和gopher协议就成为了更强大的武器。1. 使用dict协议探测Redisdict协议通常用于访问字典服务器但它可以用于与一些简单的TCP文本协议交互。Redis的默认端口是6379。构造请求http://your_server_ip/ssrf.php?urldict://127.0.0.1:6379/info如果服务器6379端口运行着Redis并且未设置密码或绑定IP这个请求会向Redis发送INFO命令并将Redis返回的系统信息显示在网页上。这直接泄露了Redis的配置、数据等敏感信息。2. 使用gopher协议构造原生TCP攻击gopher协议是一个古老的协议但它允许我们几乎自由地构造原始的TCP数据包是SSRF攻击中的“瑞士军刀”。利用它攻击Redis未授权访问并写入Webshell是一个经典案例。假设我们想攻击内网IP172.20.10.2上的Redis端口6379并且知道Web根目录是/www/wwwroot/vul_site/。攻击步骤通常是 a. 通过SSRF让服务器向172.20.10.2:6379发起连接。 b. 发送Redis命令将一段PHP代码写入Web目录下的一个文件如shell.php。 c. 通过访问这个Webshell文件获得命令执行权限。由于gopher协议的payload是URL编码后的原始TCP流构造起来非常复杂。这里给出一个概念性的简化示例实际攻击需要精确计算每个字符gopher://172.20.10.2:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$XX%0d%0a...PHP代码...%0d%0aquit%0d%0a这个URL编码后的字符串解码后相当于向Redis发送了set 1 “?php eval($_POST[‘cmd’]);?”等命令。注意事项现代版本的Redis默认配置是保护模式只允许本地回环地址访问并且可能要求密码认证。这使得通过SSRF攻击外网或配置良好的Redis变得困难。但在内部网络或配置失误的环境中这种攻击仍然极具威胁。我们的靶场实验更可能是在本机127.0.0.1上模拟一个配置错误的Redis服务进行。4.3 利用SSRF攻击宝塔面板自身模拟场景这是一个非常有趣的模拟场景。宝塔面板运行在8888端口。如果我们能通过SSRF让服务器进程向127.0.0.1:8888发送请求会发生什么首先我们需要知道宝塔面板的登录接口。通常登录是POST请求到/login。我们可以尝试构造一个请求但这里会遇到几个问题CSRF Token 宝塔登录表单通常有CSRF令牌我们需要先GET一次登录页提取token再构造POST。这通过单个SSRF请求很难自动化。会话Cookie 登录后的操作需要维护会话。权限 即使以www用户身份请求面板面板后台的敏感操作可能还有额外的身份验证。因此直接通过SSRF“黑进”宝塔面板是复杂的。但我们可以进行一些信息探测urlhttp://127.0.0.1:8888/ 看看是否能获取到宝塔面板的首页可能泄露一些版本信息。urlhttp://127.0.0.1:8888/files?actionGetFileBodypath/etc/passwd 尝试访问一些已知的、无需强认证的API接口如果存在且配置不当。这需要事先了解宝塔面板的API结构属于已知漏洞利用的范畴切勿用于非法测试。这个模拟旨在说明SSRF的威胁边界取决于Web应用进程能访问到的“网络位置”和“协议能力”。将关键管理后台如宝塔、phpMyAdmin暴露在服务器本地回环地址且缺乏额外认证会无形中扩大SSRF漏洞的危害半径。5. 漏洞修复方案与安全加固实践复现漏洞是为了更好地修复它。针对我们搭建的这个ssrfme靶场我们可以从多个层面进行加固。5.1 代码层修复输入过滤与请求限制这是最根本的修复方式。修改ssrf.php的源代码。白名单校验 如果业务只允许访问特定的几个外部域名那么使用白名单是最安全的方式。$allowed_hosts [api.trusted.com, cdn.safe.org]; $parsed_url parse_url($_GET[url]); if (!in_array($parsed_url[host], $allowed_hosts)) { die(Access denied.); }协议禁用 禁用危险的URL协议只允许HTTP/HTTPS。$parsed_url parse_url($_GET[url]); if (!in_array($parsed_url[scheme], [http, https])) { die(Unsupported protocol.); }限制访问内网 阻止对私有IP地址段和回环地址的请求。function is_internal_ip($ip) { $long_ip ip2long($ip); return !$long_ip || ($long_ip ip2long(10.0.0.0) $long_ip ip2long(10.255.255.255)) || ($long_ip ip2long(172.16.0.0) $long_ip ip2long(172.31.255.255)) || ($long_ip ip2long(192.168.0.0) $long_ip ip2long(192.168.255.255)) || ($long_ip ip2long(127.0.0.0) $long_ip ip2long(127.255.255.255)); } $parsed_url parse_url($_GET[url]); $host $parsed_url[host]; $ip gethostbyname($host); if (is_internal_ip($ip)) { die(Internal network access is forbidden.); }使用更安全的库 使用如guzzlehttp/guzzle等现代HTTP客户端库它们通常提供了更好的默认安全配置和更易用的选项来限制重定向、协议等。5.2 网络与系统层加固缩小攻击面即使代码有缺陷我们也可以通过环境配置来限制损失。宝塔面板安全设置修改默认端口 将宝塔的8888端口修改为一个不常见的高位端口。绑定访问IP 在宝塔面板的“安全”设置中添加IP访问限制只允许你自己的管理IP访问面板端口。启用面板SSL和BasicAuth 为面板访问地址设置二次认证。操作系统防火墙 重新启用firewalld并严格限制端口开放。只开放必要的80、443、SSH端口。确保服务器上的其他服务如MySQL、Redis只监听在127.0.0.1或内网IP上而不是0.0.0.0。# 例如配置MySQL只监听本地 # 在 /etc/my.cnf 的 [mysqld] 部分添加 bind-address 127.0.0.1服务自身认证 为Redis、MySQL等服务设置强密码并禁用默认的空密码或弱密码。PHP配置加固 恢复并检查PHP的open_basedir配置将PHP限制在其应有的目录内。在php.ini中确保allow_url_fopen和allow_url_include为Off虽然对cURL无直接影响但能防止其他文件包含漏洞。5.3 部署层防护使用反向代理与WAF对于无法彻底修改代码的遗留系统可以考虑外围防护。Nginx反向代理与访问控制 可以在Nginx层面对向该漏洞接口的请求进行初步过滤。例如在对应的Nginxlocation块中使用if指令检查$arg_url参数是否包含file://、127.0.0.1等危险字符串并直接返回403。但要注意Nginx条件判断的局限性和性能影响。location ~ \.php$ { if ($arg_url ~* (file|gopher|dict|127\.0|192\.168|10\.|172\.(1[6-9]|2[0-9]|3[01]))) { return 403; } ... # 其他fastcgi配置 }Web应用防火墙WAF 宝塔面板自带免费的Nginx防火墙插件如宝塔Nginx免费防火墙。可以启用该插件它内置了一些常见的SSRF攻击特征规则如URL参数中包含特殊协议、内网IP等能够在一定程度上拦截攻击请求。在插件中找到“全局配置”或“URL过滤”规则确保SSRF相关的规则是开启状态。6. 常见问题排查与调试技巧实录在搭建和复现过程中你可能会遇到各种问题。这里记录了几个我踩过的坑和解决方法。6.1 宝塔环境下的典型问题问题1使用file://协议读取文件返回空或错误。可能原因1open_basedir限制。这是最常见的原因。通过宝塔PHP管理界面查看该网站PHP版本的“配置修改”检查open_basedir项。实验时可以临时注释掉但生产环境必须合理设置。可能原因2SELinux阻止。即使setenforce 0临时关闭某些上下文标签可能仍存在影响。可以尝试查看Nginx/PHP-FPM的日志/www/wwwlogs/或系统日志/var/log/messages获取线索。排查命令在SSH中切换到www用户尝试直接读取文件看是否是权限问题。sudo -u www cat /etc/passwd问题2访问SSRF漏洞页面cURL请求外部URL超时或失败。可能原因1服务器网络不通。确保服务器本身可以访问互联网ping 114.114.114.114。可能原因2PHP的cURL扩展未安装或配置问题。在宝塔“软件商店”的PHP设置中确认已安装curl扩展。可以创建一个phpinfo.php文件查看curl部分是否启用。可能原因3DNS解析问题。尝试在URL中使用IP地址而非域名进行测试。问题3使用gopher协议攻击Redis无响应。可能原因1PHP的cURL不支持gopher协议。使用第3.3节的测试脚本验证。可能原因2Redis服务未运行或配置了保护。在服务器上执行ss -tlnp | grep 6379检查Redis是否监听在127.0.0.1。检查Redis配置文件bind 127.0.0.1和protected-mode yes。可能原因3Payload构造错误。gopher的payload格式非常严格需要精确的CRLF%0d%0a和字符长度前缀。建议先用Python或Go写一个小脚本本地生成payload再进行测试。6.2 漏洞利用过程中的调试技巧开启详细错误日志 在测试的PHP文件开头加入以下代码便于查看PHP错误。error_reporting(E_ALL); ini_set(display_errors, 1);同时查看宝塔面板上的站点日志Nginx访问日志、错误日志和PHP日志在/www/server/php/版本号/var/log/目录下。使用Burp Suite或浏览器开发者工具 拦截浏览器发送的漏洞利用请求可以清晰地看到最终发送出去的URL编码格式是否正确。Burp Suite的Repeater模块非常适合修改和重放攻击payload。分步测试 不要一开始就构造复杂的gopher攻击。先测试http://127.0.0.1:80能否访问到本机的Web服务再测试file:///etc/passwd最后再尝试dict和gopher。每一步都确认环境是通的。在服务器端监听端口验证请求是否到达 如果你怀疑请求没有发出去可以在目标内网IP的某个端口上启动一个简单的网络监听器。例如在目标机器或本机另一个终端上运行nc -lvnp 11211然后从SSRF漏洞点请求urlhttp://目标IP:11211/test看看nc是否收到了连接和“GET /test ...”的请求。这能直观证明SSRF是否生效。6.3 安全加固后的验证方法修复漏洞后如何验证修复是否有效回归测试 重新执行之前成功的攻击payload。例如尝试访问file:///etc/passwd、http://127.0.0.1:3306。预期结果应该是看到自定义的错误信息如“Access denied”而不是文件内容或数据库banner。边界测试 测试白名单之外的合法域名是否被阻止测试允许的HTTP/HTTPS协议是否工作正常测试对http://localhost和http://127.0.0.1的拦截是否都生效有些过滤可能只检查字符串127.0.0.1而localhost可能被绕过。使用工具辅助扫描 可以使用像SSRFmap、Gopherus这样的自动化工具对修复后的接口进行模糊测试看是否还存在未考虑到的绕过方式。注意仅对自己的测试环境使用。整个从搭建到攻击再到修复的闭环实践下来我对SSRF漏洞的理解不再停留在纸面。它不仅仅是“服务器能发请求”那么简单而是关乎到整个服务器所处的网络环境、权限体系、服务配置以及代码中对用户输入的控制。在宝塔这样便捷的管理面板下默认的安全配置为我们挡掉了一部分风险但开发者绝不能因此放松警惕。真正的安全始于每一行对用户输入保持怀疑的代码和每一项遵循最小权限原则的配置。