加固-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:
+24
-4
@@ -462,6 +462,10 @@ switch ($_GET['mod']){
|
|||||||
if (empty($_POST['mod'])){
|
if (empty($_POST['mod'])){
|
||||||
die('模块标识不能为空');
|
die('模块标识不能为空');
|
||||||
}
|
}
|
||||||
|
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
|
||||||
|
if (!wq_is_db_field($_POST['mod'])){
|
||||||
|
die('不支持修改的字段');
|
||||||
|
}
|
||||||
if (empty($_POST['gid'])){
|
if (empty($_POST['gid'])){
|
||||||
die('等级ID不能为空');
|
die('等级ID不能为空');
|
||||||
}
|
}
|
||||||
@@ -476,6 +480,10 @@ switch ($_GET['mod']){
|
|||||||
if (empty($_POST['mod'])){
|
if (empty($_POST['mod'])){
|
||||||
die('模块标识不能为空');
|
die('模块标识不能为空');
|
||||||
}
|
}
|
||||||
|
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
|
||||||
|
if (!wq_is_db_field($_POST['mod'])){
|
||||||
|
die('不支持修改的字段');
|
||||||
|
}
|
||||||
if (empty($_POST['userid'])){
|
if (empty($_POST['userid'])){
|
||||||
die('用户ID不能为空');
|
die('用户ID不能为空');
|
||||||
}
|
}
|
||||||
@@ -561,6 +569,10 @@ switch ($_GET['mod']){
|
|||||||
if (empty($_POST['mod'])){
|
if (empty($_POST['mod'])){
|
||||||
die('模块标识不能为空');
|
die('模块标识不能为空');
|
||||||
}
|
}
|
||||||
|
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
|
||||||
|
if (!wq_is_db_field($_POST['mod'])){
|
||||||
|
die('不支持修改的字段');
|
||||||
|
}
|
||||||
if (empty($_POST['fid'])){
|
if (empty($_POST['fid'])){
|
||||||
die('分类ID不能为空');
|
die('分类ID不能为空');
|
||||||
}
|
}
|
||||||
@@ -700,6 +712,10 @@ switch ($_GET['mod']){
|
|||||||
if (empty($_POST['mod'])){
|
if (empty($_POST['mod'])){
|
||||||
die('模块标识不能为空');
|
die('模块标识不能为空');
|
||||||
}
|
}
|
||||||
|
// P0-06: mod 作为动态列名入 SQL,先校验合法标识符,防列名注入
|
||||||
|
if (!wq_is_db_field($_POST['mod'])){
|
||||||
|
die('不支持修改的字段');
|
||||||
|
}
|
||||||
if (empty($_POST['aid'])){
|
if (empty($_POST['aid'])){
|
||||||
die('代理ID不能为空');
|
die('代理ID不能为空');
|
||||||
}
|
}
|
||||||
@@ -807,8 +823,9 @@ switch ($_GET['mod']){
|
|||||||
}
|
}
|
||||||
$time = time();
|
$time = time();
|
||||||
$money = $_POST['sc_money'];
|
$money = $_POST['sc_money'];
|
||||||
if ($money <= 0){
|
// P0-06: money 会裸拼进 INSERT SQL 与卡密字符串,强制数字
|
||||||
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
|
if (!is_numeric($money) || $money <= 0){
|
||||||
|
die(json_encode(array('code'=>'-1','msg'=>'卡密金额格式错误')));
|
||||||
}
|
}
|
||||||
$keylist='';
|
$keylist='';
|
||||||
for ($x=1; $x<=$_POST['sc_count']; $x++) {
|
for ($x=1; $x<=$_POST['sc_count']; $x++) {
|
||||||
@@ -1471,9 +1488,11 @@ switch ($_GET['mod']){
|
|||||||
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
|
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
|
||||||
}
|
}
|
||||||
$keylist='';
|
$keylist='';
|
||||||
|
// P0-06: sc_fid 裸拼进 INSERT SQL,强制整型
|
||||||
|
$scfid = (int)$_POST['sc_fid'];
|
||||||
for ($x=1; $x<=$_POST['sc_count']; $x++) {
|
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);
|
$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;
|
$keylist .= '<br>'.$key;
|
||||||
}
|
}
|
||||||
$sign = rand_str(32);
|
$sign = rand_str(32);
|
||||||
@@ -1552,7 +1571,8 @@ switch ($_GET['mod']){
|
|||||||
die('成功删除'.(int)$db->affected_num().'行');
|
die('成功删除'.(int)$db->affected_num().'行');
|
||||||
break;
|
break;
|
||||||
case 'DelUse':
|
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().'行');
|
die('成功删除'.(int)$db->affected_num().'行');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-4
@@ -1044,9 +1044,11 @@ switch ($_GET['mod']) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$keylist='';
|
$keylist='';
|
||||||
|
// P0-06: sc_fid 裸拼进 INSERT SQL,强制整型(上方 fidinfo 校验保证 fid 有效)
|
||||||
|
$scfid = (int)$_POST['sc_fid'];
|
||||||
for ($x=1; $x<=$_POST['sc_count']; $x++) {
|
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);
|
$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;
|
$keylist .= '<br>'.$key;
|
||||||
}
|
}
|
||||||
$sign = rand_str(32);
|
$sign = rand_str(32);
|
||||||
@@ -1126,8 +1128,9 @@ switch ($_GET['mod']) {
|
|||||||
}
|
}
|
||||||
$time = time();
|
$time = time();
|
||||||
$money = $_POST['sc_money'];
|
$money = $_POST['sc_money'];
|
||||||
if ($money <= 0){
|
// P0-06: money 会裸拼进 INSERT SQL 与卡密字符串,强制数字
|
||||||
die(json_encode(array('code'=>'-1','msg'=>'卡密金额不能为空或者0或者负数')));
|
if (!is_numeric($money) || $money <= 0){
|
||||||
|
die(json_encode(array('code'=>'-1','msg'=>'卡密金额格式错误')));
|
||||||
}
|
}
|
||||||
$spendmoney = $money * $count;
|
$spendmoney = $money * $count;
|
||||||
if (!$agentinfo = $db->select_first_row('sq_agent', '*', array('username' => $_SESSION['agent_username']), 'AND')) {
|
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().'行');
|
die('成功删除'.(int)$db->affected_num().'行');
|
||||||
break;
|
break;
|
||||||
case 'DelUse':
|
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().'行');
|
die('成功删除'.(int)$db->affected_num().'行');
|
||||||
break;
|
break;
|
||||||
case 'DelAll':
|
case 'DelAll':
|
||||||
|
|||||||
@@ -251,6 +251,15 @@ function wq_verify_password($plain, $stored, $legacy = 'plain')
|
|||||||
return hash_equals($stored, $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
|
* 布尔逻辑型转换为数字0或1
|
||||||
|
|||||||
Reference in New Issue
Block a user