前两天在写一道cthub上的一道SSRF题,其中题目提示到使用post请求以及一些提示
1
自己尝试没法写出来后,就去网上找write up
但是找出来的write up大多不尽人意,尤其是在下面几个问题的解释上十分混乱

  1. gopher协议在提交post请求时的编码规定
  2. 为什么要对post请求进行URL编码多次
  3. 到底要编码几次

一生要强的我就决定试试能不能自己搞懂

分析

我就以下面三篇博客作为引例记录一下
CTFhub-ssrf-POST请求
CTFHUB-SSRF-POST请求(小宇特详解)
CTFHub-技能树-SSRF POST请求
如果需要看正确解题步骤可以参考第一篇博客,这里我主要分析一下其中的正确原理
第一篇博客中认为URL编码两次即可,其中他的提交条件和理由为:

  1. 在index.php页面提交
  2. 因为gopher提交post本身需要进行URL编码,并且curl进行请求发送之前会进行URL解码,所以需要进行两次编码
  3. 浏览器为Chrome

第二篇博客中认为URL编码需要编码三次,其中他的提交条件和理由为:

  1. 在302.php页面使用BP进行提交
  2. 具体为什么需要进行三次编码的原因没有说明
  3. 浏览器为Firefox

第三篇博客中认为URL编码需要编码两次,其中他的提交条件和理由为:

  1. 在index.php页面提交
  2. 具体为什么需要进行两次编码的原因没有说明
  3. 浏览器不明

三篇博客相同之处在于都有提到换行符%0d%0a的转换,但具体是为什么而转换,除了第一篇稍微提到以外,另外两篇并没有提及。
有趣的是,第三篇的题面和前两篇似乎并不一样,第二篇在过程中并不能正常访问302.php,而最后拿到flag时根据所给截图来看,靶机也已经过期销毁。而我自己去使用他所提供的方法时,发现也是404。
3
为了解决这个疑惑,我还到ctfhub官方群上问了一下
4
5

解决

接下来我就从gopher协议格式、php编码自动转换两个方面来介绍一下
Gopher协议原理和限制介绍
这篇博客同样是从SSRF的角度,来介绍gopher协议,里面清楚地提到了将换行符转换成%0d%0a是gopher协议本身规定,并非像第一篇题解说的是因为“windows和linux之间的不同”。而且两次编码转换的原因也在这篇文中有提到,原因就是在curl_exce()函数调用前就已经存在解码,而发送gopher协议需要对请求方式进行URL编码才能正常发送,所以才需要两次。更多关于gopher协议使用的具体内容可以参考一下这篇博客。
既然两次编码问题得到了解决,那么解码是在哪里发生的呢?如果单看上面这篇博客也还是不能得到这个问题的最终答案。
因此又经过的大量的查阅后,我又发现了这篇博客
http请求(GET/POST)时,url/参数编码的过程分析
答主提到了由于php在使用$_GET等接收方式储存前会按照URL规范对URL进行处理,因此我们可以理解成每在不同文件中使用$_GET等方式接收各种提交时php都会对其进行一次解码。
而为了证明这一点,我也进行了一次测试,来看看在php中是不是真的

实验

先来看一下我构造的php代码

<?php

error_reporting(0);


print_r($_REQUEST['url']);echo "<br/>";
print_r($_GET['url']);echo "<br/>";
print_r($_POST['url']);echo "<br/>";


$ch=$_GET['url'];
$r=curl_init($ch);
print_r($r);
//print_r($r);
?>

首先我模拟一个跟那道ctfhub上的题目差不多payload的gopher协议,先将原文直接在这个页面发起一个假gopher请求看一下URL从浏览器传入php的结果
6
可以看到显示内容确实和输入的一样,但浏览器已经进行了一次编码。接着将浏览器编码完的POST请求部分复制下来到在线编码器上再编码一次并提交
7
可以看到,传入一个php文件后确实只对URL进行了一次解码。接下来将index.php改为具有重定向功能,并继续构造flag.php

index.php

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_exec($ch);
curl_close($ch);
?>

flag.php

<?php
print_r($_REQUEST['url']);
?>

因为URL地址栏不允许输入太长的内容,所以这里我就只在在线转码器中转换了这一句:Content-Type: application/x-www-form-urlencoded
结果如图
8
因此就能得到上面总结的结论

小插曲

在我得到结论前,我一度被困扰在题解中的第二篇博客,因此怀疑有可能是不同浏览器的编码问题,因此我也特地又下载了Firefox来和我已经有的Edge和Chrome进行对比。比较如图
9
不难看出,Firefox确实在编码上和Chrome有所不同(Edge结果和Chrome相同),不会对输入原文进行编码。而这也是一个值得注意的地方,如果是一些对URL编码有依赖的靶场,使用Firefox应该就会造成一些问题。

同时,这个故事也告诉我们,不要轻信低质量博客的题解,只有自己多思考,才是掌握真理的唯一手段。