美文网首页
LCTF的一道preg_match绕过+出题人的锅

LCTF的一道preg_match绕过+出题人的锅

作者: _阿烨_ | 来源:发表于2017-11-22 02:32 被阅读0次

    http://123.206.120.239/

    • .idea/workspace.xml 泄露文件信息,常见于用phpstorm写项目然后同步到github


      image.png
    • 下载xdcms233.zip得到源码,有register.php login.php member.php 源码丢在最下面
      主页是 index.html 通过按钮跳转到这几个php

    这里看到xdebug,以为是phpstorm开了远程调试,因为题目说管理员开发完登陆界面就睡着了,2333

    image.png
    尝试打了payload,无果,参考文章
    http://momomoxiaoxi.com/2017/09/18/WHCTF/
    • 正常代码审计
    • 大致逻辑是这样的,你可以注册一个用户,其中的code可以定义你的身份,如果你的code被pre_match处理后,能满足以下条件
    $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
    //$admin是每次随机生成的,碰撞的可能性是1/(35*35)
    preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
    if (count($matches) === 3 && $admin === $matches[0]) 
    

    那你就是admin,不然你就是guest

    image.png
    image.png

    这题的正确做法是输入很长的code,让pre_match处理的时候出错,php进程崩掉,然后你后面的guest身份的插入语句就不会执行

    赛后知道这个解法后,我真的是惊呆了。。。比赛时实在是搜不到能绕过pre_match的方法,因为这个给了头尾^$,以后搜不到还是得多看看php文档。。。

    我比赛用的方法很匪夷所思,赛后跟出题人交流才知道原因,这是他的答复


    image.png

    导致数据库的user表和identities表清空的频率很快

    • 可以反复注册同一个账户
    • 登陆进去后,刚开始是guest,等一会刷新以下,你会发现你不是guest了,因为identities表清空了,自然就绕过了member.php的逻辑


      image.png
    同时也有条件竞争的解法
    • 第一天出题者的数据库还是正常的,这个时候就只能注册不同用户,代码的逻辑是,注册时先将username插入user表,再将guest身份插入identities表,这之间有个间隙,而且由于pre_match()函数处理慢,这个间隙还是可利用的,code大概20个字符就能挺拖速度了,大概每秒注册10个用户,然后另一边同时用这10个用户去登陆,就能绕过guest了
    • 第二天数据库异常,导致同一个用户能反复注册,那么只需要一个脚本疯狂地注册同一个用户,另一个脚本疯狂地用这个用户去登陆,就能利用这个间隙去绕过guest身份的插入


      image.png
    其实这里的条件竞争,还有另外一处

    login.php里面会清理session


    image.png

    memeber.php的会检测session

    image.png

    那么如果我们用脚本不断得注册并访问login.php,当member刚运行完此处


    image.png

    $_SESSION['is_logined'] 和 $_SESSION['is_guest'] 刚好在login.php那边被清除掉,接着member.php继续往下走,执行

    image.png

    这样if里面的判断就是 0 || 0 ,自然能进入下面的else逻辑

    绕过了guest,来到文件读取处

    image.png
    image.png

    进入下面else逻辑,开始读取文件,但is_file()判断是文件,就不让读取,不过好在readfile()函数支持php伪协议,成功绕过is_file() 读取config.php

    payload:
    http://123.206.120.239/member.php?file=php://filter/read=convert.base64-encode/resource=config.php

    image.png image.png

    后端源码:
    login.php

    <?php
        session_start();
        include('config.php');
        try{
            $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
        }catch (Exception $e){
            die('mysql connected error');
        }
        $username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
        $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
    
        if (strlen($username) > 32 || strlen($password) > 32) {
            die('Invalid input');
        }
    
        $sth = $pdo->prepare('SELECT password FROM users WHERE username = :username');
        $sth->execute([':username' => $username]);
        if ($sth->fetch()[0] !== $password) {
            die('wrong password');
        }
        $_SESSION['username'] = $username;
        unset($_SESSION['is_logined']);
        unset($_SESSION['is_guest']);
        #echo $username;
        header("Location: member.php");
    ?>
    

    register.php

    <?php
        include('config.php');
        try{
            $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
        }catch (Exception $e){
            die('mysql connected error');
        }
        $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
        $username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
        $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
        $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
    
        if (strlen($username) > 16 || strlen($username) > 16) {
            die('Invalid input');
        }
    
        $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
        $sth->execute([':username' => $username]);
        if ($sth->fetch() !== false) {
            die('username has been registered');
        }
    
        $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
        $sth->execute([':username' => $username, ':password' => $password]);
    
        preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
        if (count($matches) === 3 && $admin === $matches[0]) {
            $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
            $sth->execute([':username' => $username, ':identity' => $matches[1]]);
        } else {
            $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
            $sth->execute([':username' => $username]);
        }
        echo '<script>alert("register success");location.href="./index.html"</script>';
    

    member.php

    <?php
        include('config.php');
        try{
            $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
        }catch (Exception $e){
            die('mysql connected error');
        }
        $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
        $username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
        $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
        $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
    
        if (strlen($username) > 16 || strlen($username) > 16) {
            die('Invalid input');
        }
    
        $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
        $sth->execute([':username' => $username]);
        if ($sth->fetch() !== false) {
            die('username has been registered');
        }
    
        $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
        $sth->execute([':username' => $username, ':password' => $password]);
    
        preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
        if (count($matches) === 3 && $admin === $matches[0]) {
            $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
            $sth->execute([':username' => $username, ':identity' => $matches[1]]);
        } else {
            $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
            $sth->execute([':username' => $username]);
        }
        echo '<script>alert("register success");location.href="./index.html"</script>';
    

    走过路过,欢迎纠错

    相关文章

      网友评论

          本文标题:LCTF的一道preg_match绕过+出题人的锅

          本文链接:https://www.haomeiwen.com/subject/hxngvxtx.html