文章目录
- 参考
- 环境
- 题目
- index.php
- highlight_file()
- include()
- 多次调用,多次执行
- 单次调用,单次执行
 
- $_POST
- 超全局变量
- HackBar
- HackBar 插件的获取
 
- $_POST
- 打开 HackBar 插件
- 通过 HackBar 插件发起 POST 请求
 
 
- GET 请求
- 查询字符串
- 超全局变量 $_GET
 
- JSON
- JSON 数据格式
- JSON 基本结构
 
- json_decode()
 
- 弱比较
- 隐式类型转换
- 字符串连接
- 数学运算
- 布尔判断
 
- 相等运算符
 
 
 
- 进攻
- 整体分析
- 构造数据
- 攻击
- 为什么 id 不能等于 0
 
 
参考
| 项目 | 描述 | 
|---|---|
| 搜索引擎 | Bing、Google | 
| AI 大模型 | 文心一言、通义千问、讯飞星火认知大模型、ChatGPT | 
| PHP 手册 | PHP Manual | 
环境
| 项目 | 描述 | 
|---|---|
| PHP | 5.5.0、5.6.8、7.0.0、7.2.5、7.4.9、8.0.0、8.2.9 | 
题目
| 项目 | 描述 | 
|---|---|
| 得分项 | HTTP 请求方法、JSON 数据格式、发起 POST 请求 | 
| 题目来源 | NSSCTF | 
index.php
访问题目首页 index.php,你将得到如下 PHP 代码。
<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>
接下来,让我们对 index.php 中的代码逐一进行讲解。
highlight_file()
highlight_file() 函数是 PHP 提供的一个用于 PHP 代码高亮显示 的函数,使用该函数将能够获取到 指定文件中的内容 通过 HTML 标签 实现高亮显示的结果。
highlight_file(string $filename, bool $return = false): string|bool
其中:
| 项目 | 描述 | 
|---|---|
| $filename | 欲高亮显示的文件的 路径。 | 
| $return | 若该参数的值为 true,则highlight_file()函数将返回文件高亮处理后的HTML 文本,而不是将其直接输出。 | 
举个栗子
<?php// 按照一定规则通过 HTML 标签高亮显示
// 指定 PHP 文件中的内容。
highlight_file(__FILE__);// 通过在网页中嵌入 HTML 标签 </br>
// 实现换行效果。
echo '</br>';var_dump('Hello World');
注:
在 PHP 中,__FILE__ 是一个包含当前 正在执行的 PHP 文件 的 字符串形式的完整路径 的内置全局常量。
执行效果
执行 highlight_file(__FILE__); 后,PHP 会自动将全局变量 __FILE__ 所指向的文件中的内容 高亮处理后得到的 HTML 文本进行输出。尝试通过浏览器访问 __FILE__ 所指向的文件将得到类似如下效果:

由 highlight_file() 高亮处理目标文件后得到的 HTML 文本如下:
<code><span style="color: #000000">
<br /></span><span style="color: #0000BB">var_dump</span><span style="color: #007700">(</span><span style="color: #DD0000">'Hello World'</span><span style="color: #007700">);</span>
</span>
</code>
注:
highlight_file() 函数 并不是仅能够 对 PHP 代码进行高亮处理,实际上,highlight_file() 函数将通过 PHP 内置的语法高亮器 按照一定的高亮规则对文件中 <?php 后与?>(可被省略) 前的内容进行高亮处理。对此,请参考如下示例:
<?php// 按照一定规则通过 HTML 标签高亮显示
// 指定 PHP 文件中的内容。
highlight_file('./calc.js');// 通过在网页中嵌入 HTML 标签 </br>
// 实现换行效果。
echo '</br>';var_dump('Hello World');
calc.js 文件中的内容
<?php
// 定义两个数字
var num1 = 5;
var num2 = 10;// 计算两个数字的和
var sum = num1 + num2;// 显示计算结果
console.log("两个数字的和为:" + sum);莫愁前路无知己,天下谁人不识君。?>foreach(str_split('Hello World') as $char){print($char . "\n");
}
执行效果

