zoukankan      html  css  js  c++  java
  • 某CTF代码审计题

    记一次参加CTF比赛翻车记!

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

    开始还是挺有信心的,毕竟也是经常打一些CTF锻炼,然而比赛发现大佬们平时不显山不漏水的一比赛全出来了!赛后看了一下各题的writeup发现自己的确技不如人啊!借鉴一个案例拿出来分析一下!

    正言:

     

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

    这是入口界面登录+注册,开始注册登录看了一下

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

    有了一个简单的提你好啊,但是你好像不是XDSEC的人,所以我就不给你flag啦~~ 

    然而成功误导了我,百度了一番找到XDSEC官网,拿XDSEC的队员名称注册了一番,并无卵用!然后爆破目录、并无任何发现。

    思路断了!之后才知道是由于Phpstorm IDE 开发过程中会生成一个.idea的缓存目录(里面包含一些敏感文件)输入url/.idea/workspace.xml 成功下载下来

    CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog 

    发现一个zip压缩包(里面是它的源码无疑)下载后

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

     

    里面只有三个文件注册、登录、用户下面开始我们的代码审计

    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="http://ashe666.blog.163.com/blog/./index.html"</script>';

    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");
    ?>

    Member.php

    <?php
    error_reporting(0);
    session_start();
    include('config.php');
    if (isset($_SESSION['username']) === false) {
    die('please login first');
    }
    try{
    $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
    }catch (Exception $e){
    die('mysql connected error');
    }
    $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
    $_SESSION['is_guest'] = true;
    }
    
    $_SESSION['is_logined'] = true;
    if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
    
    }else{
    if(isset($_GET['file'])===false)
    echo "None";
    elseif(is_file($_GET['file']))
    echo "you cannot give me a file";
    else
    readfile($_GET['file']);
    }
    ?>
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body background="./images/1.jpg">
    <object type="application/x-shockwave-flash" style="outline:none;" data="http://cdn.abowman.com/widgets/hamster/hamster.swf?" width="300" height="225"><param name="movie" value="http://cdn.abowman.com/widgets/hamster/hamster.swf?"></param><param name="AllowScriptAccess" value="always"></param><param name="wmode" value="opaque"></param></object>
    <p style="color:orange">你好啊,但是你好像不是XDSEC的人,所以我就不给你flag啦~~</p>
    </body>
    </html>

    直到member.php发现是一个文件读取漏洞,既然是一个文件读取漏洞而前面看.idea缓存里面有config.php 这样也许就可以获取我们所需要的信息,而漏洞形成是需要条件的下面我们来具体分析一下。

    如果你能看懂上面的代码那么应该已经发现漏洞地点了。

     漏洞触发地点:

    member.php 第28行

    <?php
    error_reporting(0);
    session_start();
    include('config.php');
    if (isset($_SESSION['username']) === false) {
    die('please login first');
    }
    try{
    $pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
    }catch (Exception $e){
    die('mysql connected error');
    }
    $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
    $_SESSION['is_guest'] = true;
    }
    $_SESSION['is_logined'] = true;
    if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
    
    }else{
    if(isset($_GET['file'])===false)
    echo "None";
    elseif(is_file($_GET['file']))
    echo "you cannot give me a file";
    else
    readfile($_GET['file']);
    }
    ?>

    readfile($_GET['file']); 导致文件读取漏洞,漏洞利用的前提是先达到前面的条件

    isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true

     想让他们执行到else语句,必须绕过isset($_SESSION['is_guest']) === true这个判断条件

     $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
        $sth->execute([':username' => $_SESSION['username']]);
        if ($sth->fetch()[0] === 'GUEST') {
            $_SESSION['is_guest'] = true;
        }

     继续跟踪到register.php

      $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="http://ashe666.blog.163.com/blog/./index.html"</script>';
    
     

     这里进行一个判断满足条件

      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]]);
        } 

     那么我们只需要通过这个判断就可以绕过isset($_SESSION['is_guest']) === true这个条件

    $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
    $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';
    preg_match('/^(xdsec)((?:###|w)+)$/i', $code, $matches);
    if (count($matches) === 3 && $admin === $matches[0]) {

    而过程就是这个正则
    $code 是可控的匹配字符、$matches是返回值储存地址

    而上面就是需要让$matches[0]===$admin

    而$matches[0]是匹配到的值

    $admin的值由于str_shuffle函数是不确定的,当然我们也可以通过爆破来实现,然而我们还有一个更好的方法。

    通过$code传入长字符串来让preg_match函数消耗资源(拖延时间)导致后面的语句暂时无法执行,而此时我们的账户已经注册成功了,由于传入大量字符串preg_match不能在短时间内执行完成所以我们可以在这段时间内进行漏洞利用,由于数据库查询是空的所以可以绕过验证。

     漏洞复现:

    payload:to=reg&did=0&username=coolbreeze&password=coolbreeze&code=xdsec###超长字符串

    注册处使用burp拦截修改

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

     这是点击Go
    进入登录
    直接构造payload:member.php?file=php://filter/resource=config.php
    因为前面通过is_file()函数来过滤所以这里通过php伪协议来读取数据

     

     CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

     

     这里成功获取CTF

    $flag = "LCTF{pr3_maTch_1s_A_amaz1ng_Function}"
    到此结束

    CTF之翻车记(一) - Coolbreeze - Coolbreezes Blog

    
    
  • 相关阅读:
    Windows10字体模糊解决方法
    Synaptics触摸板在Windows10下双击弹出右键菜单无效的解决方法
    JDBC连接字符串及参数
    IntelliJ IDEA数据库工具连接MySQL提示Download missing driver files
    IntelliJ IDEA利用数据表生成JavaBean
    Windows下MySQL8.0的配置文件及数据库的默认目录
    MySQL配置说明
    (medium)LeetCode 221.Maximal Square
    (medium)LeetCode 222.Count Complete Tree Nodes
    (medium)LeetCode 224.Basic Calculator
  • 原文地址:https://www.cnblogs.com/ashe666/p/8268029.html
Copyright © 2011-2022 走看看