Web学习笔记 之 文件与序列化

文件与序列化

  这一章的标题是文件与序列化,因为反序列化的利用总是要用到unserialize()这个函数。而在实际场景中这个函数也许并不常见,但是如果有文件上传点的话,兴许是可以利用phar伪协议的。所以我想连着文件上传、文件包含一起整理下。

序列化&反序列化

  在开发过程中,如果遇到需要将数据存储,并且要保持他原来的类型就需要用到序列化。比如将一个对象序列化,顾名思义就是将这个对象以序列化的形式存储,方便下次使用的时候恢复。举个栗子就是,你想将变量$a = 1存起来,下次好直接用。如果你只是把他以字符串的形式存进了文件:“$a = 1”,那么下次读取文件的时候你也只能读到一串普通的字符串,也不是一个被赋值了的变量。

序列化、反序列化用到的函数就是serialize和unserialize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
$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类型。

1
2
3
4
5
6
<?php
$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反序列化链

魔术方法

1
2
3
4
5
6
7
8
9
10
11
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发

__toString()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php   

class TestClass
{
public function __toString()
{
return '__toString<br />';
}
}
// 创建一个对象
$object = new TestClass();

// 对象被当作一个字符串
// __toString会被调用
echo $object;
?>

输出效果如下:
__toString

__invoke()

1
2
3
4
<?php 
?>")}
r1 = r.post(url, files=file1)
print(r1.text)

但如果服务端没有对上传的文件进行过滤的话,或者过滤的不够完善,则容易被攻击者趁虚而入。下面就讲一下文件后缀的绕过

客户端绕过

  浏览器禁用JavaScript;或者抓包修改后缀就可以了。

服务器端MIME绕过

同样抓包修改数据包的content-type字段就可。

常见的图片格式的MIME类型有以下 几种类型:

PNG图像:image/png

GIF图形: image/gif

JPG图形:image/jpeg

服务器端扩展名检测绕过

黑名单

  1. 文件名大小写绕过:pHp,AsP

  2. 特殊文件名绕过

      在Windows下有一个特性就是如果文件后缀以点‘.’或者空格‘ ’结尾的后缀 名时,系统在保存文件时会自动去除点和空格。但要注意 Unix/Linux 系统没有 这个特性。因为有些服务器端的后缀名检测是取文件名最后一个.后面的字符串,拿这个字符串与黑名单列表对比

  3. 0x00截断绕过

      名后缀有一个%00字节,可以截断某些函数对文件名的判断。在许多语言函 数中,处理字符串的函数中0x00被认为是终止符。

      例如: 网站上传函数处理xxx.asp%00.jpg时,首先后缀名是合法的jpg格式,可以 上传,在保存文件时,遇到%00字符丢弃后面的 .jpg,文件后缀最终保存的后缀 名为xxx.asp

白名单

  1. 截断绕过

      用像test.asp%00.jpg的方式进行截断,属于白名单文件

      【但是防御的话,就是检测的是啥后缀,就以啥后缀来保存,文件名再进行变化,比如用时间戳来替换,而非以原始文件名直接保存。】

  2. 接下来将要讨论的 解析/包含 漏洞绕过

解析漏洞

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

1
2
3
4
# 将文件名含有"dd"的解析成php文件
<FilesMatch "dd">
SetHandler application/x-httpd-php
</FilesMatch>

方法2

1
2
#将文件后缀为.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文件伪装成其他格式的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
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,也可以邮件至 643713081@qq.com

文章标题:Web学习笔记 之 文件与序列化

文章字数:2.9k

本文作者:Van1sh

发布时间:2020-08-09, 14:16:00

最后更新:2020-12-17, 13:21:49

原始链接:http://jayxv.github.io/2020/08/09/Web学习笔记之文件与序列化/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