• session 会话机制以及变量覆盖


    session会话机制介绍如下

    http是无状态协议。服务器靠cookie和session来记住用户。$_SESSION 和 $_GET等一样,是超全局变量。

    后台脚本里面会写: session() start 。会话开始。所以,当浏览器访问一个页面时,session机制就开始了。这个时候机制会生成一个PHPSESSID. 存放在cookie当中,当在这个网站post,get 请求数据的时候,会带着这个ID。这个ID是随机的,是由PHP决定的。在注册时,这个PHPSSID可能和注册时间,毫秒,用户名,密码哈希值等有关,所以爆破碰撞PHPSSID难度很大。

    在服务器后台,会创建一个以PHPSSID命名文件,里面会储存用户的信息。服务器根据用户发送过来的PHPSSID,读取文件里面的信息。

    上面的和这道题没有太大关系。
    题目源码打包地址: 120.27.32.227/www.zip
    这是一道代码审计。
    下面是相关代码示例。

    <?php
    define("DB_NAME", "xian");
    define("DB_HOST", "localhost");
    define("DB_USER", "root");
    define("DB_PASSWD", "");
    define("DSN", "mysql:host=".DB_HOST.";dbname=".DB_NAME);
    ini_set("display_errors", "On");
    error_reporting(0);
    foreach (array('_COOKIE','_POST','_GET') as $_request)  
    {
        foreach ($$_request as $_key=>$_value)  
        {
            $$_key=  $_value;
        }
    }
    session_start();
    ?>
    

    这是common.php里面的代码,连接数据库,取出超全局变量里面的变量和值,根据顺序,先取出cookie,再试post和get。++如果这些变量里面有重名的变量,根据先后顺序,get具有决定性。接着就是SESSION会话机制开始++。由于$_SESSION也是一个超全局变量,如果碰到和前面那三个有重名的,$_SESSION具有决定性。

    下面看do_register.php。

    <?php
    include_once("common.php");
    $dbh = new PDO(DSN, DB_USER, DB_PASSWD);
    $sql = "select * from user where username = :username";
    $sth = $dbh->prepare($sql);
    $sth->execute(array(':username'=>$username));
    $res = $sth->fetch(PDO::FETCH_ASSOC);
    if($res !== false) {
        header("Location: error.php?msg=username%20has%20been%20used!");
        die();
    }
    $sql = "insert into user (username, password, role) values(:username, :password, 1)";
    $sth = $dbh->prepare($sql);
    $res = $sth->execute(array(':username'=>$username, ':password'=>$password));
    if($res === false) {
        header("Location: error.php?msg=register%20error!");
        die();
    }
    
    $sql = "select * from user where username = :username and password = :password";
    $sth = $dbh->prepare($sql);
    $sth->execute(array(':username'=>$username, ':password' => $password));
    $res = $sth->fetch(PDO::FETCH_ASSOC);
    if($res === false) {
        header("Location: error.php?msg=register%20error!");
        die();
    }
    $userinfo["id"] = $res["id"];
    $userinfo["username"] = $username;
    $userinfo["password"] = $password;
    $_SESSION["userinfo"] = $userinfo;
    $userinfo["role"] = $res["role"];
    header("Location: index.php");
    ?>
    

    看这段代码的最后几行,是直接把userinfo的信息写进了session里面。下面再看do_changepassword.php。

    <?php
    include_once("common.php");
    if(!isset($_SESSION["userinfo"])) {
        header("Location: login.php");
        die();
    }
    $userinfo = $_SESSION["userinfo"];
    if($old_pass = $userinfo['password']) {
    	if($userinfo["id"] == 1) {
    		echo "flag{xxx}";
    		die();
    	}
        $dbh = new PDO(DSN, DB_USER, DB_PASSWD);
        $sql = "update user set password = :password where id=:id";
        $sth = $dbh->prepare($sql);
        $res = $sth->execute((array(':password'=>$new_pass, ':id'=>$userinfo['id'])));
    	var_dump $new_pass;
    	echo "<br>";
    	var_dump $userinfo['id'];
    	
        if($res === false) {
            header("Location: error.php?msg=changepass%20error!");
            die();
        }
    	
        $userinfo["password"] = $new_pass;
        $_SESSION['userinfo'] = $userinfo;
    	
        header("Location: index.php");
    } else {
        header("Location: error.php?msg=invalid%20old%20pass!");
        die();
    }
    ?>
    

    这里看到,要得到flag,id必须为1。
    所以正常来说,我们会想到在超全局变量里面传入参数userinfo['id']=1。

    这是一个数组,所以我们请求应该这么写。 http://localhost/do_changepass.php?userinfo[id]=1
    但是并没有用,因为$_SESSION这个超全局变量里面有和这个重名的,最后这个变量的值是由session决定的。

    所以,这题,只要在post,get,cookie这三个里面传入和session里面重名的变量,都会被session给覆盖,也就是传入参数数组的方式行不通。

    解这题的正确姿势,还是要用到“php是最好的语言这个特性”---弱类型。

    所以这道题,在register的时候,传入参数,userinfo=1 。然后访问do_changepass ,改密码,就出flag了。解释如下。

    传入参数 userinfo=1, 在register页面,

    $userinfo["id"] = $res["id"];
    $userinfo["username"] = $username;
    $userinfo["password"] = $password;
    $_SESSION["userinfo"] = $userinfo;
    $userinfo["role"] = $res["role"];
    header("Location: index.php");
    

    这几行代码之前,session为空。所以这里调用的userinfo 是我们传进去的参数,而我们传进去的参数是一个字符串 , $userinfo["id"]
    会把我们的字符串先转化为字符数组,但是是一个数字索引数组 。里面没有id这个键,所以 $userinf["id"] 就与 userinfo[0] 等同。我们的userinfo就变成了userinfo="$rs["id"]"。只改变了第一个字符。接下来的变量赋值,都只改变了第一个字符。所以最后第一个字符是由 $res["role"]决定的,值为1。

    我们传入变量的时候,只传入一个字符,同样能出flag。

    	if($userinfo["id"] == 1) {
    		echo "flag{xxx}";
    		die();
    	}
    

    这里userinfo["id"]=userinf[0]=$res["role"]=1。

    总结:这一道题考察了 变量覆盖,php弱类型:字符和数组的转化,变量重名。还有观察力。

  • 相关阅读:
    django项目环境搭建备忘
    Python IDE的选择和安装
    MAC上python环境搭建
    hadoop1.2.1+hbase0.90.4+nutch2.2.1+elasticsearch0.90.5配置(伪分布式)
    ubuntu下hadoop完全分布式部署
    ubuntu下集群设置静态ip
    C语言调用库函数实现生产者消费者问题
    poj 1703(带权并查集)
    poj 1330
    poj1724
  • 原文地址:https://www.cnblogs.com/deen-/p/7227781.html
Copyright © 2020-2023  润新知