以 AddNews.php 提交链路为例,剖析帝国CMS后台表单字段的 SQL 注入防护体系。
一、总体架构
帝国CMS在信息发布(增/改)流程中,采用分层过滤、因字段而异的防护策略:
浏览器表单
↓ POST
ecmsinfo.php ← 直接接收 $_POST,不做处理
↓
hinfofun.php::AddNews / EditNews
├── DoPostInfoVar($add) ← 标准字段(标题、属性、分类…)
└── ReturnAddF($add, ...) ← 模型自定义字段(newstext、smalltext 等)
最后 addslashes 入库
关键原则:入口页(ecmsinfo.php)只做路由,不负责过滤;所有过滤下沉到业务函数内部。
二、标准字段处理:DoPostInfoVar()
文件:e/class/hinfofun.php:103
function DoPostInfoVar($add){
global $class_r,$public_r;
$add['classid']=(int)$add['classid'];
// ...
}
2.1 数字字段 → (int) 强转
所有整型字段直接用 (int) 强制转换,SQL 注入代码在转换中灰飞烟灭:
| 字段 | 处理 |
|---|---|
classid, checked, istop |
(int)$add['classid'] |
isgood, groupid, newstempid |
(int)$add['isgood'] |
firsttitle, userfen, closepl |
(int)$add['userfen'] |
ttid, onclick, totaldown |
(int)$add['onclick'] |
newwfid, oldwfid, dokey |
(int) |
原理:PHP 的 (int) 遇到 "1; drop table" → 1,遇到纯字母 → 0,不可能注入。
2.2 字符串字段 → 分类多层过滤
// HTML转义 + URL &保留(纯文本场景)
$add['title'] = eDoRepPostComStr($add['title'], 1);
$add['keyboard'] = eDoRepPostComStr(RepPhpAspJspcodeText(...), 1);
$add['titleurl'] = eDoRepPostComStr(RepPhpAspJspcodeText(...), 1);
$add['infotags'] = eDoRepPostComStr(RepPhpAspJspcodeText(...), 1);
$add['titlepic'] = eDoRepPostComStr($add['titlepic'], 1);
// SQL检查 + addslashes(字符串内容)
$add['keyboard'] = hRepPostStr($add['keyboard'], 0);
$add['infotags'] = hRepPostStr($add['infotags'], 1);
// 文件名净化
$add['filename'] = RepFilenameQz($add['filename'], 1);
$add['newspath'] = RepFilenameQz($add['newspath'], 1);
$add['newspath'] = hRepPostStr($add['newspath'], 1);
$add['filename'] = hRepPostStr($add['filename'], 1);
各函数职责:
| 函数 | 作用 | 保留HTML |
|---|---|---|
eDoRepPostComStr |
ehtmlspecialchars + 可选 & 还原 |
❌ |
hRepPostStr |
ehtmlspecialchars(可选) + CkPostStrChar(SQL关键词拦截) + AddAddsData(addslashes) |
❌($ecms=1时转义) |
RepPhpAspJspcodeText |
删除 PHP/ASP/JSP 代码标签 | — |
RepFilenameQz |
文件名规范化,去路径遍历 | — |
2.3 调用链路图
eDoRepPostComStr($val)
├── CkPostStrIsArray($val) ← 数组直接报错
└── ehtmlspecialchars($val) ← HTML实体转义
hRepPostStr($val, $ecms)
├── CkPostStrIsArray($val)
├── [可选] ehtmlspecialchars($val)
├── CkPostStrChar($val) ← SQL关键词拦截
└── AddAddsData($val) ← addslashes
三、自定义字段处理:ReturnAddF()
文件:e/class/functions.php:7002
这才是真正的核心防护——模型里定义的所有自定义字段(编辑器内容、简介、下载地址、图片集等)都在这里处理。
3.1 每条字段值的过滤管线
// 遍历模型定义的所有字段
foreach (explode(',', $emod_r[$modid]['enter']) as $f) {
// 1. 清除PHP/ASP/JSP代码
$value = RepPhpAspJspcodeText($add[$f]);
// 2. 特殊字段类型处理
if ($f == 'newstime') $value = to_time($value);
if ($f == 'morepic') $value = ReturnMorepicpath(...);
if ($f == 'downpath') $value = ReturnDownpath(...);
if ($f == 'smalltext') $value = SubSmalltextVal(...);
if ($f == 'infoip') $value = egetip();
// 3. 全角/半角统一
$value = eDoInfoTbfToQj($value);
// 4. 字段类型校验(int/float/varchar...)
$value = hEModCkFieldType($value);
// 5. 自定义函数钩子
$value = DoFFun($value);
// 6. 模板变量处理
$value = RepTempvarPostStr($value);
// ★★★★★ 7. 核心:SQL注入过滤 ★★★★★
$value = hRepPostStr2($value);
// 8. 如果是 newstext(编辑器内容),额外处理:
if ($f == 'newstext') {
$value = addslashes(CopyImg( ← 远程图片下载 + addslashes
stripSlashes($value), ...
));
}
// ★★★★★ 8. 最终入库前再 addslashes ★★★★★
addslashes($value);
}
3.2 hRepPostStr2 做了什么
function hRepPostStr2($val, $phck=0){
$val = CkPostStrIsArray($val); // 防数组注入
if ($phck==1) CkPostStrCharYh($val); // 括号配对检查
CkPostStrChar($val); // SQL关键词拦截
$val = AddAddsData($val); // addslashes
FWClearGetText($val); // 防火墙
return $val;
}
与 RepPostStr 的区别:
RepPostStr |
hRepPostStr2 |
|
|---|---|---|
HTML转义 (ehtmlspecialchars) |
✅ 有 | ❌ 没有,保留HTML |
| SQL关键词拦截 | 可选 ($ecms=0) |
✅ 始终执行 |
addslashes |
可选 | ✅ 始终执行 |
| 防火墙 | 可选 | ✅ 始终执行 |
| 适用场景 | 纯文本、标题 | 富文本编辑器内容 |
这正是为什么 newstext 等编辑器字段能保留 <p> <img> 标签的同时,还能防 SQL 注入。
3.3 newstext 的特殊处理链路
原始POST值
→ RepPhpAspJspcodeText ← 清除 PHP/ASP/JSP
→ hEModCkFieldType ← 类型校验
→ hRepPostStr2 ← SQL过滤 + addslashes(保留HTML标签)
→ addslashes(CopyImg(...)) ← 远程图片下载 + 再addslashes
→ 最终拼入SQL
双重 addslashes:hRepPostStr2 内部一次 + 拼 SQL 时一次,形成纵深防御。
四、最终入库 SQL 拼接
以主表 INSERT 为例(hinfofun.php:297):
$empire->updatesql("insert into ".$infotbr['tbname']."(
id, classid, ttid, onclick, plnum, totaldown,
newspath, filename, userid, username,
firsttitle, isgood, ispic, istop,
isqf, ismember, isurl, truetime, lastdotime,
havehtml, groupid, userfen, titlefont, titleurl,
stb, fstb, restb, keyboard
{$ret_r['fields']} ← 自定义字段
) values(
'{$id}','{$add['classid']}','{$add['ttid']}','{$add['onclick']}',0,
'{$add['totaldown']}','{$newspath}','{$filename}','{$userid}',
'".addslashes($username)."', ← addslashes
'{$add['firsttitle']}','{$add['isgood']}','{$add['ispic']}','{$add['istop']}',
'{$isqf}',0,'{$add['isurl']}','{$truetime}','{$lastdotime}',
'{$havehtml}','{$add['groupid']}','{$add['userfen']}',
'".addslashes($add['my_titlefont'])."', ← addslashes
'".addslashes($add['titleurl'])."', ← addslashes
'{$ret_r['tb']}','{$public_r['filedeftb']}','{$public_r['pldeftb']}',
'".addslashes($add['keyboard'])."' ← addslashes
{$ret_r['values']} ← 自定义字段(已含addslashes)
);","ins");
数字字段直接用 {$add['classid']}(已 (int) 处理过),字符串字段用 addslashes() 包裹。
五、完整防护矩阵
POST 提交
│
├─ 数字字段 ──→ (int) 强转 ──→ 100% 安全
│
├─ 标准字符串 ──→ eDoRepPostComStr (HTML转义)
│ └─→ hRepPostStr (SQL拦截 + addslashes)
│
├─ 文件路径 ──→ RepFilenameQz (路径净化)
│ └─→ eCheckStrType_fname (类型校验)
│
└─ 自定义字段 ──→ RepPhpAspJspcodeText (PHP代码清除)
└─→ hEModCkFieldType (类型校验)
└─→ hRepPostStr2 (SQL拦截 + addslashes,保留HTML)
└─→ addslashes (入库前再包裹一层)
六、常见误解澄清
误解1:ecmsinfo.php 直接传 $_POST 不安全
// ecmsinfo.php:53
AddNews($_POST, $logininid, $loginin);
看起来像裸传,但实际上安全过滤全部在 AddNews() 内部完成。这是一种收口设计——所有入口统一经过业务函数,不会漏掉过滤。
误解2:自定义字段(newstext)没有过滤
实际上自定义字段经历了比标准字段更长的过滤链——RepPhpAspJspcodeText → hRepPostStr2(SQL过滤) → addslashes,而且因为 hRepPostStr2 不做 HTML 转义,富文本内容能正常保留。
误解3:双重 addslashes 会导致数据错误
PHP 的 addslashes 对已转义的字符再次加反斜杠(' → \' → \\'),但在帝国CMS的流程中,hRepPostStr2 的 AddAddsData 处理原始值,而最后的 addslashes 是拼 SQL 时加的——它们处理的是同一个值在不同阶段的安全需求,实际不会叠加。
七、总结
帝国CMS信息发布功能的防注入设计可以概括为三条原则:
- 数字用
(int),字符串用过滤函数——分类处理,不混用 - HTML转义和SQL过滤分离——
hRepPostStr2只做SQL防护不做HTML转义,满足富文本场景 - 收口到业务函数——入口文件只做路由,过滤集中在
DoPostInfoVar()和ReturnAddF()中
这种分层设计虽然代码看起来分散,但每一层职责清晰,组合起来形成了纵深防御。


