【Web安全攻防从入门到精通】SSRF漏洞
SSRF漏洞
原理
服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤和限制。通过控制前台的请求远程地址加载的响应,来让请求数据由远程的URL域名修改为请求本地,或者内网的IP地址及服务,来造成对内网系统的攻击。
危害
- 扫描内网开放服务
- 向内网任意主机的任意端口发送Payload来攻击内网服务
- DOS攻击(请求大文件,始终保持连接Keep-Alive Always)
- 攻击内网的Web应用(如 直接SQL注入、XSS攻击等)
- 利用file(读取文件)、gopher(命令执行)、dict(探测端口)协议读取本地文件、执行命令
检测和绕过
漏洞检测
//端口
http://www.xxx.com/image.php?image=http://127.0.0.1:22
//协议
http://www.xxx.com/image.php?image=file:///ect/passwd
http://www.xxx.com/image.php?image=dict://127.0.0.1:22/data:data2(dict可以向服务端口请求data data2)
http://www.xxx.com/image.php?image=gopher://127.0.0.1:2233/_test(向2233端口发送数据test,同样可以发送POST请求)
//不同语言实现的不同Web系统可以使用的协议不同
//php
http https file gopher phar dict ftp ssh telnet
//java
http https file ftp jar netdoc mailto
漏洞监测点
- 分享
通过URL地址分享文章,从URL参数的获取可实现单机链接时跳到指定的分享文章。如果在此功能中没有对目标地址的范围做过滤和限制,就会存在ssrf漏洞
- 图片加载与下载
通过URL地址加载和下载图片,图片加载存在于很多编辑器中,编辑器上传文件处,有的是加载远程图片到服务器内。还有一些采用了加载远程图片的形式,本地文章加载了设定好的传承图片服务器上的图片地址,如果没对加载的参数做限制可能造成SSRF。
- 图片、文章收藏功能
假如title参数是文章的标题地址,代表一个文章的链接地址,请求连接后返回当前文章是否保存、收藏的返回信息。如果文章被保存,则收藏功能也存在保存文章,在没有限制参数的形式下则可能存在SSRF。
- 利用参数中的关键字来查找
share
wap
url
link
src
source
target
u
3g
display
sourceURI
imageURL
domain
...
漏洞绕过
- 限制为http://www.xxx.com域名
可以尝试采用HTTP基本身份认证的方式绕过,如http://www.xxx.com@www.xxc.com。在对@解析的域名中,不同函数存在处理差异。
http://www.xxx.com@www.aaa.com@www.bbb.com PHP中的parse_url会识别www.bbb.com 而libcurl则识别为www.aaa.com
- 限制IP不为内网地址
- 采用短网址绕过
- 采用可以指向任意域名的xip.io
- 采用进制转换
- 限制请求只为HTTP协议
采用302跳转、百度短地址,或使用https://tinyurl.com生成302跳转地址
与Redis(端口:6379)的结合
此漏洞在没有配置密码的情况下,可以利用SSRF来绕过绑定在本地的限制,从而实现外网攻击内网应用目的。
Redis漏洞
默认绑定在6379端口。如果没有采取相关的策略,如添加防火墙规则避免其他非信任来源IP访问等,会将Redis服务暴露在公网上。如果没设置密码认证(一般为空)的情况下,会导致任意用户在可以访问目标服务器的情况下,未经授权访问Redis及读取Redis的数据。
漏洞修复
- 限制返回信息:请求文件只返回文件是否请求成功,没有成功请求的文件,统一返回错误信息
- 对请求地址设置白名单,只允许请求白名单内的地址:在不影响正常业务的情况下,对请求地址设置白名单之后,访问白名单之内的地址将跳转到指定页面,访问白名单之外的地址将返回错误信息。
- 禁用除HTTP/HTTPS外的协议:任何真实生产环境中,请禁用除HTTP/HTTPS协议之外的其他协议,如:file、gopher、dict协议等
- 限制请求的端口为固定服务的端口:真实生产环境中,服务不要开放高位端口如135、443,只允许用户请求80等一些必要的服务端口。
Java类代码修复
//方法调用的示例代码
String[] urlWhiteList = {"jaychou.com", "jaychou.me"};
if (!UrlSecCheck(url, urlWhiteList)){
return;
}
//方法代码的示例代码
//先添加guava库(目的是获取一级域名)
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>21.0</version>
</denpendency>
//方法实现的示例代码
public static Boolean UrlSecCheck(Striing url, String[] urlWhiteList){
try{
URL u= new URL(url);
//只允许HTTP和HTTPS协议
if(!u.getProtocol().startsWith("http") && !u.getProtocol().startsWith("https")){
return false;
}
//获取域名,并转为小写
String host = u.getHost().toLowerCase();
//获取一级域名
String rootDomain = InternetDomainName.from(host).topPrivateDomain().toString();
for(String whiteList : urlWhiteList){
if(rootDomain.equals(whiteList)){
return true;
}
}
return false;
}catch (Exception e){
return false;
}
}