加固-07(P0-06): 封堵 SQL 注入面(动态列名白名单 + 裸拼值强类型化)

背景:多处 SQL 由用户输入拼接——update() 的动态键(mod)直接作列名入 SET 子句;
卡密/分类卡密批量 INSERT 手拼 SQL 值元组(insert_back_row 零转义);DelUse 删除
语句手拼 fid。列名注入与手拼值注入均可被利用篡改/破坏数据。

1. 列名注入(动态 mod 键):
   - function/function_core.php 新增 wq_is_db_field($name):仅允许 [A-Za-z0-9_]{1,64}
     合法 SQL 标识符,杜绝引号/空格/注释符进入列名
   - admin/ajax.php 四处 change* 动态更新加守卫(非法 mod 直接 die「不支持修改的字段」):
     * changegrade (sq_level)
     * changeuser  (sq_user)
     * changefid   (sq_fidlist)
     * changeagent (sq_agent)
   - 注:agent 侧 changeuser/changeagent 已有字段白名单(P0-05 已加 password 哈希),无需重复

2. 手拼 INSERT 值元组(creatkey / creatfidkey,admin+agent 双侧):
   - 卡密金额 sc_money:强制 is_numeric(),非法直接拒绝(money 会裸拼进 INSERT 与卡密串)
   - 分类 fid sc_fid:强制 (int) 整型化后再拼入(防多语句/注释符逃逸)

3. DelUse 删除语句(admin+agent 双侧):
   - fid 强制 (int) 整型化;agent 侧 aid 亦 (int) 化
   - 注入载荷如 "1 AND 1=1" 被截为 1,SQL 结构不再可被改写

