看这篇文章看的很难受,逻辑不清楚。。。
expand_filepath
是php 里面路径展开函数, 用来处理 ../
, ./
,//
为什么 /
会变成\x00
,可以去看看expand_filepath
其中递归处理过程。
还有即然已经能 ini_set()
,那么这样做还有意义吗?
在twiter上看到一个bypass open_basedir的新方法 顺便就分析了一下
先看payload
chdir('img');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');echo(file_get_contents('flag'));
很好搜
对着php.net的解释看一下 猜也能猜个大概
先跳过php_check_open_basedir
看zend_alter_ini_entry_ex
先从EG表中取出要修改的项目的指针,然后一路赋值过去new_value => duplicate => ini_entry->value
直接看下gdb的调试结果 最初的时候值是ini里面的值
执行下去 可以看到值改变了
先经过open_basedir的检测 然后来到336行 VCWD_CHDIR
这个宏最终会调用_chdir()
来修改当前工作目录
这个也简单 先看下报错搜源代码
这个函数就是上面忽略的php_check_open_basedir
的具体实现 函数很简单没啥好说的 直接跟php_check_specific_open_basedir
(虽然这里ptr
的值来自PG(open_basedir)
但是在ini_set
中作出的对EG
的修改最终会影响PG
)
函数有点长不想看 直接gdb调一发 对着php_check_specific_open_basedir
打个断点 然后直接c到第一个chdir('..')
这里可以看到传进去的两个参数 继续执行来到161行 先记录一下关键参数
执行下去 会发现一个有趣的结果 / => \000
看下源码 肉眼跟一下来到expand_filepath_with_mode
大致功能就是规范化路径
relative_to
必定为NULL 所以必定进入esle逻辑 而VCWD_GETCWD
这个宏最终是通过_getcwd()
实现的
继续跟 来到216行 还是先记录一下关键变量的值
继续执行到strncmp
再看一下变量的值
看了下就resolved_name_len
变化了 回去看源代码
resolved_name_len = strlen(resolved_name);
因为c中字符串是以\0
为结尾 所以长度从17变为13 经过strncmp
判断 前13、14位肯定一样 返回0 然后这个check就过了
到这里为止好像还没什么问题 那么回到刚刚
刚才是从第一个chdir('..')
开始调试的 重新梳理一下
首先传入的两个参数的值都是都是..
这个应该没什么问题
来到151行 就是一个复制感觉也没问题
strlcpy(local_open_basedir, basedir, sizeof(local_open_basedir));
看一下值
问题来了 local_open_basedir
会在之后的逻辑中参与resolved_basedir
的规范化
但是由于开头的..\0
会使得resolved_basedir
的规范化流程和resolved_name
一样 都是以当前工作目录为基础进行处理
..\0
导致if必然是假 从而进入else逻辑 那就一定会到VCWD_GETCWD
这个宏 最后会使resolved_basedir
和resolved_name
的值几乎一样
这样就导致了php_check_specific_open_basedir
一直返回0 从而控制当前工作目录一直向上穿越 导致open_basedir被绕过
这里的关键就是如何将open_basedir设置为..
在payload中先chdir
到了img 再利用open_basedir可以设置为相对目录的特点进行bypass 真的很巧妙