include()
在 PHP 中,include 函数常用于将一个文件的内容包含到另一个 PHP 文件中,以便在多个文件中共享相同的代码,提高代码的可维护性和重用性。与 include 函数起着相同功能还有 require 函数。
举个栗子
<?php# 包含文件 another.php
include('another.php');# 被 include 包含的内容可以理解为
# 与相关的 include 语句发生了跨文件的覆盖。# 实例化 MyClass 类对象
$myClass = new MyClass();# 尝试获取 myClass 对象的属性
print($myClass -> name . "\n");
print($myClass -> nation . "\n");
# 尝试调用 myClass 对象的方法
$myClass -> sayHello();
上述 PHP 代码尝试通过调用 include 函数来包含文件 another.php,another.php 文件的具体内容如下:
<?php# 定义一个名为 MyClass 的类
class MyClass
{public $name = 'RedHeart';public $nation = 'China';public function sayHello(){print('Hello World' . "\n");}
}
执行效果
RedHeart
China
Hello World多次调用,多次执行
每当你通过调用 include 或 require 函数来包含其他文件中的内容时,被包含文件中的内容都将 立即被 PHP 解释器执行,当你对某一文件进行 多次包含操作 时,该文件的内容 将被执行多次。对此,请参考如下示例:
<?phpinclude('./another.php');
include('./another.php');
include('./another.php');require('./another.php');
require('./another.php');
require('./another.php');
another.php 文件的具体内容如下:
<?phpprint('Hello World' . "\n");
执行效果
Hello World
Hello World
Hello World
Hello World
Hello World
Hello World单次调用,单次执行
include 与 require 函数多次调用多次执行的特性可能导致 数据被污染、程序性能下降等问题,在大型项目中尤其如此。使用 require_once 与 include_once 函数代替 require 与 include 函数执行文件包含操作将能够拒绝隐患的发生。对此,请参考如下示例:
<?php# ./another.php 文件中的内容保持不变
include('./another.php');
require_once('./another.php');
include_once('./another.php');
执行效果
无论使用 include、require、include_once 及 require_once 中的哪一个函数包含文件,再次使用 include_once 或 require_once 包含 相同文件 都将导致文件 包含失败,或者说文件 执行失败。
Hello World$_POST
超全局变量
在 PHP 中,超全局变量(Superglobals) 是一类特殊的变量,这些变量在脚本的任何地方都可以访问,而无需使用 global 关键字或其他特殊的语法。超全局变量在 PHP 中起着重要的作用,用于存储和传递与请求、会话、服务器配置等相关的信息。
PHP 中常用到的超全局变量:
| 项目 | 描述 | 
|---|---|
| $_POST | $_POST是 PHP 中用于处理从客户端通过POST 方法提交的数据的超全局变量,该变量允许你访问通过 HTTP POST 请求传递到服务器的数据。 | 
| $_GET | $_GET用于获取客户端通过 URL 的查询字符串传递的参数。 | 
| $_COOKIE | $_COOKIE用于获取客户端发送的Cookie数据。 | 
| $_SESSION | $_SESSION用于在服务器端存储和访问会话数据,该变量允许你在不同页面之间共享和保持用户会话数据。 | 
| $_SERVER | $_SERVER包含有关服务器和当前请求的相关信息(例如请求的 URL、请求方法、服务器的 IP 地址)。 | 
注:
大多数 的超全局变量都存储着一个 关联数组(上述列举的超全局变量均是如此),其中的每个元素对应一个与超全局变量功能相关的信息。
HackBar
Hackbar 是一个 浏览器扩展,该扩展工具为安全专家、渗透测试人员和开发人员提供了一组工具,用于测试和评估 Web 应用程序的安全性。HackBar 可以帮助用户识别潜在的安全漏洞,进行渗透测试,以及执行各种与网络安全相关的任务。以下是 Hackbar 的一些 主要 特点和功能:
-  HTTP 请求和响应拦截 
 HackBar 允许用户查看编辑浏览器发送的 HTTP 请求报文及接收到的 HTTP 响应报文。在需要的时候,你可以通过修改 HTTP 请求报文来实现对请求的精细化控制。
-  Cookie 编辑和管理 
 HackBar 允许用户轻松编辑和管理浏览器中的 Cookie,这对于模拟不同的用户会话非常有用。
-  表单处理 
 HackBar 提供了表单提交和数据包装功能,以帮助用户测试应用程序的输入验证和表单处理。你可以手动构建 POST 或 GET 请求,并查看服务器对此进行的响应。
