ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

杂记10 SSRF 服务端请求伪造

杂记10 SSRF 服务端请求伪造 SSRF 的本质只有一句话我进不去的地方骗服务器替我去。本文按「原理 → 武器库 → 三个递进靶场 → 绕过原理 → 与 CSRF 的区别 → 防御」组织。⚠️ 本文所有测试均在授权靶场中完成仅用于安全学习与防御研究请勿用于未授权目标。一、SSRF 是什么1.1 一句话理解借刀杀人你进不去公司的金库访问不了服务器的本地文件、内网服务。但你发现前台有个极其听话、毫无防备的小弟服务器——他的工作就是只要客户递上地址我就去把东西取回来。于是你递上一张纸条去金库把那个文件拿给我。他连看都不看照做了。这就是 SSRF。 注意这次攻击视角的转变文件上传是把恶意文件塞进服务器SSRF是欺骗服务器去替我访问资源1.2 专业定义SSRFServer-Side Request Forgery服务端请求伪造攻击者构造恶意输入通常是 URL 或 IP让服务器端应用代为发起请求。由于服务器通常位于内网、且权限远高于外部用户能访问本地文件、内网数据库、云元数据攻击者便能把它当成跳板去访问那些自己根本无法触及的资源。1.3 危害危害说明读取本地敏感文件如本文靶场读取/flag.txt探测内网服务端口扫描、服务识别攻击内网应用打 Redis、MySQL、内部管理系统读取云元数据获取临时凭证如169.254.169.254绕过防火墙借服务器的内网身份进入受保护区域1.4 大白话一个有求必应的前台小弟把靶场想象成一家公司前台后端 PHP 代码就是坐在前台的跑腿小弟代码动作大白话客户递纸条用户传入url参数小弟接过纸条$url $_GET[url]小弟看都不看就出发curl_setopt($ch, CURLOPT_URL, $url)←漏洞核心小弟把东西拿回来给你echo $response为什么叫服务端请求伪造因为真正去拿文件这个动作是服务端跑腿小弟做的不是你做的。你只是伪造了一个请求借用了服务器的位置和权限。二、武器库PHP cURL 与协议cURL 是 PHP 中最常用的网络请求库也是 SSRF 的重灾区。理解它支持什么才知道能怎么打。2.1 核心函数函数作用curl_init()启动一次跑腿任务初始化会话curl_setopt($ch, CURLOPT_URL, $url)漏洞核心点告诉 cURL 要去哪个地址。这里若是用户输入且无过滤SSRF 就诞生了CURLOPT_RETURNTRANSFER把结果作为返回值拿回来而不是直接输出CURLOPT_FOLLOWLOCATION是否跟随 302 重定向这是利用跳转绕过协议检查的关键CURLOPT_TIMEOUT超时时间curl_exec($ch)真正开始跑腿curl_error($ch)失败原因如Protocol file not supported——这个报错会暴露服务器的底牌2.2 cURL 支持的协议弹药库协议用途http:///https://最常用探测内网 Web 服务、扫描端口file://读取本地文件的神器dict://字典服务器协议探测端口、与内网 Redis 交互gopher://SSRF 中的终极杀器可构造任意 TCP 数据包用于打 Redis、MySQL、FastCGI进而 RCEftp:///sftp://文件传输ldap://目录服务2.3 关键认知php://不是 cURL 的协议这是一个很多人搞错的点。php://filter/convert.base64-encode/resource/flag.txt是PHP 的流包装器stream wrapper它只对PHP 自己的文件函数有效file_get_contents(php://filter/convert.base64-encode/resource/flag.txt); // ✅ 有效 fopen(...)、include(...) // ✅ 有效而cURL 走的是 libcurl 自己的协议栈从来就不认识php://。所以漏洞代码用的函数php://filter能用吗file_get_contents($_GET[url])✅ 可能成功curl_setopt($ch, CURLOPT_URL, $url)❌必然失败报Protocol php not supported结论看到Protocol php not supported or disabled in libcurl这个报错不是php 协议被管理员禁用了而是cURL 压根不支持它。判断该用什么 payload第一步永远是看清代码用的是哪个函数。三、靶场一最简单的 PHP-SSRF3.1 靶场信息项目值靶场名称最简单的 PHP-SSRF难度2 级核心考点SSRF 原理、file://协议利用、本地文件读取目标通过 SSRF 读取服务器本地的/flag.txt3.2 源码与漏洞点后端核心代码?php if (isset($_GET[url])) { header(Content-Type: text/html; charsetutf-8); // 接收用户传入的 url 参数 $url $_GET[url]; ​ // 简单过滤拦截直接输入的 127.0.0.1防御形同虚设 if (strpos($url, 127.0.0.1) ! false) { die(Error: 禁止访问本地地址); } ​ // 初始化 curl发起请求SSRF 核心漏洞点 $ch curl_init(); curl_setopt($ch, CURLOPT_URL, $url); // 直接将用户输入的 URL 传入 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 返回结果而非直接输出 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 允许 302 重定向 curl_setopt($ch, CURLOPT_TIMEOUT, 5); // 超时 5 秒 $response curl_exec($ch); ​ if ($response false) { $error curl_error($ch); curl_close($ch); echo h3SSRF请求结果/h3; echo precURL 错误: .htmlspecialchars($error)./pre; } else { curl_close($ch); echo h3SSRF请求结果/h3; echo pre.htmlspecialchars($response)./pre; } } ?这段代码哪里错了代码大白话解读isset($_GET[url])有客户来但没说找谁那就别办了$url $_GET[url]客户递上纸条strpos($url, 127.0.0.1)⚠️只拦127.0.0.1这一个暗号—— 防御形同虚设curl_setopt($ch, CURLOPT_URL, $url)核心漏洞点连看都不看直接把纸条上的地址当目的地CURLOPT_FOLLOWLOCATION, true允许 302 跳转 → 给后续靶场留了绕过入口curl_error($ch) 输出信息泄露把底层报错原样给攻击者看htmlspecialchars($response)✅ 这一点做对了输出转义避免了二次 XSS最讽刺的地方开发者费劲拦了127.0.0.1可我们的 payload 是file:///flag.txt——压根用不到那个过滤器。他守的是大门我们走的是窗户。这就是黑名单式防御的典型困境你永远不知道攻击者会从哪个方向来。3.3 通关流程第一步信息收集页面有一个输入框提示请输入你想要读取的 URL提交后服务器返回该 URL 的内容。这是典型的 SSRF 入口——服务器在充当代理。页面还提示 Flag 在/flag.txt这是一个服务器本地文件。第二步构造 Payload由于目标是本地文件且后端未对协议做严格限制直接用文件协议file:///flag.txt第三步执行并获取 Flag后端执行curl请求该 URL读取本地文件并返回页面直接显示/flag.txt的内容。3.4 为什么file:///flag.txt是三个斜杠拆开看就清楚了file:///flag.txt └─┬─┘└┬┘└────┬───┘ scheme │ path绝对路径以 / 开头 └─ 空的 authority// 表示 authority 部分开始这里是空的 file: → 协议scheme // → authority主机名部分的开始标记这里是空的表示本机 /flag.txt → 绝对路径三个斜杠 //的两个 绝对路径开头的那个/。 顺带记住file://localhost/flag.txt也等价——因为 RFC 8089 [1] 规定localhost这个 authority 就表示本机等同于没有 authority。3.5 防御要点措施说明协议白名单只允许http/https从根上禁用file、gopher、dict限制内网访问禁止访问127.0.0.1、10.x、172.16.x、192.168.x等统一错误信息不要把 cURL 的底层报错返回给用户关闭重定向跟随业务不需要时CURLOPT_FOLLOWLOCATION设为false四、靶场二大小写绕过协议黑名单靶场名称SSRF 进阶不允许使用 file 协议4.1 靶场信息项目值难度2 级核心考点源码审计、协议大小写绕过目标读取/tmp/flag.txt4.2 三次试探三次碰壁尝试Payload结果说明① 常规file:///tmp/flag.txt被拒服务器已限制file协议② PHP 伪协议php://filter/convert.base64-encode/resource/tmp/flag.txtProtocol php not supported or disabled in libcurl见第二章 2.3cURL 本来就不支持php://③ HTTP 回环http://127.0.0.1/tmp/flag.txt404 Not FoundWeb 根目录与系统/tmp/不互通三次失败的原因各不相同这本身就很有价值① 是协议黑名单在起作用② 是工具选错了cURL 不认识 PHP 伪协议③ 是路径映射不同详见 4.54.3 转折点源码审计点击页面上的点击查看源码拿到后端代码// 只检测小写 file 协议允许大写 FILE 绕过 if (strpos($url, file) 0) { echo div classmsg不允许使用file协议了/div; exit; }漏洞点一目了然问题说明strpos区分大小写只找小写的fileFILE完全不被匹配只判断位置 0只检查开头但 URL 协议本身大小写不敏感一句大白话保安拿着扫描仪但只会念小写的 file。你把牌子写成FILE他就不认识了。4.4 通关FILE:///tmp/flag.txtPayloadFILE:///tmp/flag.txt绕过逻辑后端strpos($url, file)找不到小写的file→校验通过底层 cURL 识别出大写的FILE://协议协议名大小写不敏感→ 成功读取本地文件页面显示/tmp/flag.txt的内容4.5 为什么http://127.0.0.1/tmp/flag.txt会 404这是 SSRF 中极常见的误区值得单独说清http://127.0.0.1/tmp/flag.txt └────┬────┘└─────┬─────┘ 访问的是 Web 服务 它去找的是 Web 根目录下的 /tmp/flag.txtHTTP 协议请求的是Web 服务Apache/Nginx。Web 服务有严格的根目录限制如/var/www/html无法访问/tmp/这类系统目录file 协议直接调用操作系统文件系统走的是完全另一条路记住这个区分http://127.0.0.1/...访问的是网站file:///...访问的是硬盘。想读系统文件必须用file://或者借助文件包含漏洞LFI。4.6 源码审计的价值本次通关的核心不是猜而是拿到了源码源码里的注释直接写着// 只检测小写file协议允许大写FILE绕过——等于把答案给了你实战启示遇到 SSRF应优先尝试获取源码.git泄露、备份文件、报错信息、highlight_file等。有了源码漏洞利用就从盲盒变成了开卷考试4.7 防御要点措施说明统一转小写再判断检查前先strtolower($url)改用白名单只允许http://、https://比黑名单可靠得多注意光加strtolower只是补丁白名单才是正解五、靶场三外部代理跳板靶场名称SSRF 不允许使用 File 协议了 25.1 靶场信息项目值难度3 级核心考点协议绕过、源码审计、外部代理跳板SSRF Proxy目标读取/tmp/flag.txt5.2 双重封杀源码审计结果if (stripos($url, file://) 0) { // 不区分大小写检查是否以 file:// 开头 echo div classmsg不允许使用file协议了/div; exit; } curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // 允许 302 重定向这次的防御上了两个层次层次手段效果代码层stripos不区分大小写上一关的FILE://大小写绕过失效底层库编译 libcurl 时禁用file协议即使绕过代码检查也会报Protocol file not supported连 302 跳转也救不了因为跳转的终点仍是file://最终还是死在 libcurl 手上。 附带一个细节这个检查比对的是file://带双斜杠。而file:/tmp/flag.txt单斜杠其实不匹配这个字符串能绕过代码检查——但底层 libcurl 已经禁用了 file 协议所以直接打仍然不通。这也说明代码层的字符串检查靠不住真正的防线在底层。5.3 破局雇一个外援硬闯不行、翻窗302也不行。但你注意到一个事实公司对外界的网络通讯没有完全切断——服务器允许访问外部 HTTP 链接。上一关那个前台小弟是公司自己的人被公司规矩管着。 那这次我们从外面雇一个跑腿小哥外部代理服务器——他不受这家公司的规矩约束。5.4 通关 Payload 与执行流程http://外部代理:8888/?urlfile:/tmp/flag.txt执行链路① 靶场服务器收到这个 URL —— 以 http:// 开头绕过代码层的 file:// 检查 ✅ ↓ ② 靶场服务器向外部代理8888 端口发起请求 ↓ ③ 外部代理解析 ?urlfile:/tmp/flag.txt它自己的 cURL 环境支持 file 协议 ↓ ④ 代理读取文件把结果返回给靶场服务器 ↓ ⑤ 靶场服务器把结果显示在页面上 → 拿到 Flag5.5 ⚠️ 必须想清楚的一点代理读的是它自己的文件这是本关最容易误解的地方。外部代理读取的/tmp/flag.txt是代理所在那台机器的文件不是靶场服务器的文件。那么为什么能拿到目标 Flag只有一个解释这个外部代理与靶场处在同一台主机或共享同一文件系统上——它很可能就是靶场平台在同一主机的另一个端口上提供的服务。反过来说如果代理真的部署在另一台机器上你读到的就是那台机器的/tmp/flag.txt拿不到目标 Flag。所以外部代理跳板的正确用法是 找一个能访问到目标访问不到的资源的第三方服务。 它解决的核心问题是访问能力不对等而不是简单地换个协议。5.6 防御要点措施说明协议白名单只允许http/https禁用其他所有协议出网管控限制服务器能访问的外部地址这正是本关被突破的原因禁止访问内网127.0.0.1、10.x、172.16.x、192.168.x一律拒绝关闭重定向跟随防止利用 302 绕过协议检查统一错误信息不返回 cURL 详细报错防止泄露底层配置网络隔离服务器放入 DMZ限制对内网核心资源的访问六、绕过原理与速查6.1 URL 的构成scheme://host:port/path?query#fragment ↓ ↓ ↓ ↓ ↓ ↓ https www.example.com 443 /index.php?id1 #top6.2 原理一协议名大小写不敏感RFC 3986根据 RFC 3986 标准URI 的scheme协议部分不区分大小写http、HTTP、hTtP完全等价。所以开发者如果只检查小写的file://就会被FILE://绕过。6.3 原理二fileURI 有两种合法写法RFC 8089很多人以为file:/tmp/flag.txt单斜杠是畸形 URL 靠解析器容错这个说法是错的。RFC 8089 [1] 的语法定义是这样的file-hier-part ( // auth-path ) ← 带 authority 的写法 / local-path ← 不带 authority 的写法附录 A 明确写道整个 authority 部分包括双斜杠//都是可选的。附录 B 给出的合法示例包括形式RFC 8089 中的定位file:///path/to/file常见的空 authority写法file:/path/to/file本地文件的最小表示形式标准写法file://host.example.com/path带 authority 的非本地文件结论单斜杠不是解析器的容错而是标准里就有的合法形式。所以构造 payload 时file:///、file:/、file://localhost/都应该试一遍——它们都是合法的。6.4 原理三IP 变形的各种写法当127.0.0.1被黑名单拦截时写法说明2130706433十进制整数形式127.0.0.1的十进制值0x7f000001十六进制形式017700000001八进制形式127.1省略写法等价于127.0.0.10/0.0.0.0部分系统上指向本机[::1]IPv6 回环地址127.0.0.1.nip.io用 DNS 服务把域名解析回127.0.0.1 核心思路这些写法在字符串层面各不相同但最终都会解析成同一个 IP。黑名单比的是字符串解析器比的是 IP —— 这就是错位的根源。6.5 绕过手法速查表绕过手法针对的防御示例大小写只检查小写协议的代码FILE://、File://单斜杠 / localhost检查file://字符串的代码file:/tmp/flag.txt、file://localhost/tmp/flag.txt302 跳转代码层协议检查需底层支持外部 URL 返回Location: file:///...外部代理跳板底层协议被禁用http://proxy/?urlfile:/tmp/flag.txtIP 变形IP 黑名单2130706433、0x7f000001、127.1换协议file被禁gopher://、dict://DNS Rebinding解析时校验、请求时变脸第一次解析为外网 IP 过校验第二次解析为内网 IPURL 编码关键字匹配%66%69%6c%65%3a%2f%2f七、SSRF 与 CSRF 的区别两个名字都带请求伪造初学极易混淆但方向、对象、原理完全不同。7.1 两个比喻SSRF 借刀杀人你想进安保严密的公司内网偷文件但进不去。你发现前台服务器很热心于是递张纸条帮我去档案室把 X 文件拿来。前台照做了。CSRF 冒名顶替你去银行办了张授权卡Cookie银行说拿着这张卡来谁都能办。攻击者做了个假网页诱导你点开。你的浏览器不知情地拿着授权卡把一张转账申请表提交给了银行。银行一看是持卡人本人办了。7.2 核心区别对照表维度SSRFCSRF谁在发请求服务器受害者的浏览器被利用的是谁服务器利用它的内网权限用户利用他的登录状态攻击者想干什么读服务器本地文件、扫内网、打内网服务以用户名义转账、改密码、发帖需要用户配合吗❌ 不需要直接给服务器发请求✅ 需要必须诱导用户访问恶意页面利用的信任服务器对内网环境的信任服务器对用户浏览器的信任防御核心限制请求协议 限制访问内网 IPCSRF Token SameSite Cookie 校验 Origin典型靶场输入 URL 去读/tmp/flag.txt点个链接就改了管理员密码7.3 记忆口诀SSRFServer 发请求—— 受害的是服务器CSRF浏览器发请求跨站发起—— 受害的是用户⚠️ 注意别被缩写误导CSRF 的 C 是 Cross跨站不是 Client。区分两者的可靠依据永远是谁把请求发出去的。7.4 能组合吗能在高级渗透场景中两者常被串成一条链先用CSRF让管理员在不知情的情况下访问某个 SSRF 接口SSRF再驱动服务器去访问内网 Redis写入计划任务 / 反弹 Shell最终拿下整个内网八、防御总览8.1 五道防线#措施说明有效性1协议白名单只允许http/https禁用file、gopher、dict等⭐⭐⭐ 根本措施2禁止访问内网解析目标 IP 后判断是否为内网/保留地址且校验解析结果的 IP防 DNS Rebinding⭐⭐⭐3不跟随重定向CURLOPT_FOLLOWLOCATION false⭐⭐4统一错误信息不返回 cURL 底层报错⭐⭐5网络隔离服务器放 DMZ出网走白名单代理⭐⭐8.2 关键为什么校验域名不够很多代码这样写// ❌ 看似安全实际有洞 if (strpos($url, http://127.0.0.1) ! false) { die(禁止); }三个致命问题黑名单比的是字符串解析器比的是 IP→2130706433、0x7f000001轻松绕过DNS Rebinding→ 校验时解析成外网 IP真正请求时解析成内网 IP302 跳转→ 校验的是第一跳cURL 自动跟到内网正确做法先解析出真实 IP再对 IP 做判断并且禁止重定向。8.3 一个简单的防护思路$url $_GET[url]; ​ // 1. 协议白名单 $scheme strtolower(parse_url($url, PHP_URL_SCHEME)); if (!in_array($scheme, [http, https], true)) { exit(只允许 http/https); } ​ // 2. 解析出真实 IP 再判断防 IP 变形 防部分 DNS Rebinding $host parse_url($url, PHP_URL_HOST); $ip gethostbyname($host); if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) false) { exit(禁止访问内网/保留地址); } ​ // 3. 不跟随重定向 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);⚠️ 注意这段代码仍不是万能的——gethostbyname之后 cURL 可能重新解析一次域名DNS Rebinding 窗口。生产环境的稳妥做法是把解析出的 IP 固定下来直接请求该 IP并带上原始 Host 头或者干脆让所有出网请求走一个带白名单的代理。九、总结9.1 三个靶场串起来看靶场难度防御手段绕过手法最简单的 PHP-SSRF2 级几乎无防御只拦了127.0.0.1file:///flag.txt不允许使用 file 协议2 级代码层黑名单区分大小写FILE:///tmp/flag.txt不允许使用 file 协议 23 级代码层stripos底层禁用 file 协议外部代理跳板递进关系从没有防御→代码层黑名单→代码层 底层双重封杀绕过手法也从业余到高级。9.2 三条核心心法① 先看源码知道它防什么。有源码就有开卷考试strpos区分大小写 → 试FILE://stripos不区分 → 换思路。② 再看函数知道它支持什么。用 cURL 就别试php://它压根不认识用file_get_contents才有php://filter的机会。③ 最后看协议规范构造合法但畸形的 payload。大小写、单斜杠、IP 变形、编码——都是标准允许的合法写法只是开发者的检查没想到。9.3 练习建议改协议把file://依次换成gopher://、dict://观察报错差异练 IP 变形把127.0.0.1换成十进制、十六进制、八进制看黑名单是否被绕过造 DNS Rebinding本地起个域名第一次解析到外网、第二次解析到内网搭本地靶场写一段带curl_setopt($ch, CURLOPT_URL, $url)的代码亲手把上面每种绕过都打一遍9.4 一句话收尾SSRF 的本质是借用服务器的位置和权限。防御的核心是用白名单锁死协议用 IP 校验锁死目的地别让服务器替任何人乱跑腿。
返回列表