回归(30 项全 PASS,8082 内置服务器):
- 各 change* 正常字段修改成功且 DB 生效;列名注入(price,x / 多语句 DROP 载荷)全部被拒,目标表完好
- creatkey 正常生成、余额正确扣除;money 注入载荷被 is_numeric 拒绝
- creatfidkey 合法 fid 正常生成;注入 fid 在 fid 查找即被拒(int 化后无命中)
- DelUse 注入 fid 被 int 化后正常执行(删 0 行),表结构完好
- 注:(int)"1; DROP..."=1(前导数字保留),(int)"'; DROP..."=0,载荷均无法逃逸 SQL 结构
This commit is contained in:
youpaishidifu
2026-10-10 12:41:33 +08:00
parent 24264cf320
commit aaf5fe48c6
3 changed files with 41 additions and 8 deletions
+24 -4
View File
@@ -462,6 +462,10 @@ switch ($_GET['mod']){
if (empty($_POST['mod'])){
die('模块标识不能为空');
}
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
if (!wq_is_db_field($_POST['mod'])){
die('不支持修改的字段');
}
if (empty($_POST['gid'])){
die('等级ID不能为空');
}
@@ -476,6 +480,10 @@ switch ($_GET['mod']){
if (empty($_POST['mod'])){
die('模块标识不能为空');
}
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
if (!wq_is_db_field($_POST['mod'])){
die('不支持修改的字段');
}
if (empty($_POST['userid'])){
die('用户ID不能为空');
}
@@ -561,6 +569,10 @@ switch ($_GET['mod']){
if (empty($_POST['mod'])){
die('模块标识不能为空');
}
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
if (!wq_is_db_field($_POST['mod'])){
die('不支持修改的字段');
}
if (empty($_POST['fid'])){
die('分类ID不能为空');
}
@@ -700,6 +712,10 @@ switch ($_GET['mod']){
if (empty($_POST['mod'])){
die('模块标识不能为空');
}
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
if (!wq_is_db_field($_POST['mod'])){
die('不支持修改的字段');
}
if (empty($_POST['aid'])){
die('代理ID不能为空');
}
@@ -807,8 +823,9 @@ switch ($_GET['mod']){
}
$time = time();
$money = $_POST['sc_money'];
if ($money <= 0){
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
// P0-06: money 会裸拼进 INSERT SQL 与卡密字符串,强制数字
if (!is_numeric($money) || $money <= 0){
die(json_encode(array('code'=>'-1','msg'=>'卡密金额格式错误')));
}
$keylist='';
for ($x=1; $x<=$_POST['sc_count']; $x++) {
@@ -1471,9 +1488,11 @@ switch ($_GET['mod']){
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
}
$keylist='';
// P0-06: sc_fid 裸拼进 INSERT SQL,强制整型
$scfid = (int)$_POST['sc_fid'];
for ($x=1; $x<=$_POST['sc_count']; $x++) {
$key = substr($time,0,6).'-'.rand_str(6).'-'.rand_str(6).'-'.rand_str(6).'-'.rand_str(6).rand_str(6);
$insarray[] = "'{$key}','{$time}','0','{$_POST['sc_fid']}','1'";
$insarray[] = "'{$key}','{$time}','0','$scfid','1'";
$keylist .= '<br>'.$key;
}
$sign = rand_str(32);
@@ -1552,7 +1571,8 @@ switch ($_GET['mod']){
die('成功删除'.(int)$db->affected_num().'行');
break;
case 'DelUse':
$db->delete('sq_fidkey', '`usetime` > 0 AND `fid` = '.$_POST['fid'].' AND `status` = 1','AND');
// P0-06: fid 裸拼进 DELETE SQL,强制整型
$db->delete('sq_fidkey', '`usetime` > 0 AND `fid` = '.(int)$_POST['fid'].' AND `status` = 1','AND');
die('成功删除'.(int)$db->affected_num().'行');
break;
}
+8 -4
View File
@@ -1044,9 +1044,11 @@ switch ($_GET['mod']) {
}
$keylist='';
// P0-06: sc_fid 裸拼进 INSERT SQL,强制整型(上方 fidinfo 校验保证 fid 有效)
$scfid = (int)$_POST['sc_fid'];
for ($x=1; $x<=$_POST['sc_count']; $x++) {
$key = substr($time,0,6).'-'.rand_str(6).'-'.rand_str(6).'-'.rand_str(6).'-'.rand_str(6).rand_str(6);
$insarray[] = "'{$key}','{$time}','0','{$_POST['sc_fid']}','1','{$_SESSION['agent_id']}'";
$insarray[] = "'{$key}','{$time}','0','$scfid','1','{$_SESSION['agent_id']}'";
$keylist .= '<br>'.$key;
}
$sign = rand_str(32);
@@ -1126,8 +1128,9 @@ switch ($_GET['mod']) {
}
$time = time();
$money = $_POST['sc_money'];
if ($money <= 0){
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
// P0-06: money 会裸拼进 INSERT SQL 与卡密字符串,强制数字
if (!is_numeric($money) || $money <= 0){
die(json_encode(array('code'=>'-1','msg'=>'卡密金额格式错误')));
}
$spendmoney = $money * $count;
if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $_SESSION['agent_username']), 'AND')) {
@@ -1182,7 +1185,8 @@ switch ($_GET['mod']) {
die('成功删除'.(int)$db->affected_num().'行');
break;
case 'DelUse':
$db->delete('sq_fidkey', '`usetime` > 0 AND `fid` = '.$_POST['fid'].' AND `aid` = '.$_SESSION['agent_id'].' AND `status` = 1','AND');
// P0-06: fid 裸拼进 DELETE SQL,强制整型
$db->delete('sq_fidkey', '`usetime` > 0 AND `fid` = '.(int)$_POST['fid'].' AND `aid` = '.(int)$_SESSION['agent_id'].' AND `status` = 1','AND');
die('成功删除'.(int)$db->affected_num().'行');
break;
case 'DelAll':
+9
View File
@@ -251,6 +251,15 @@ function wq_verify_password($plain, $stored, $legacy = 'plain')
return hash_equals($stored, $plain);
}
/**
* P0-06: 校验动态字段标识(mod)是否为合法 SQL 列名,防列名注入
* 动态 UPDATE/INSERT 的键来自用户输入时,须先通过本校验
*/
function wq_is_db_field($name)
{
return is_string($name) && preg_match('/^[A-Za-z0-9_]{1,64}$/', $name) === 1;
}
/*
* 布尔逻辑型转换为数字0或1