一、 Phar 简介
phar扩展提供了一种将整个PHP应用程序放入称为“ phar”(PHP归档文件)的单个文件中的方法,以便于分发和安装。 类似于java 中的jar包
phar 这个词是PHP和 Archive的缩写,它宽松地基于Java开发人员熟悉的jar(Java Archive)。
别的也不说了 详情看文档 phar文档
二、Phar用法
构建也是类似于jar
前提php.ini
phar.readonly = 0
构建如下目录
src 目录存放源码,build 用来存放构建好的phar文件 index.php用来构建phar
源码如下
/index.php
<?php
$phar = new Phar("build/test.phar",
FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME, "test.phar");
$phar->startBuffering();
$phar->buildFromDirectory("src", '/\.php$/');
$phar->setStub($phar->createDefaultStub("index.php"));
$phar->stopBuffering();
/src/index.php(调用打包后的php文件)
<?php
require_once "phar://test.phar/function.php";
require_once "phar://test.phar/function1.php";
?>
/src/function.php
<?php
class Test {
public $name;
public function run(){
echo("Test::run\n");
}
}
function myfunc(){
echo("myfunc\n");
}
?>
/src/function1.php
<?php
class Test1 {
public $name;
public function run(){
echo("Test1::run\n");
}
}
function myfunc1(){
echo("myfunc1\n");
}
?>
运行/index.php 会生成 /build/test.phar
然后就是利用test.phar文件 ,新建一个php 文件 运行效果如下
<?php
require 'phar://test.phar';
myfunc();
myfunc1();
$test = new Test();
$test->Run();
$test1 = new Test1();
$test1->Run();
?>
这样就实现了简单的封装功能
三、Phar 文件格式
格式主要如下
phar 文件的本质就是 php代码 + 数据 + 签名 直接php phar文件
就能运行,stub
简单来说就是当使用phar协议
执行phar 文件时如何去处理 类似序列化的__wakeup
,也就是刚才说的php代码部分
这里还要说一下__HALT_COMPILER()
函数的作用
简单来说就是中制编译,后面的代码不管对错都不会去编译,因此 数据 + 签名放在 __HALT_COMPILER()
之后
继续看stub部分
<?php
$phar = new Phar("test.phar",0,"test.phar");
$phar->startBuffering();
$phar->setStub("<?php phpinfo();__HALT_COMPILER();?>"); // 验证
$phar["aa"] = 123; //随便写点数据进去
$phar->stopBuffering();
?>
生成的phar文件内容
使用phar 协议调用test.phar 触发了 stub 部分的代码,因此__HALT_COMPILER()
必须要有否则会报错,之前的要符合php语法 及开始格式的xxx<?php xxx;__HALT_COMPILER();
stub 也可以使用默认的,但他的操作就很多
$phar->setDefaultStub();
代码太多就放开头和结尾一部分
四、Phar 漏洞利用
(1) 文件上传 + 文件包含
因为phar文件 特性,可以在stub最开始添加任意字符,而后缀又没有任何限制,可以绕过某些文件上传限制,然后再利用文件包含去调用phar中的内容
举个很早以前的题目,图片存储的题目
扫一下后台
url 存在f参数,
伪协议读取源码 php://filter/read/convert.base64-encode/resource=
然后分别读取到了 index.php upload.php show.php
- index.php
<?php
error_reporting(0);
@session_start();
// posix_setuid(1000);
$f = empty($_GET['f']) ? 'fail' : $_GET['f'];
if(preg_match('/\.\./',$f))
{
die('Be a good student!');
}
if(preg_match('/rm/i',$_SERVER["QUERY_STRING"]))
{
die();
}
foreach ($_POST as $key => $value) {
if(preg_match('/rm/i',$value))
{
die();
}
if(preg_match('/mv/i',$value))
{
die();
}
if(preg_match('/cp/i',$value))
{
die();
}
if(preg_match('/touch/i',$value))
{
die();
}
if(preg_match('/>/i',$value))
{
die();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>图像系统</title>
<meta charset="utf-8">
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1>图片存储</h1>
<p class="lead">请在这里上传您的图片,我们将为您保存:)</p>
<form action="?f=upload" method="POST" id="form" enctype="multipart/form-data">
<input type="file" id="image" name="image" class="btn btn-lg btn-success" style="margin-left: auto; margin-right: auto;">
<input type="submit" id="submit" name="submit" class="btn btn-lg btn-success" role="button" value="上传图片">
</form>
</div>
</div>
</body>
</html>
<?php
if($f !== 'fail')
{
if(!(include($f.'.php')))
{
?>
<div class="alert alert-danger" role="alert">NOPE</div>
<?php
exit;
}
}
?>
- uploads.php
<?php
if(isset($_POST['submit']) && !empty($_FILES['image']['tmp_name']))
{
$name = $_FILES['image']['tmp_name'];
$type = $_FILES['image']['type'];
$size = $_FILES['image']['size'];
if(!is_uploaded_file($name))
{
?>
<div class="alert alert-danger" role="alert">图片上传失败,请重新上传:)</div>
<?php
exit;
}
if($type !== 'image/png')
{
?>
<div class="alert alert-danger" role="alert">只能上传PNG图片</div>
<?php
exit;
}
if($size > 10240)
{
?>
<div class="alert alert-danger" role="alert">图片大小超过10KB</div>
<?php
exit;
}
function create_imageid()
{
return sha1($_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT'] . time() . mt_rand());
}
$imageid = create_imageid();
move_uploaded_file($name,"uploads/$imageid.png");
echo "<script>location.href='?f=show&imageid=$imageid'</script>";
}
?>
- show.php
<?php
@$imageid = $_GET['imageid'];
if(empty($imageid))
{
echo "<br>请输入imageid";
}
else
{
echo <<<EOF
<div class="alert alert-success" role="alert">
图像上传成功,<a href="uploads/$imageid.png" class="alert-link">点此查看</a>
</div>
EOF;
}
?>
构造phar 文件
shell.php
修改后缀然后上传
(2) Phar反序列化
只有通过phar::setMetadata();
设置的值才会被序列化放在stub 之后 通过直接赋值 无法序列化
需要添加额外数据 或者$phar->addFromString()
添加任意文件才能生成phar文件
调用后会进行反序列化, 利用的时候构造pop链 然后触发反序列化
五、参考
File Operation Induced Unserialization via the “phar://” Stream Wrapper