PHP 特性 - 伪协议总结
PHP 特性 - 伪协议
总结一下 PHP 常见的伪协议的利用条件, 手段, 常用方法;
php 伪协议
伪协议的通用特性
php 内置的伪协议是 stream wrappers, 它既不是网络协议, 也不是外部命令调用;
本质上来说, 它是直接使用这些系统调用来实现的:
1 | fopen() |
file:// 协议
通常用来读取本地文件,
1 | file_get_contents("file:///etc/passwd"); |
启用配置
file:// 协议即使在双 off 的情况下也能启用;
allow_url_fopen:off/onallow_url_include:off/on
php 源码
1 | php_stream_open_wrapper_ex() |
这里的
fopen()是 C 标准库函数,不是 PHP 用户函数。
file:// 伪协议在 PHP 源码层面靠调用 C 的 fopen() 实现,在更底层靠 Linux 的 open() 系统调用。要禁用 fopen() 可以禁用 file:// 协议, 不过通常来说不会这么做, 因为这一操作会影响大量其他函数的使用。
php:// 协议
启用配置
allow_url_fopen:off/onallow_url_include:php://input,php://stdin,php://memory,php://temp需要打开这个配置
php://filter 协议
php://filter 允许给任意流添加处理器 (filter), 一个常见的用法:
1 | php://filter/read=convert.base64-encode/resource=index.php |
协议有三个部分:
php://filter: 协议头;read=读模式, 对应的还有write=写模式; 其后为解释器;resource=读写操作的目标;
这个协议有读 (read) 和写 (write) 两个模式, 一般用读比较多, read 模式下, 通俗的理解就是读取, 并加工
php 源码
1 | php_stream_open_wrapper_ex("php://filter/...") |
常用手法
一般在渗透的时候用 php://filter 结合 include() 等函数来读取特定文件, 例如源码;
常用过滤器
只归纳了一些常用的, 来源: PHP 手册
| 过滤器 | 作用 |
|---|---|
convert.base64-encode; convert.base64-decode |
base64 编码 / 解码 |
string.rot13 |
rot13 变换, 对字母做向前/后顺位 13 位的变换 |
string.toupper; string.tolower |
大小写变换 |
convert.quoted-printable-encode; convert.quoted-printable-decode |
quoted-printable 字符串与 8-bit 字符串编码 / 解码 |
zlib.deflate; zlib.inflate |
gzip 压缩/解压, 注意这里的压缩和解压都只对文件流本身做读写 (raw), 不涉及正常 gzip 文件的文件头 (header) |
bzip2.compress; bzip2.decompress |
和上面同理, 不过是创建的 bz2 格式文件; |
mcrypt.*; mdecrypt.* |
libmcrypt 对称加 / 解密算法 |
zip:// |
php://filter 并不是文件访问协议,它只是对已有流进行加工。底层文件仍然由 file:// 的 fopen → open 系统调用完成。filter 本身不依赖 popen 或任何命令执行链。
php://input 协议
php://input 是一个只读原始请求体 (raw body) 流, 可以访问请求的原始数据的只读流, 在 POST 请求中访问 POST 的 data 部分。
协议特性
需要注意:
在 enctype=”multipart/form-data” 的时候
php://input是无效的。开启后, POST 内容不会解析为
$_POSTphp://input本质是一个虚拟流, 不对应任何真实文件。
php 源码
1 | php_stream_open_wrapper_ex("php://input") |
注意,
php://input并不会调用file wrapper, 因为它是虚拟流;
PHP 在解析 HTTP 请求时就将 request body 存入一个 buffer, 打开 php://input 时,PHP 只是在这个 buffer 上建立一个 stream 视图。
换句话说, php://input 指向的是 php 内部的一块缓冲区。
因此, 在访问 php://input 时, 不执行任何系统调用, 只在内存中移动指针。
常见利用
一个典型利用:
1 | http://127.0.0.1/include.php?file=php://input |
然后把代码执行内容放进 POST 正文就行了。
php://temp 协议
php://temp 是一个 内存 + 临时文件 的混合流。
在默认 2MB 的数据阈值内, 保存在内存中; 超过阈值后写入一个临时文件; 长话短说, 是可自动溢写到磁盘的虚拟文件。
php 源码
1 | php_stream_open_wrapper_ex("php://temp") |
阈值
max_memory默认是 2MB;
在不写入临时文件时 (
<= max_memory) 只发生emalloc()/erealloc()/memcpy(), 都是用户态内存管理, 不涉及系统调用;超出阈值时, 触发系统调用:
tmpfile(), 临时文件根据sys_get_temp_dir()的配置存放, 这里默认是/tmp:open("/tmp/..."): 创建匿名临时文件unlink("/tmp/..."): 立即删除目录项,但文件仍保持打开状态 (匿名)
可能的利用
通过写超量文件触发落盘, 然后通过路径泄露或者报错, 找到临时文件名, 触发代码进行 RCE;
zip / bzip2 / zlib:// 协议
用于读取 zip / bzip2 / gzip + zlib 文件内部的内容; 和 filter 协议中的过滤器的区别是, 这三个协议是只读不写的, 并且作用于完整的压缩文件而不仅是一段压缩流;
常用手法
可以访问压缩文件中的子文件, 并且不限制后缀名, 例如:
1 | http://127.0.0.1/include.php?file=zip:///tmp/phpinfo.jpg%23phpinfo.txt |
压缩 phpinfo.txt 为 phpinfo.zip, 并上传;
data:// 协议
使用 data:// 数据流封装器, 以在 url 中传递相应格式的数据。通常可以用来执行代码。
启用配置
allow_url_fopen:onallow_url_include:on
php 层面
它属于 data 流包装器, php_stream_wrapper 注册。 主要用在 RCE bypass;
或者在不能读写文件时, 绕过 open_basedir 的限制, 通过 data://, include 解析它。
常用手法
形如:
1 | data://text/plain;base64, |
其它
主要是 phar:// 协议, 会解析 phar 归档文件 (和 jar 差不多), 利用点在于特定的反序列化。
























































