前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WriteUp分享 | LCTF的一道preg_match绕过+出题人的锅

WriteUp分享 | LCTF的一道preg_match绕过+出题人的锅

作者头像
安恒网络空间安全讲武堂
发布2018-02-06 12:02:27
7.5K1
发布2018-02-06 12:02:27
举报

0x00题目

http://123.206.120.239/

  • idea/workspace.xml 泄露文件信息,常见于用phpstorm写项目然后同步到github
  • 下载xdcms233.zip得到源码,有register.php login.php member.php 源码丢在最下面 主页是 index.html 通过按钮跳转到这几个php

0x01步骤

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

尝试打了payload,无果,参考文章 http://momomoxiaoxi.com/2017/09/18/WHCTF/

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

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

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

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

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

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

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

同时也有条件竞争的解法

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

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

login.php里面会清理session

memeber.php的会检测session

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

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

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

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

进入下面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

0x02后端源码: 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>';

走过路过,欢迎纠错~

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-11-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
远程调试
远程调试(Remote Debugging,RD)在云端为用户提供上千台真实手机/定制机/模拟器设备,快速实现随时随地测试。运用云测技术对测试方式、操作体验进行了优化,具备多样性的测试能力,包括随时截图和记录调试日志,稳定的支持自动化测试, 设备灵活调度,用例高效执行, 快速定位产品功能和兼容性问题。云手机帮助应用、移动游戏快速发现和解决问题,节省百万硬件费用,加速敏捷研发流程。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档