浅析SSRF与文件读取的一些小特性
0x0 前言
很久之前打比赛的遇到的一个点, 我当时以为这样的读取特性是跟php伪协议有关呢, 之前也看飘零师傅发过朋友圈,但是记忆比较模糊了,刚好最近放假有时间来调试一下才发现原来是curl的锅。
0x1 分析问题
引起我的好奇心是一个师傅分析POSCMS的文章的一个tips:
其实我个人觉得师傅这里解释不是很严谨,这个问题其实主要还是出在了curl请求上面,比如我们请求http://127.0.0.1/1.php?.jpg,那么我们访问的内容就是1.php,而不是名称为`1.php?.jpg`的文件,而`file_get_contents`刚好相反,至于为什么是这样,其实通俗来说原因是按照URL的定义来解析,那么1.php才是资源名,所以才会导致这样的结果, 但是下面我将从php底层来分析下这个原理,说明白下面两段代码的实现差异。
我们先从两段代码开始看起:
<?php
$url = $_GET['url'];
$file = $url . '.jpg';
var_dump($url);
var_dump($file);
echo file_get_contents($url);
echo '</br>';
echo file_get_contents($file);
?>
<?php
var_dump(ini_get('allow_url_fopen'));
$url = $_POST['url'];
$url = $url . '.jpg';
var_dump($url);
// echo file_get_contents($url);
if(function_exists('curl_init') && function_exists('curl_exec')){
$ch = curl_init($url);
$data = '';
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($ch);
curl_close($ch);
var_dump($data);
}
?>
我们经常在代码里面看到下载文件的时候,一般都会有这两种函数去读取文件内容(导致SSRF), 他们一般是先执行file_get_contents
判断返回结果为False
的则接着去执行curl_exec
,否则直接return, 至于为什么这么写,估计是考虑了像我上面列举的情况?
下面我就主要来分析下php底层如何实现file_get_contents
的功能。
0x2 从底层分析file_get_contents
0x2.0 debug环境构建
下载:
git clone https://github.com/php/php-src.git
cd php-src
git checkout remotes/origin/PHP-7.2.0
编译:
./buildconf
./configure --enable-debug --disable-all --prefix=/Users/xq17/Desktop/PHPCore/
make && make install
0x2.1 开始分析
关于StreamWrapper和protocols的关系: Supported Protocols and Wrappers
关于file://
,wrapper.file
PHP流的的概念
我们都知道PHP中的文件操作函数可以打开文件、URL等资源然后返回一个句柄。那么PHP是如何做到使用一致的API对不同数据源进行操作的呢? 其实就得益于PHP在底层对各种操作进行了分装, 再上层将其统一看做成"流"对象,在底层在进行具体解析。
php7.1.8
file_get_contents函数的定义
/ext/standard/file.c
520 line
解析完参数之后,开始解析流,我们跟入php_stream_open_wrapper_ex
/main/streams/streams.c
2010 line
跟进php_stream_locate_url_wrapper
这个函数
解析协议类型,继续向下看
里面看到path[n+3]!='/'
这就是为什么我们使用file协议需要:file:///
的原因
也就是必须要用绝对路径。