文章目录
- 参考
- 环境
- 题目
- 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 解释器执行
,当你对某一文件进行 多次包含操作
时,该文件的内容 将被执行多次
。对此,请参考如下示例:
<?php
include('./another.php');
include('./another.php');
include('./another.php');
require('./another.php');
require('./another.php');
require('./another.php');
another.php
文件的具体内容如下:
<?php
print('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
。对此,请参考如下示例:
<?php
var_dump($_POST['X']);
echo '</br>';
var_dump($_GET['X']);
执行效果