文件与序列化
这一章的标题是文件与序列化,因为反序列化的利用总是要用到unserialize()这个函数。而在实际场景中这个函数也许并不常见,但是如果有文件上传点的话,兴许是可以利用phar伪协议的。所以我想连着文件上传、文件包含一起整理下。
序列化&反序列化
在开发过程中,如果遇到需要将数据存储,并且要保持他原来的类型就需要用到序列化。比如将一个对象序列化,顾名思义就是将这个对象以序列化的形式存储,方便下次使用的时候恢复。举个栗子就是,你想将变量$a = 1存起来,下次好直接用。如果你只是把他以字符串的形式存进了文件:“$a = 1”,那么下次读取文件的时候你也只能读到一串普通的字符串,也不是一个被赋值了的变量。
序列化、反序列化用到的函数就是serialize和unserialize
1234567891011121314 |
$a = array('a' => 'Apple' ,'b' => 'banana' , 'c' => 'Coconut');//序列化数组$s = serialize($a);echo $s;//输出结果:a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}echo '<br /><br />';//反序列化$o = unserialize($s);print_r($o);//输出结果 Array ( [a] => Apple [b] => banana [c] => Coconut ) |
对于序列化的规则,比如a:3:{s:1:"a";s:5:"Apple";s:1:"b";s:6:"banana";s:1:"c";s:7:"Coconut";}
这条序列化中字符的具体含义:a就代表这是一个数组(array),3代表这个数组里有三个元素,用分号隔开;每个元素分为两个部分:key、value。这里的s代表这里的key一个字符串,1代表这个key的长度,“a”就是key的值。紧接着的s代表这里的value是一个字符串,5代表这个value的长度,“Apple”就是value的值。如果这个数组不是键值对的形式话,那么序列化串则会将元素的下标作为key,int类型。
123456 |
$a = array('a' , 'Apple', 'b' , 'banana', 'c' , 'Coconut');//序列化数组$s = serialize($a);echo $s;//输出结果:a:6:{i:0;s:1:"a";i:1;s:5:"Apple";i:2;s:1:"b";i:3;s:6:"banana";i:4;s:1:"c";i:5;s:7:"Coconut";} |
除了数组可以序列化,对象啥的也可以,O代表的是对象,对象中的属性的类型在序列化中也有会特殊的标识,但有些是不可打印字符,可以用base64编码来解决这个问题。
在CTF解题过程中,利用点一般是出现在反序列化上。比如,我们之前提到字符串的序列化串会有专门一个表示字符串长度的值,这个值决定着反序列化时读取的字符串的长度【试想如果这个值比实际的值大了,或者小了,会产生啥问题呢?】。然后就是在对象的序列化时,php中魔术方法的存在了。
推荐一下先知这篇文章:怎样挖掘出属于自己的php反序列化链
魔术方法
1234567891011 |
__wakeup() //使用unserialize时触发__sleep() //使用serialize时触发__destruct() //对象被销毁时触发__call() //在对象上下文中调用不可访问的方法时触发__callStatic() //在静态上下文中调用不可访问的方法时触发__get() //用于从不可访问的属性读取数据__set() //用于将数据写入不可访问的属性__isset() //在不可访问的属性上调用isset()或empty()触发__unset() //在不可访问的属性上使用unset()时触发__toString() //把类当作字符串使用时触发__invoke() //当脚本尝试将对象调用为函数时触发 |
__toString()
12345678910111213141516171819 |
class TestClass { public function __toString() { return '__toString<br />'; } } // 创建一个对象 $object = new TestClass(); // 对象被当作一个字符串 // __toString会被调用 echo $object; 输出效果如下:__toString |
__invoke()
1234 |
")}r1 = r.post(url, files=file1)print(r1.text) |
但如果服务端没有对上传的文件进行过滤的话,或者过滤的不够完善,则容易被攻击者趁虚而入。下面就讲一下文件后缀的绕过
客户端绕过
浏览器禁用JavaScript;或者抓包修改后缀就可以了。
服务器端MIME绕过
同样抓包修改数据包的content-type字段就可。
常见的图片格式的MIME类型有以下 几种类型:
PNG图像:image/png
GIF图形: image/gif
JPG图形:image/jpeg
服务器端扩展名检测绕过
黑名单
-
文件名大小写绕过:pHp,AsP
-
特殊文件名绕过
在Windows下有一个特性就是如果文件后缀以点‘.’或者空格‘ ’结尾的后缀 名时,系统在保存文件时会自动去除点和空格。但要注意 Unix/Linux 系统没有 这个特性。因为有些服务器端的后缀名检测是取文件名最后一个.后面的字符串,拿这个字符串与黑名单列表对比
-
0x00截断绕过
名后缀有一个%00字节,可以截断某些函数对文件名的判断。在许多语言函 数中,处理字符串的函数中0x00被认为是终止符。
例如: 网站上传函数处理xxx.asp%00.jpg时,首先后缀名是合法的jpg格式,可以 上传,在保存文件时,遇到%00字符丢弃后面的 .jpg,文件后缀最终保存的后缀 名为xxx.asp
白名单
-
截断绕过
用像test.asp%00.jpg的方式进行截断,属于白名单文件
【但是防御的话,就是检测的是啥后缀,就以啥后缀来保存,文件名再进行变化,比如用时间戳来替换,而非以原始文件名直接保存。】
-
接下来将要讨论的 解析/包含 漏洞绕过
解析漏洞
Apache解析漏洞
CVE-2017-15715,上传一个文件名包含换行符的文件。注意,只能是\x0A
,不能是\x0D\x0A
,我们可以用hex功能在1.php后面添加一个\x0A
。
另一个是上传1.php.xxx,后面xxx随便来个不能被解析的东西,随后apache就会往前,直到找到一个能被解析的后缀名,比如这里的php。
IIS解析漏洞
IIS6.0有两个解析漏洞,一个是如果目录名包含asp 、asa、cer字符串,那么这个目录下所有的文 件都会按照 asp 去解析。
例如: chaoasp/1.jpg
因为文件名中有asp字样,所以该文件夹下的1.jpg文件打开时,会按照asp文件去解析执行
另一个是只要文件名中含有.asp、.asa、.cer会优先按 asp 来解析
IIS7.0/7.5是对php解析时有一个类似于Nginx的解析漏洞, 对任意文件名只要在URL后面追加 上字符串“/任意文件名.php”就会按照 php 的方式去解析 。
例子 : ”http://www.baidu.com/upload/chao/1.jpg/chao.php”
这种情况下访问1.jpg,该文件就会按照php格式被解析执行
Nginx解析漏洞
一个是对任意文件名,在后面添加/任意文件名.php的解析漏洞,比如原本文件名是 test.jpg, 可以添加为 test.jpg/x.php 进行解析攻击。
一种是对低版本的 Nginx 可以在任意文件名后面添加%00.php
例如:127.0.0.1/sql-loads/load/chao.jpg%00.php
那么chao.jpg也就被当作php格式文件执行
nginx 0.5.* [Success]
nginx 0.6.* [Success]
nginx 0.7 <= 0.7.65 [Success]
nginx 0.8 <= 0.8.37 [Success]
文件包含
文件包含一般涉及四个函数
require()
require_once()
include()
include_once()
无论包含的文件是啥格式,txt还是jpg,被<?php ?>
标签 【短标签也可】包裹的内容都会被当成php代码执行。因此,如果文件上传不能绕过后缀的话,能找到文件包含漏洞也同样实现攻击。而如果想读被包含文件的源码的话,就要用到php伪协议,因为php代码【除非你 highlight_file(__FILE__);
了】是不可见的,需要用php的filter伪协议去base64编码一下——php://filter/read=convert.base64-encode/resource=$path”
但是如果有上传点,有过滤,然后又找不到文件包含,是否就无路了?倒也不尽然,那要看这个文件上传的点的过滤严不严格了,如果只是简简单单黑名单,那就,嘿嘿嘿~
.htaccess
.htaccess文件(或者”分布式配置文件”),全称是Hypertext Access(超文本入口)。提供了针对目录改变配置的方法, 即,在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。管理员可以通过Apache的AllowOverride指令来设置。
所以我们可以利用该文件来绕过文件上传时的过滤。前提是我们能上传该类型文件【如果能读写服务端的该文件也可】,具体方法就是上传包含如下内容的.htaccess文件
方法1
1234 |
# 将文件名含有"dd"的解析成php文件<FilesMatch "dd"> SetHandler application/x-httpd-php</FilesMatch> |
方法2
12 |
#将文件后缀为.png的解析成php文件AddType application/x-httpd-php .png |
.user.ini
自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件仅被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。即.user.ini可以修改配置,但是只有PHP_IN_USER模式的配置才可以在.user.ini中设定。
而可修改的配置中我们利用的比较多的就是auto_prepend_file
,
1
|
auto_prepend_file=01.gif
|
就是让所有php文件都“自动”包含 01.gif
这个文件,而这个文件可以是一个正常php文件,也可以是一个包含一句话的webshell。所以有一个要求就是.user.ini所在目录至少得有一个可访问的php文件。
除了上传这两种能够修改配置的文件,如果能够找到反序列化链,但是却苦于没有unserialize()函数来利用,那么phar伪协议你值得了解。
并且由于phar伪协议是靠文件内容中的标签来识别的,所以我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件。
1234567891011121314 |
class TestObject { } @unlink("phar.phar"); $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>"); //设置stub,增加gif文件头 $o = new TestObject(); $phar->setMetadata($o); //将自定义meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); |
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 [email protected] - source:Van1sh的小屋
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论