-  编码和加密工具 
 HackBar 包括一些编码和加密工具,可用于处理Base64、MD5、SHA1等的相关数据。
-  XSS 攻击测试 
 HackBar 提供了一些工具,用于检测和测试跨站脚本(XSS)漏洞。
-  SQL 注入测试 
 HackBar 还包括用于测试 SQL 注入漏洞的工具,可以帮助用户评估 Web 应用程序的数据库安全性。
由于微软的 Edge 浏览器的插件官网没有提供 HackBar 这款扩展工具,而 Google 的 Chrome 浏览器的插件官网由需要科学上网,所以推荐需要这款插件的朋友可以使用 Firefox 浏览器。
HackBar 插件的获取
首先,打开 Firefox 浏览器并进入其 插件官网,搜索插件 HackBar,你将得到如下类似界面。

选择图中 红色箭头指向的两个插件中的其中一个 进行安装即可。
$_POST
<?php# 超全局变量 $_POST 保持着一个关联数组
var_dump($_POST);# 输出一个具有换行功能的 HTML 标签
echo '</br>';# 获取名为 X 的 POST 参数的值
var_dump($_POST['X']);
访问上述 PHP 页面,你将得到如下内容:

PHP 解释器抛出了 Notice 异常信息,这是由于我们还未提交 POST 数据,导致 $_POST['X'] 正在 尝试访问一个不存在的元素 从而引发了异常。
打开 HackBar 插件
右键 当前页面,得到如下类似界面:

点击其中的 检查,得到如下类似界面:

选择其中的 HackBar 分栏,得到如下类似界面:

通过 HackBar 插件发起 POST 请求
点击 LOAD 按钮 加载与当前页面相关的 URL 至输入框中,当然你也可以自己输入 URL,URL 所指向的页面是插件将处理的页面,不一定需要是当前页面的 URL。
点击 Use POST method 旁边的按钮尝试进行 POST 请求,enctype 下拉框中提供了 可供选择的 POST 请求数据的编码方式。

默认选项 application/x-www-form-urlencode 允许我们使用 同 URL 查询字符串相同的格式编写 POST 数据。于是构造如下 POST 数据:
name=RedHeart&X=BinaryMoon&special_symbol=%26%25%24
点击 EXECUTE 发起请求,得到如下类似界面:

GET 请求
查询字符串
在 Web 开发中,查询字符串(Query String) 是指 URL 中位于 问号 ? 后的 部分 文本。查询字符串常用于客户端浏览器 通过 URL 传递数据给服务器。
 查询字符串由 一个或多个参数 组成,每个参数之间使用 & 符号 进行 分隔。
举个栗子
https://example.com/index.php?ID=1&UserName=RedHeart#BinaryMoon
在上述 URL 中,查询字符串为 ID=1&UserName=RedHeart,其中包含两个参数,即 ID=1 和 UserName=RedHeart。这意味着,参数名为 ID 的参数对应的参数值为 1,而参数名为 UserName 参数对应的参数值为 RedHeart。
超全局变量 $_GET
在 PHP 中,可以使用超全局变量 $_GET 来获取由浏览器客户端提交的查询字符串中的 参数信息。$_GET 是一个 关联数组,其中的 键 为查询字符串中的参数名,值 为参数对应的值。
index.php 页面所包含的内容
<?php// 获取由查询字符串中的参数组成的关联数组
var_dump($_GET);// 通过向网页中输入一个 </br> HTML 标签来实现换行的效果
echo '</br>';// 获取查询字符串中参数名为 Name 的参数所对应的参数值
var_dump($_GET['Name']);
执行效果
在客户端浏览器访问 index.php 页面后,服务器端的输出结果。
array(2) { ["Name"]=> string(8) "RedHeart" ["Host"]=> string(10) "BinaryMoon" }
string(8) "RedHeart"
JSON
JSON 数据格式
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它以易于阅读和编写的文本形式表示数据。JSON 最初是为JavaScript开发 的,但已成为一种 通用的数据格式,常用于数据存储与交换。
JSON 数据格式的特点
| 优势 | 具体描述 | 
|---|---|
| 键值对结构 | JSON数据是以键值对的形式表示的,键值对的结构使得 JSON 非常适合表示结构化数据。 | 
| 跨语言兼容性 | JSON 是一种 独立于编程语言的数据格式,因此可以在不同编程语言之间轻松传输和解析数据,几乎所有现代编程语言都支持 JSON 的编码和解码。 | 
| 支持的数据类型 | JSON 支持多种数据类型,包括 字符串、数字、布尔值、数组、null以及JSON。 | 
| 不支持注释 | JSON 主要用于对数据的描述,故 JSON 不支持注释,这意味着你 不能在 JSON 数据中添加注释以解释数据的含义。 | 
| 无循环引用 | JSON 不支持循环引用,也就是说, JSON 中的键值对不能引用包含它的对象,这有助于避免数据结构的无限递归。 | 
| 支持嵌套 | JSON 内部可以嵌套 JSON数据。 | 
JSON 的典型用途包括 配置文件、API 数据交换、日志记录 和各种应用程序中的数据传输和存储。JSON 的 简洁性 和 通用性 使它成为一种流行的数据交换格式,广泛应用于Web开发、移动应用程序和云计算等领域。
JSON 基本结构
JSON 数据是由一组 键值对 组成的,其中键是字符串,值可以是 字符串、数值、布尔值、数组、JSON 或 null。
 JSON 对象使用花括号 {} 包围,键与值之间使用冒号 :  分隔,键值对之间使用逗号 , 分隔。
 JSON 数组使用方括号 [] 包围,数组元素之间使用逗号 , 分隔。
