问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
关于Phar的深入探索与发现
CTF
相信大多数师傅应该都是和我之前一样, 对pahr的了解主要就是局限于它的压缩包功能和反序列化的使用, 但其实除了这两个功能外phar还提供了很多的其他函数, 甚至可以直接将一个phar文件作为一个简单的php服务运行.这几天因为一点别的原因我就对phar的使用做了更多的探索, 相信看了这篇文章之后可以解决不少小伙伴对phar的一些疑问
0x00 前言 ======= 经过几天的php源码调试和phar功能学习, 在这里写一个关于phar进一步探索的小总结吧 0x01 文件结构 ========= 我们先看一下官方对phar文件结构的说明: ![image-20221008022712956](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-a288f92c5ddc6cc488d79f3451a6be9994bd7f76.png) Stub存根 ------ Stub部分我们之前一般都是使用以下代码设置: ```php $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub ``` 这里的php格式代码在第一次学习phar文件的时候就激发了我的好奇心, 这段代码什么时候会被执行, \_\_HALT\_COMPILER又有什么用, 这里的代码能不能被我们利用,如果可以被利用的话什么时候可以使用, 这些问题一直以来都困扰着我, 直到最近才解决 **\_\_HALT\_COMPILER** 我们先来看一下`__HALT_COMPILER`这个函数的作用 ![image-20221008024527648](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-ed4e6fe5bd0fc3341a9da72546dedf4df85bd973.png) 这个函数是用于物理截断文件, 什么意思呢, 就相当于我们执行代码的时候后面的内容直接全部忽略, 一旦读取到`__HALT_COMPILER`就不再获取后面的内容 来个简单示例: ![image-20221008024846072](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-ea8dd5bfa6e523649fc8ae971a26d21a29daf8ee.png) 了解了它的作用之后我们再回到phar文件中, 我们需要知道为什么要用它, > 在pahr的stub存根中, `__HALT_COMPILER();`是必须要有的, 至于其他的内容其实是可有可无的, 在它后面的数据会直接被丢弃, 前面的内容则是直接原封不动的获取然后放到文件头中并且在后面加上`?>` > > ![image-20221008030224707](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-bc45d3ca175ad3ff7a8a588aca73cf876694a432.png) > > **为什么要使用\_\_HALT\_COMPILER** > > 之前我对stub存根数据格式定义为php代码格式一直都是很不理解的, 直到我这几天看到了它作为web服务的`.php`以及`.phps`用法的时候我才明白 > > 实际上phar格式除了定义为压缩包的格式之外还可以变为`.php`格式或者`.phps`格式的文件, 并且可以直接作为一个服务目录直接部署, 而`__HALT_COMPILER()`就是为了避免`.php`或者`.phps`格式的文件执行stub存根的代码的时候受到文件内容数据的影响,详细的继续往后看吧 文件描述和条目数据 --------- 从学习中可以知道这段数据主要由两个部分组成: `别名`+`Metadata反序列化元数据` ```php <?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->setAlias('h0cksr'); $phar->startBuffering(); $phar->setStub("<? __HALT_COMPILER();?>"); //设置stub $o = new TestObject(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 $phar->addFromString("flag.txt", "flag{test}"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); system("hexdump phar.phar") ?> ``` 生成的phar.phar文件16进制输出如下: ```txt 00000000: 3C 3F 20 5F 5F 48 41 4C 54 5F 43 4F 4D 50 49 4C <? __HALT_COMPIL 00000010: 45 52 28 29 3B 20 3F 3E 0D 0A 76 00 00 00 02 00 ER(); ?>..v..... 00000020: 00 00 11 00 00 00 01 00 06 00 00 00 68 30 63 6B ............h0ck 00000030: 73 72 16 00 00 00 4F 3A 31 30 3A 22 54 65 73 74 sr....O:10:"Test 00000040: 4F 62 6A 65 63 74 22 3A 30 3A 7B 7D 08 00 00 00 Object":0:{}.... 00000050: 74 65 73 74 2E 74 78 74 04 00 00 00 CD 7C 40 63 test.txt.....|@c 00000060: 04 00 00 00 0C 7E 7F D8 B6 01 00 00 00 00 00 00 .....~.......... 00000070: 08 00 00 00 66 6C 61 67 2E 74 78 74 0A 00 00 00 ....flag.txt.... 00000080: CD 7C 40 63 0A 00 00 00 F9 67 6B 85 B6 01 00 00 .|@c.....gk..... 00000090: 00 00 00 00 74 65 73 74 66 6C 61 67 7B 74 65 73 ....testflag{tes 000000A0: 74 7D 78 A3 03 F7 CB 91 1F 44 FC 0E 7E 76 80 70 t}x......D..~v.p 000000B0: 3C DC 7A 6F 6D 14 02 00 00 00 47 42 4D 42 <.zom.....GBMB ``` 可以看到在stub存根`<? __HALT_COMPILER(); ?>`之后跟着的数据中有我们设置的别名`h0cksr`,还有反序列化Matedata元数据`O:10:"TestObject":0:{}` 至于其它的不可见数据, 还没从源码仔细深挖过, 但是我想应该是别名以及Metadata原数据的一些16进制标识符以及一些相关信息吧 然后找手册翻了一会之后至于找到那些不可见字符格式的16进制数据所代表的含义了: ![image-20221008034004681](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-6f43ed2545b7f5d51a60b708f08f2e8459dacb3d.png) 文件数据 ---- 文件数据部分我们的文件名和文件内容的数据可以说是原封不动的可以看到了, 简单观察可以看到它是先有全部的文件名在前面(我想这应该是以phar的某种格式, 通过特定的格式定义还原拿到目录结构)然后后面接的就是文件内容了,而且这些文件的内容是无缝衔接直接连在一起的, 所以我们可以合理推断前面的文件名那部分数据不仅定义了文件结构, 而且文件大小也是在里面的 之后回去看一下可以发现确实如此, 在原始的文件名数据部分中, 它的两个文件数据部分结构均为`文件名+文件长度+7字节的16进制数据000000CD7C4063`(这些都是使用pack压缩打包的数据,文章后面我们会说到使用unpack将其解压缩) 文件签名 ---- Phar支持的签名格式有下面几种: MD5/SHA1/SHA256/SHA512/OpenSSL pahr默认使用sha1加密就是有`20字节`的签名生成结果, 在签名后面还有`8字节`,`前4字节表示文件使用的签名算法`,`最后四字节固定用于表示该文件存在签名` 因为签名是整个pahr最后的数据段, 所以默认情况下我们可以知道`文件最后的28字节`为: 签名(默认sha1有20字节大小)+签名方式(4字节)+声明文件有无签名(4字节) 了解了phar的整个文件结构之后, 就让我们看一下phar的文件都有哪些使用方法吧: 0x02 作为普通的压缩文件使用 ================ 作为一般的压缩文件可以说是我们最基本的使用方法了, 对于这点, 如果是常规的单个文件的存储获取这些我并不想多说, 一般的用法就是通过`phar:///path/to/phar.phar/pahrdir/pharfile`读出文件内容 0x03 作为一个服务压缩包启用 ================ 我们需要先明白一点, 那就是在pahr压缩包中的文件如果被包含了的话那么对于里面的代码来说他们是在一个全新的目录结构中的, 并且phar压缩包中的文件之间是可以相互包含的 ![image-20221008040543031](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-05b2585532ed00477903cff7c39173d8a83581d4.png) 可以看到,我们在一个文件中包含一个pahr压缩包中的文件和普通的文件包含并没有什么区别, 都可以传递全局变量 但是有一点是很大的不同, 那就是输出`__FILE__`可以看到显示的文件路径直接就是`phar://`协议的文件路径, 使用include文件包含的时候默认包含的是当前phar压缩包内同目录下的文件 另外还有一点是需要区别的, 那就是在`test1.php`中对`test2.php`和`./test2.php`的解析效果是不一样的 - `test2.php`指向的是`pahr://phar.phar/test2.php` - `表示test2.php`指向的是`C:\phpstudy_pro\WWW\test\test2.php` 我们先继续包含默认的`test2.php`, 然后在`test2.php`添加一个`var_dump(scandir("."))`扫描当前目录并且输出看一下输出的结果是`phar://phar.phar`的目录还是`C:\phpstudy_pro\WWW\test`的目录 ![image-20221008041427445](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-f5feaee0ac3865d87ba005cbec18ec60096c2b0a.png) 可以看到输出显示的是`C:\phpstudy_pro\WWW\test`目录扫描结果,这时候我们再将`test1.php`中包含的文件改为`./test2.php`看一下会不会成功包含解析 ![image-20221008041420881](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-b93cba5e3e359247bf329c5c6d7b9e97f718a935.png) 从结果可以看到并没有执行`phar://phar.phar/test2.php`的代码, 这就说明其寻找解析的应该是`C:\phpstudy_pro\WWW\test\test2.php`(但是不存在这个文件) 0x04 作为一个服务页面启用 =============== 下面我们就看一下一个phar结构的文件是如何以`.php`的格式直接运行或者`.phps`被包含如何直接启动一个服务的. 在使用`.php`和`.phps`之前先继续使用`.pahr`格式的数据看一下默认的`Stub`存根数据 我们上面了解文件结构的时候使用`setStub`函数设置了存根数据, 那么如果我们不设置的话会不会报错呢? 答案是不会的, 并且会使用原生默认的stub数据, 执行一下可以看到(): ![image-20221008034744813](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-6adfd50b4038a9f1e80775955d3feb2607ac4f7f.png) 我们这时候可以看到, 在注释了`setStub`函数之后还是会有存根代码, 依旧为一段php代码, 而且还很长, 将这段代码拉出来看一下吧: ![image-20221008035039396](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-e7fa36ef1fe5ed9cf6f59269cf82a2c3bb703625.png) 存根数据会被放到`stub.php`中 这里的代码说长不长说短不短, 这段代码放在phar里面我们或许并没有太多的感觉, 但是如果我们将它放在一个`.php`文件中呢??? 修改一下phar的生成代码: ```php <?php class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->setAlias('h0cksr'); $phar->startBuffering(); /*$phar->setStub("<? __HALT_COMPILER();?>"); //设置stub*/ $o = new TestObject(); $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("index.php","<?php echo 'this is index.php';?>"); //签名自动计算 $phar->stopBuffering(); ?> ``` 我们直接给phar.phar添加一个`.php`后缀名,变为`phar.phar.php`, 复制的数据如下: ```php <?php $web = 'index.php'; if (in_array('phar', stream_get_wrappers()) && class_exists('Phar', 0)) { Phar::interceptFileFuncs(); set_include_path('phar://' . __FILE__ . PATH_SEPARATOR . get_include_path()); Phar::webPhar(null, $web); include 'phar://' . __FILE__ . '/' . Extract_Phar::START; return; } if (@(isset($_SERVER['REQUEST_URI']) && isset($_SERVER['REQUEST_METHOD']) && ($_SERVER['REQUEST_METHOD'] == 'GET' || $_SERVER['REQUEST_METHOD'] == 'POST'))) { Extract_Phar::go(true); $mimes = array( 'phps' => 2, 'c' => 'text/plain', 'cc' => 'text/plain', 'cpp' => 'text/plain', 'c++' => 'text/plain', 'dtd' => 'text/plain', 'h' => 'text/plain', 'log' => 'text/plain', 'rng' => 'text/plain', 'txt' => 'text/plain', 'xsd' => 'text/plain', 'php' => 1, 'inc' => 1, 'avi' => 'video/avi', 'bmp' => 'image/bmp', 'css' => 'text/css', 'gif' => 'image/gif', 'htm' => 'text/html', 'html' => 'text/html', 'htmls' => 'text/html', 'ico' => 'image/x-ico', 'jpe' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'js' => 'application/x-javascript', 'midi' => 'audio/midi', 'mid' => 'audio/midi', 'mod' => 'audio/mod', 'mov' => 'movie/quicktime', 'mp3' => 'audio/mp3', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'pdf' => 'application/pdf', 'png' => 'image/png', 'swf' => 'application/shockwave-flash', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'wav' => 'audio/wav', 'xbm' => 'image/xbm', 'xml' => 'text/xml', ); header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); $basename = basename(__FILE__); if (!strpos($_SERVER['REQUEST_URI'], $basename)) { chdir(Extract_Phar::$temp); include $web; return; } $pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename)); if (!$pt || $pt == '/') { $pt = $web; header('HTTP/1.1 301 Moved Permanently'); header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt); exit; } $a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt); if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) { header('HTTP/1.0 404 Not Found'); echo "<html>\n <head>\n <title>File Not Found<title>\n </head>\n <body>\n <h1>404 - File Not Found</h1>\n </body>\n</html>"; exit; } $b = pathinfo($a); if (!isset($b['extension'])) { header('Content-Type: text/plain'); header('Content-Length: ' . filesize($a)); readfile($a); exit; } if (isset($mimes[$b['extension']])) { if ($mimes[$b['extension']] === 1) { include $a; exit; } if ($mimes[$b['extension']] === 2) { highlight_file($a); exit; } header('Content-Type: ' .$mimes[$b['extension']]); header('Content-Length: ' . filesize($a)); readfile($a); exit; } } class Extract_Phar { static $temp; static $origdir; const GZ = 0x1000; const BZ2 = 0x2000; const MASK = 0x3000; const START = 'index.php'; const LEN = 6643; static function go($return = false) { $fp = fopen(__FILE__, 'rb'); fseek($fp, self::LEN); $L = unpack('V', $a = fread($fp, 4)); $m = ''; do { $read = 8192; if ($L[1] - strlen($m) < 8192) { $read = $L[1] - strlen($m); } $last = fread($fp, $read); $m .= $last; } while (strlen($last) && strlen($m) < $L[1]); if (strlen($m) < $L[1]) { die('ERROR: manifest length read was "' . strlen($m) .'" should be "' . $L[1] . '"'); } $info = self::_unpack($m); $f = $info['c']; if ($f & self::GZ) { if (!function_exists('gzinflate')) { die('Error: zlib extension is not enabled -' . ' gzinflate() function needed for zlib-compressed .phars'); } } if ($f & self::BZ2) { if (!function_exists('bzdecompress')) { die('Error: bzip2 extension is not enabled -' . ' bzdecompress() function needed for bz2-compressed .phars'); } } $temp = self::tmpdir(); if (!$temp || !is_writable($temp)) { $sessionpath = session_save_path(); if (strpos ($sessionpath, ";") !== false) $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1); if (!file_exists($sessionpath) || !is_dir($sessionpath)) { die('Could not locate temporary directory to extract phar'); } $temp = $sessionpath; } $temp .= '/pharextract/'.basename(__FILE__, '.phar'); self::$temp = $temp; self::$origdir = getcwd(); @mkdir($temp, 0777, true); $temp = realpath($temp); if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) { self::_removeTmpFiles($temp, getcwd()); @mkdir($temp, 0777, true); @file_put_contents($temp . '/' . md5_file(__FILE__), ''); foreach ($info['m'] as $path => $file) { $a = !file_exists(dirname($temp . '/' . $path)); @mkdir(dirname($temp . '/' . $path), 0777, true); clearstatcache(); if ($path[strlen($path) - 1] == '/') { @mkdir($temp . '/' . $path, 0777); } else { file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); @chmod($temp . '/' . $path, 0666); } } } chdir($temp); if (!$return) { include self::START; } } static function tmpdir() { if (strpos(PHP_OS, 'WIN') !== false) { if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { return $var; } if (is_dir('/temp') || mkdir('/temp')) { return realpath('/temp'); } return false; } if ($var = getenv('TMPDIR')) { return $var; } return realpath('/tmp'); } static function _unpack($m) { $info = unpack('V', substr($m, 0, 4)); $l = unpack('V', substr($m, 10, 4)); $m = substr($m, 14 + $l[1]); $s = unpack('V', substr($m, 0, 4)); $o = 0; $start = 4 + $s[1]; $ret['c'] = 0; for ($i = 0; $i < $info[1]; $i++) { $len = unpack('V', substr($m, $start, 4)); $start += 4; $savepath = substr($m, $start, $len[1]); $start += $len[1]; $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] & 0xffffffff); $ret['m'][$savepath][7] = $o; $o += $ret['m'][$savepath][2]; $start += 24 + $ret['m'][$savepath][5]; $ret['c'] |= $ret['m'][$savepath][4] & self::MASK; } return $ret; } static function extractFile($path, $entry, $fp) { $data = ''; $c = $entry[2]; while ($c) { if ($c < 8192) { $data .= @fread($fp, $c); $c = 0; } else { $c -= 8192; $data .= @fread($fp, 8192); } } if ($entry[4] & self::GZ) { $data = gzinflate($data); } elseif ($entry[4] & self::BZ2) { $data = bzdecompress($data); } if (strlen($data) != $entry[0]) { die("Invalid internal .phar file (size error " . strlen($data) . " != " . $stat[7] . ")"); } if ($entry[3] != sprintf("%u", crc32($data) & 0xffffffff)) { die("Invalid internal .phar file (checksum error)"); } return $data; } static function _removeTmpFiles($temp, $origdir) { chdir($temp); foreach (glob('*') as $f) { if (file_exists($f)) { is_dir($f) ? @rmdir($f) : @unlink($f); if (file_exists($f) && is_dir($f)) { self::_removeTmpFiles($f, getcwd()); } } } @rmdir($temp); clearstatcache(); chdir($origdir); } } Extract_Phar::go(); __HALT_COMPILER(); ?>S h0cksr O:10:"TestObject":0:{} index.php! _�@c! �(�'� <?php echo 'this is index.php';?>�Vpi����Ӿ�\v����x GBMB ``` 可以看到有两百多行代码, 但是它的结构是很简单的,就是两个if判断语句还有一个`Extract_Phar::go();`(第二个if语句也会执行这个go函数) ![image-20221008053517106](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-d9863e8c19155a3354695268698301f37048c6d3.png) **第一个if**(事实上一般我们都是在第一个if就结束了): 判断`pahr`在不在`当前运行系统中已经注册并可使用的流类型列表`中, 并且判断是否存在`Phar`类, 如果都有的话进入第一个if: 前面几行代码中我们只需要关注两行代码: ```php set_include_path('phar://' . __FILE__ . PATH_SEPARATOR . get_include_path()); include 'phar://' . __FILE__ . '/' . Extract_Phar::START;//Extract_Phar::START默认就是index.php ``` 第一行代码先是将当前文件经过phar://解析后作为第一优先级的包含目录, 第二行代码使用phar://解析当前文件并且包含pahr包里的`index.php`文件 **第二个if** 第二个if先是执行`Extract_Phar::go(true);`,然后就是进行服务逻辑判断, 中间进行了很多的请求判断最终返回相应的结果到客户端 **Extract\_Phar::go(true);** 这个`go`函数就是用于解压phar文件的, 因为后面这些代码都是在第一个if判断不满足, 也就是当前拓展不直接顺利解压pahr文件的时候执行的, 所以可以说代码就是使用php代码的形式实现pahr压缩包的解压了. 可以看到里面使用的`_unpack`函数对phar的原始数据进行了多次多点的`unpack`数据解压, 从而得到数据的目录结构,文件信息以及文件内容,将其一一对应起来 > 此外有一个变量参数是值得我们注意的, 那就是Extract\_Phar::LEN > 这个变量可以在代码中看到在读取数据进行解压之前先是读取了这个LEN并且移动指针偏移LEN个字节,然后再进行数据读取和解压工作 > 默认情况下我们之所以任意在Stub添加数据的原因就是因为这个LEN,在pahr格式的文件中有对应的`LEN`大小pack压缩数据指定文件头偏移 ### 一次失败的尝试小记 虽然说没有成功但也算是一次简单的思维发散了, 所以就记一下吧 在整个默认的stub存根代码中最引起我关注的便是`Extract_Phar::go`的解压写入文件的过程, 因为开始的时候我认为后面的代码是在默认不支持phar文件格式的解压的情况下执行的php代码, 正是这些php代码的功能完成了phar文件的解压, 所以我就认为默认的phar解释器对其进行phar协议数据解析的时候操作的实际解压过程也和这里的php源码一样, 如果是一样的话, 那么我们就可以任意文件写了, 因为它的文件名是从原始数据`unpack`解压拿到的, 而且会将这个文件名直接拼接到临时文件夹目录后,然后将原始文件数据写入到临时文件夹下的历史文件中, 那么这时候我们是否可以将文件名修改为`../../../多个上级目录/tmp/php`然后往`/tmp/php`写文件(或者像之前p牛一样通过非法文件名让文件驻留在解压后的目录下)? 但是其实不然, 在我使用`010editor`修改pahr文件中的文件名, 然后又进行哈希修复后发现并没有成功, 从始至终我们都没有看到phar往/tmp生成任何文件, 我的尝试步骤如下: 1. 生成有两个文件`012345678901234567890123456789012.php`和`1.php`的pahr压缩包`pahr.phar` 2. 使用`010editor`将`012345678901234567890123456789012.php`修改为`../../../../../../../../../../tmp/php` 3. 使用签名修复脚本更新签名 4. 使用`fswatch`监控`/tmp`目录文件的变化 5. 执行php测试代码执行`file_get_contents`使用`pahr://`协议解析修改后的pahr压缩包取出里面的文件输出文件内容 6. 最后显示结果是`/tmp`下面并没有解压生成`/tmp/php`文件, 而pahr压缩包中的`1.php`被成功输出说明签名修正是没有问题的, 但是输出原本的`012345678901234567890123456789012.php`三种方式均失败了 ![image-20221008072218482](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-c69042c9e33954c798d1f7486ceaa7370c998574.png) 之后看到默认的stub存根代码中会在临时目录下创建文件夹`pharextract`, 然后我就到php源码中检索了一下这个关键字, 结果是None, 这就说明php解释器对phar文件的解析确实是与默认stub存根中`Extract_Phar::go`函数的代码是有所区别的 对于文件解压的过程我并没有去debug到详细的函数点, 但是对解析报错的语句进行代码溯源确定到了PHP解释器源代码中的`ext/phar/util.c:342 phar_get_entry_data`, 正是在这个函数中获取资源失败返回了`FAILURE`才导致输出警告`pahr error:"xxx" is not a file in phar "phar.phar"`但是往回走找到解压函数以及接续往下走找到更深更详细的函数代码点我并没有做, 在这里放一个调试的调用栈感兴趣的师傅可以去跟一下: ![image-20221008074419462](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-e68b8694554041821a30cb42f83ab4bd3260b552.png) 0x05 更多其他... ============ 至于其他一些有意思的用法这里简单的说两个吧: 别名使用 ---- ![image-20221008075012654](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-e01688c6d357e1bfdee5735a7887c42826a5027a.png) 但是这个别名的使用只能在生成文件的当前以及phar的内部, 如果将这个生成的`pahr.phar`放到一个新的目录下, 任何在一个test.php中写入以下测试代码: ```php <?php var_dump(file_get_contents("phar://h0cksr/1.php")); ``` 此时执行`test.php`就会失败了,因为此时并不知道这个别名,只会将`h0cksr`作为文件查找 ![image-20221008075335492](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-bc01df88af0a2a9475c04a1505894b462516bd1b.png) Phar::webPhar ------------- ```php <?php // creating the phar archive: try { $phar = new Phar('myphar.phar'); $phar['index.php'] = '<?php echo "Hello World,this is index.php"; ?>'; $phar['hello.php'] = '<?php echo "Hello World,this is hello.php"; ?>'; $phar['default.php'] = '<?php echo "Hello World,this is default.php"; ?>'; $phar->setStub('<?php Phar::webPhar("","index.php","default.php"); __HALT_COMPILER(); ?>'); } catch (Exception $e) { // handle error here } ?> ``` 生成`myphar.phar`后重命名为`myphar.phar.php`,任何就可以将其作为一个服务访问了 在Stub存根中的`Phar::webPhar`函数作用就是指定服务的默认加载文件, 指定如果访问的服务资源不存在时加载的文件 以上demo中默认首页加载`index.php`, 访问的资源不存在时加载`default.php`, 我们定义的`hello.php`可以当做一个资源访问: **/myphar.phar.php** ![image-20221008080615821](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-675c47ac50901e34f94f41f92a508d8b7c8a0a8d.png) **/myphar.phar.php/hello.php** ![image-20221008080645778](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-cbe1d23240387f6896a09d6d7c4aea98add3e56f.png) **/myphar.phar.php/xxx.php** ![image-20221008080707578](https://shs3.b.qianxin.com/attack_forum/2022/10/attach-f254671fa41baee555106852f442a7fb416c3cd6.png) 此外`Phar::webPhar`的第一个参数可以使用别名指定phar压缩包, 指定之后后面的首页资源路径和默认资源路径均是别名对应压缩包下的资源文件 0x06 相关类和函数 =========== - Phar \- Phar 类 - [Phar::addEmptyDir](https://www.php.net/manual/zh/phar.addemptydir.php) — 添加一个空目录到 phar 档案 - [Phar::addFile](https://www.php.net/manual/zh/phar.addfile.php) — 将一个文件从文件系统添加到 phar 档案中 - [Phar::addFromString](https://www.php.net/manual/zh/phar.addfromstring.php) — 以字符串的形式添加一个文件到 phar 档案 - [Phar::apiVersion](https://www.php.net/manual/zh/phar.apiversion.php) — 返回 api 版本 - [Phar::buildFromDirectory](https://www.php.net/manual/zh/phar.buildfromdirectory.php) — 从目录中的文件构建 phar 归档 - [Phar::buildFromIterator](https://www.php.net/manual/zh/phar.buildfromiterator.php) — 从迭代器构造 phar 档案 - [Phar::canCompress](https://www.php.net/manual/zh/phar.cancompress.php) — 返回 phar 扩展是否支持使用 zlib 或 bzip2 进行压缩 - [Phar::canWrite](https://www.php.net/manual/zh/phar.canwrite.php) — 返回 phar 扩展是否支持写入和创建 phas - [Phar::compress](https://www.php.net/manual/zh/phar.compress.php) — 使用 Gzip 或 Bzip2 压缩压缩整个 Phar 存档 - [Phar::compressFiles](https://www.php.net/manual/zh/phar.compressfiles.php) — 压缩当前 Phar 存档中的所有文件 - [Phar::\_\_construct](https://www.php.net/manual/zh/phar.construct.php) — 构造一个 Phar 归档对象 - [Phar::convertToData](https://www.php.net/manual/zh/phar.converttodata.php) — 将 phar 存档转换为不可执行的 tar 或 zip 文件 - [Phar::convertToExecutable](https://www.php.net/manual/zh/phar.converttoexecutable.php) — 将 phar 存档转换为另一种可执行的 phar 存档文件格式 - [Phar::copy](https://www.php.net/manual/zh/phar.copy.php) — 将 phar 存档内部的文件复制到 phar 中的另一个新文件 - [Phar::count](https://www.php.net/manual/zh/phar.count.php) — 返回 Phar 存档中的条目(文件)数 - [Phar::createDefaultStub](https://www.php.net/manual/zh/phar.createdefaultstub.php) — 创建一个 phar 文件格式特定的存根 - [Phar::decompress](https://www.php.net/manual/zh/phar.decompress.php) — 解压整个 Phar 档案 - [Phar::decompressFiles](https://www.php.net/manual/zh/phar.decompressfiles.php) — 解压当前 Phar 存档中的所有文件 - [Phar::delMetadata](https://www.php.net/manual/zh/phar.delmetadata.php) — 删除 phar 的全局元数据 - [Phar::delete](https://www.php.net/manual/zh/phar.delete.php) — 删除 phar 档案中的一个文件 - [Phar::\_\_destruct](https://www.php.net/manual/zh/phar.destruct.php) — Phar 归档对象的析构函数 - [Phar::extractTo](https://www.php.net/manual/zh/phar.extractto.php) — 将 phar 存档的内容提取到目录 - [Phar::getAlias](https://www.php.net/manual/zh/phar.getalias.php) — 获取 Phar 的别名 - [Phar::getMetadata](https://www.php.net/manual/zh/phar.getmetadata.php) — 返回 phar 存档元数据 - [Phar::getModified](https://www.php.net/manual/zh/phar.getmodified.php) — 返回 phar 是否被修改 - [Phar::getPath](https://www.php.net/manual/zh/phar.getpath.php) — 获取磁盘上 Phar 存档的真实路径 - [Phar::getSignature](https://www.php.net/manual/zh/phar.getsignature.php) — 返回 Phar 存档的 MD5/SHA1/SHA256/SHA512/OpenSSL 签名 - [Phar::getStub](https://www.php.net/manual/zh/phar.getstub.php) — 返回 Phar 存档的 PHP 加载器或引导存根 - [Phar::getSupportedCompression](https://www.php.net/manual/zh/phar.getsupportedcompression.php) — 返回支持的压缩算法数组 - [Phar::getSupportedSignatures](https://www.php.net/manual/zh/phar.getsupportedsignatures.php) — 返回支持的签名类型数组 - [Phar::getVersion](https://www.php.net/manual/zh/phar.getversion.php) — 返回 Phar 存档的版本信息 - [Phar::hasMetadata](https://www.php.net/manual/zh/phar.hasmetadata.php) — 返回 phar 是否有全局元数据 - [Phar::interceptFileFuncs](https://www.php.net/manual/zh/phar.interceptfilefuncs.php) — 指示 phar 拦截 fopen、file\_get\_contents、opendir 和所有与 stat 相关的函数 - [Phar::isBuffering](https://www.php.net/manual/zh/phar.isbuffering.php) — 用于确定 Phar 写操作是被缓冲,还是直接刷新到磁盘 - [Phar::isCompressed](https://www.php.net/manual/zh/phar.iscompressed.php) — 如果整个 phar 存档已压缩(.tar.gz/tar.bz 等),则返回 Phar::GZ 或 PHAR::BZ2 - [Phar::isFileFormat](https://www.php.net/manual/zh/phar.isfileformat.php) — 如果 phar 存档基于 tar/phar/zip 文件格式,则返回 true,具体取决于参数 - [Phar::isValidPharFilename](https://www.php.net/manual/zh/phar.isvalidpharfilename.php) — 返回给定文件名是否是有效的 phar 文件名 - [Phar::isWritable](https://www.php.net/manual/zh/phar.iswritable.php) — 如果 phar 存档可以修改,则返回 true - [Phar::loadPhar](https://www.php.net/manual/zh/phar.loadphar.php) — 使用别名加载任何 phar 存档 - [Phar::mapPhar](https://www.php.net/manual/zh/phar.mapphar.php) — 读取当前执行的文件(一个 phar)并注册它的清单 - [Phar::mount](https://www.php.net/manual/zh/phar.mount.php) — 将外部路径或文件挂载到 phar 存档中的虚拟位置 - [Phar::mungServer](https://www.php.net/manual/zh/phar.mungserver.php) — 定义最多 4 个 $\_SERVER 变量的列表,这些变量应该被修改以执行 - [Phar::offsetExists](https://www.php.net/manual/zh/phar.offsetexists.php) — 确定 phar 中是否存在文件 - [Phar::offsetGet](https://www.php.net/manual/zh/phar.offsetget.php) — 获取特定文件的 PharFileInfo 对象 - [Phar::offsetSet](https://www.php.net/manual/zh/phar.offsetset.php) — 将内部文件的内容设置为外部文件的内容 - [Phar::offsetUnset](https://www.php.net/manual/zh/phar.offsetunset.php) — 从 phar 中删除文件 - [Phar::running](https://www.php.net/manual/zh/phar.running.php) — 返回磁盘上的完整路径或当前执行的 Phar 存档的完整 phar URL - [Phar::setAlias](https://www.php.net/manual/zh/phar.setalias.php) — 设置 Phar 档案的别名 - [Phar::setDefaultStub](https://www.php.net/manual/zh/phar.setdefaultstub.php) — 用于将 Phar 存档的 PHP 加载器或引导存根设置为默认加载器 - [Phar::setMetadata](https://www.php.net/manual/zh/phar.setmetadata.php) — 设置 phar 存档元数据 - [Phar::setSignatureAlgorithm](https://www.php.net/manual/zh/phar.setsignaturealgorithm.php) — 设置 phar 的签名算法并应用它 - [Phar::setStub](https://www.php.net/manual/zh/phar.setstub.php) — 用于设置 Phar 存档的 PHP 加载器或引导存根 - [Phar::startBuffering](https://www.php.net/manual/zh/phar.startbuffering.php) — 开始缓冲 Phar 写操作,不要修改磁盘上的 Phar 对象 - [Phar::stopBuffering](https://www.php.net/manual/zh/phar.stopbuffering.php) — 停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘 - [Phar::unlinkArchive](https://www.php.net/manual/zh/phar.unlinkarchive.php) — 从磁盘和内存中完全删除 phar 存档 - [Phar::webPhar](https://www.php.net/manual/zh/phar.webphar.php) — 将来自 Web 浏览器的请求路由到 phar 存档中的内部文件 - PharData — PharData 类 - [PharData::addEmptyDir](https://www.php.net/manual/zh/phardata.addemptydir.php) — 将一个空目录添加到 tar/zip 存档中 - [PharData::addFile](https://www.php.net/manual/zh/phardata.addfile.php) — 将文件系统中的文件添加到 tar/zip 存档 - [PharData::addFromString](https://www.php.net/manual/zh/phardata.addfromstring.php) — 将文件系统中的文件添加到 tar/zip 存档 - [PharData::buildFromDirectory](https://www.php.net/manual/zh/phardata.buildfromdirectory.php) — 从目录中的文件构造一个 tar/zip 存档 - [PharData::buildFromIterator](https://www.php.net/manual/zh/phardata.buildfromiterator.php) — 从迭代器构造 tar 或 zip 存档 - [PharData::compress](https://www.php.net/manual/zh/phardata.compress.php) — 使用 Gzip 或 Bzip2 压缩压缩整个 tar/zip 存档 - [PharData::compressFiles](https://www.php.net/manual/zh/phardata.compressfiles.php) — 压缩当前 tar/zip 存档中的所有文件 - [PharData::\_\_construct](https://www.php.net/manual/zh/phardata.construct.php) — 构造一个不可执行的 tar 或 zip 归档对象 - [PharData::convertToData](https://www.php.net/manual/zh/phardata.converttodata.php) — 将 phar 存档转换为不可执行的 tar 或 zip 文件 - [PharData::convertToExecutable](https://www.php.net/manual/zh/phardata.converttoexecutable.php) — 将不可执行的 tar/zip 存档转换为可执行的 phar 存档 - [PharData::copy](https://www.php.net/manual/zh/phardata.copy.php) — 将 phar 存档内部的文件复制到 phar 中的另一个新文件 - [PharData::decompress](https://www.php.net/manual/zh/phardata.decompress.php) — 解压整个 Phar 档案 - [PharData::decompressFiles](https://www.php.net/manual/zh/phardata.decompressfiles.php) — 解压缩当前 zip 存档中的所有文件 - [PharData::delMetadata](https://www.php.net/manual/zh/phardata.delmetadata.php) — 删除 zip 存档的全局元数据 - [PharData::delete](https://www.php.net/manual/zh/phardata.delete.php) — 删除 tar/zip 存档中的文件 - [PharData::\_\_destruct](https://www.php.net/manual/zh/phardata.destruct.php) — 破坏一个不可执行的 tar 或 zip 归档对象 - [PharData::extractTo](https://www.php.net/manual/zh/phardata.extractto.php) — 将 tar/zip 存档的内容提取到目录 - [PharData::isWritable](https://www.php.net/manual/zh/phardata.iswritable.php) — 如果可以修改 tar/zip 存档,则返回 true - [PharData::offsetSet](https://www.php.net/manual/zh/phardata.offsetset.php) — 将 tar/zip 中的文件内容设置为外部文件或字符串的内容 - [PharData::offsetUnset](https://www.php.net/manual/zh/phardata.offsetunset.php) — 从 tar/zip 存档中删除文件 - [PharData::setAlias](https://www.php.net/manual/zh/phardata.setalias.php) — 虚拟函数(Phar::setAlias 对 PharData 无效) - [PharData::setDefaultStub](https://www.php.net/manual/zh/phardata.setdefaultstub.php) — 虚拟函数(Phar::setDefaultStub 对 PharData 无效) - [PharData::setMetadata](https://www.php.net/manual/zh/phardata.setmetadata.php) — 设置 phar 存档元数据 - [PharData::setSignatureAlgorithm](https://www.php.net/manual/zh/phardata.setsignaturealgorithm.php) — 设置 phar 的签名算法并应用它 - [PharData::setStub](https://www.php.net/manual/zh/phardata.setstub.php) — 虚拟函数(Phar::setStub 对 PharData 无效) - PharFileInfo — PharFileInfo 类 - [PharFileInfo::chmod](https://www.php.net/manual/zh/pharfileinfo.chmod.php) — 设置文件特定的权限位 - [PharFileInfo::compress](https://www.php.net/manual/zh/pharfileinfo.compress.php) — 使用 zlib 或 bzip2 压缩压缩当前 Phar 条目 - [PharFileInfo::\_\_construct](https://www.php.net/manual/zh/pharfileinfo.construct.php) — 构造一个 Phar 入口对象 - [PharFileInfo::decompress](https://www.php.net/manual/zh/pharfileinfo.decompress.php) — 解压缩 phar 中的当前 Phar 条目 - [PharFileInfo::delMetadata](https://www.php.net/manual/zh/pharfileinfo.delmetadata.php) — 删除条目的元数据 - [PharFileInfo::\_\_destruct](https://www.php.net/manual/zh/pharfileinfo.destruct.php) — 破坏一个 Phar 入口对象 - [PharFileInfo::getCRC32](https://www.php.net/manual/zh/pharfileinfo.getcrc32.php) — 返回 CRC32 代码或在 CRC 未验证时抛出异常 - [PharFileInfo::getCompressedSize](https://www.php.net/manual/zh/pharfileinfo.getcompressedsize.php) — 返回 Phar 存档中文件的实际大小(带压缩) - [PharFileInfo::getContent](https://www.php.net/manual/zh/pharfileinfo.getcontent.php) — 获取条目的完整文件内容 - [PharFileInfo::getMetadata](https://www.php.net/manual/zh/pharfileinfo.getmetadata.php) — 返回与文件一起保存的特定于文件的元数据 - [PharFileInfo::getPharFlags](https://www.php.net/manual/zh/pharfileinfo.getpharflags.php) — 返回 Phar 文件条目标志 - [PharFileInfo::hasMetadata](https://www.php.net/manual/zh/pharfileinfo.hasmetadata.php) — 返回条目的元数据 - [PharFileInfo::isCRCChecked](https://www.php.net/manual/zh/pharfileinfo.iscrcchecked.php) — 返回文件条目是否已验证其 CRC - [PharFileInfo::isCompressed](https://www.php.net/manual/zh/pharfileinfo.iscompressed.php) — 返回条目是否被压缩 - [PharFileInfo::setMetadata](https://www.php.net/manual/zh/pharfileinfo.setmetadata.php) — 设置与文件一起保存的文件特定元数据 - [PharException](https://www.php.net/manual/zh/class.pharexception.php) — PharException 类
发表于 2022-10-17 10:03:52
阅读 ( 6041 )
分类:
WEB安全
1 推荐
收藏
0 条评论
请先
登录
后评论
markin
10 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!