举个栗子
{"name": "John","age": 30,"hometown": "New York","pets": [{"name": "Fido","species": "dog"},{"name": "Fluffy","species": "cat"}],"car": null
}
注:
- JSON 数据中,键必须为字符串,而值可以是字符串、数值、布尔值、数组、JSON或null。
- JSON 格式中,字符串 必须使用双引号而不能使用单引号。
json_decode()
json_decode() 是 PHP 中的一个 内置函数,用于将 JSON 解析为 PHP 数据。
 json_decode() 中存在参数 $associative,默认值为 false。参数 $associative 用于指定是否将 JSON 解析为 关联数组,若该参数的值为 true,则 json_decode() 函数将返回一个关联数组;否则,返回一个 PHP 对象。对此,请参考如下示例:
<?php# 定义 JSON 数据
$json_content = '{"name": "John","age": 30,"hometown": "New York","pets": [{"name": "Fido","species": "dog"},{"name": "Fluffy","species": "cat"}],"car": null
}';print('【将 JSON 数据解析为对象进行返回】' . "\n");
$result_1 = json_decode($json_content);
var_dump($result_1);print("\n" . '【将 JSON 数据解析为关联数组进行返回' . "\n");
$result_2 = json_Decode($json_content, true);
var_dump($result_2);
执行效果
【将 JSON 数据解析为对象进行返回】
object(stdClass)#1 (5) {["name"]=>string(4) "John"["age"]=>int(30)["hometown"]=>string(8) "New York"["pets"]=>array(2) {[0]=>object(stdClass)#2 (2) {["name"]=>string(4) "Fido"["species"]=>string(3) "dog"}[1]=>object(stdClass)#3 (2) {["name"]=>string(6) "Fluffy"["species"]=>string(3) "cat"}}["car"]=>NULL
}【将 JSON 数据解析为关联数组进行返回
array(5) {["name"]=>string(4) "John"["age"]=>int(30)["hometown"]=>string(8) "New York"["pets"]=>array(2) {[0]=>array(2) {["name"]=>string(4) "Fido"["species"]=>string(3) "dog"}[1]=>array(2) {["name"]=>string(6) "Fluffy"["species"]=>string(3) "cat"}}["car"]=>NULL
}
弱比较
隐式类型转换
在 PHP 中,隐式类型转换(Implicit Type Conversion) 是指在某些操作中,PHP 会 自动 将数据 由一种数据类型转换为另一个数据类型,而 无需显式 地编写 类型转换 代码。
PHP 的隐式类型转换会按照一定规则(具体情况具体分析)对操作数进行转换,以使得相关操作 能够正常进行 下去。
字符串连接
在通过使用句点运算符 . 进行字符串连接操作时,PHP 将会尝试将其他数据类型 转换为字符串数据类型。对此,请参考如下示例:
<?php// 尝试将两个字符串进行拼接
var_dump('Hello ' . 'World');// 尝试将数值与字符串进行拼接
var_dump('1 + 1 = ' . 2);// 尝试将两个数值进行拼接
var_dump(1 . 1);
执行效果
string(11) "Hello World"
string(9) "1 + 1 = 2"
string(2) "11"
数学运算
在通过 数学运算符 进行数学运算时,PHP 将会尝试将其他数据类型的数据 转换为数值类型。对此,请参考如下示例:
<?php// 尝试对布尔值 true 与数值 1 进行减法运算
var_dump(true - 1);// 尝试对布尔值 true 与 false 进行加法运算
var_dump(true + false);// 尝试进行字符串之间的乘法运算
var_dump('2' * '150');// 字符串 100djdj 将被转换为 100
var_dump('100djdj' / 10);// 字符串 djdj100 将被转换为零
var_dump('djdj100' / 10);
执行效果
int(0)
int(1)
int(300)
int(10)
int(0)
布尔判断
在需要使用布尔值的位置,PHP 将尝试将非布尔值的数据 转换为布尔类型的数据。对此,请参考如下示例:
<?php// 尝试将空字符串转换为布尔值
if(''){print('Hello World' . "\n");
}// 尝试将字符串 Hello World 转换为布尔值
if('Hello World'){print('Hello China' . "\n");
}// 尝试将数值 999 转换为布尔值
if(999){print('久久久' . "\n");
}
执行效果
Hello China
久久久
相等运算符
在 PHP 中存在两种相等运算符,即弱类型相等运算符 == 和强类型相等运算符 ===,两者都可以用于判断两个操作数是否相等,但存在一些区别。
两者的 区别 在于,弱类型相等运算符 在对操作数进行比较之前,将 自动 进行类型转换以 使两者所属的数据类型相同。而 强类型相等运算符 在进行比较时,要求两个值的 类型 和 值 都必须 完全相同,不进行类型转换。对此,请参考如下示例:
<?php// 在通过弱类型比较运算符对数值与字符串进
// 行比较时,PHP 优先将字符串转换为数值。// 由于两者转换为同一类型后,值相同,
// 故将返回 true。
var_dump('123' == 123);// 由于两者的数据类型及值均不相同,故
// 将返沪 false。
var_dump('123' === 123);
执行效果
bool(true)
bool(false)
进攻
整体分析
让我们对 index.php 的整体进行分析:
<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>
首先,该页面通过 highlight_file() 函数将当前页面的内容以高亮的形式显示出来,这使得在访问 index.php 页面时能够看到该页面的 PHP 代码而 不是一片空白(访问 PHP 页面通常能够看到的是服务器端响应的内容而不是该页面的源代码)。
 其次,该页面通过调用 include() 函数将 flag.php 页面中的内容包含到当前页面。正常情况下,包含 flag 的名称进行命名的文件通常包含了我们所需要的 flag。在该页面代码下方的判断语句中出现了 未在当前页面定义的变量 $flag,所以我们的目标也变明确了,即 绕过判断语句使服务器返回 $flag 变量中的值。
 最后,该页面通过获取 GET 与 POST 方式提交的数据来 得到两个比较对象。我们的任务是 构造合适的 GET 与 POST 数据来使得判断语句成立以执行其中的代码。
构造数据
根据判断语句,客户端提交的数据需要满足 $id=="wllmNB" 及 $json['x']=="wllm"。故构造查询字符串:
?json={"x": "wllm"}
构造 POST 数据:
id=wllmNB
攻击
将构造的数据准备好后向服务器发起请求,得到如下界面:

至此,得到 flag 。
为什么 id 不能等于 0
了解 PHP 弱比较的同学可能会想着通过 POST 提交数据 id=0 来使得判断语句成立。这是由于在 PHP 中,倘若 数值与字符串进行弱比较,则 PHP 会优先将字符串转化为数值后再进行比较。而 wllmNB 转化为数值的结果为零,与 $id 的比较结果将为 true。
 这样思考有一定的依据,但 忽略了一个事实,即通过 POST 与 GET 向服务器提交的数据都将以 字符串的方式 存储在 $_GET 与 $_POST 中。你以为提交 POST 数据 id=1 后,$id 中存储的数据将为 数值 1 ,而实际上 $id 中存储的数据为 字符串 1,因此也就不存在数值与字符串的弱比较了,故 id 不能等于 0 。对此,请参考如下示例:
<?phpvar_dump($_POST['X']);echo '</br>';var_dump($_GET['X']);
执行效果
