问题:DB 抽象层使用 addslashes 转义而非 PDO 预处理,存在宽字节注入与转义绕过
此文章为XIUNOX版本重构审计时发现问题,分享出来方便后续想基于xiuno bbs4.0.4版本制作维护版本或插件模板等需求的开发者和站长参考。
现象
Xiuno BBS 4.0.4 的数据库抽象层在拼接 SQL 时统一使用 PHP 内置 addslashes() 对字符串值进行转义,并未使用 PDO 预编译语句(prepare + bind)。该转义方式存在以下风险:
- 当 MySQL 连接字符集为 GBK/GB2312/BIG5 等多字节字符集时,攻击者可构造
%bf%27 形式的宽字节序列,使 addslashes 添加的反斜杠被前一个字节“吞掉”,从而绕过转义形成 SQL 注入。
addslashes 与 mysql_real_escape_string 行为不一致,对部分特殊字符(如 \x00、\n、\r、\x1a)处理不当,存在边缘绕过场景。
db_mysql.class.php 使用 PHP 7.0 已移除的 mysql_* 扩展,无法设置连接字符集到 mysql_set_charset,只能用 SET NAMES,导致 addslashes 与 server 端字符集认知不一致,是宽字节注入的典型温床。
源码证据
文件:xiunobbs_4.0.4/xiunophp/db.func.php 行 242-279(db_cond_to_sqladd 使用 addslashes)
function db_cond_to_sqladd($cond) {
$s = '';
if(!empty($cond)) {
$s = ' WHERE ';
foreach($cond as $k=>$v) {
if(!is_array($v)) {
$v = (is_int($v) || is_float($v)) ? $v : "'".addslashes($v)."'";
$s .= "`$k`=$v AND ";
} elseif(isset($v[0])) {
$s .= '(';
foreach ($v as $v1) {
$v1 = (is_int($v1) || is_float($v1)) ? $v1 : "'".addslashes($v1)."'";
$s .= "`$k`=$v1 OR ";
}
} else {
foreach($v as $k1=>$v1) {
if($k1 == 'LIKE') {
$k1 = ' LIKE ';
$v1="%$v1%";
}
$v1 = (is_int($v1) || is_float($v1)) ? $v1 : "'".addslashes($v1)."'";
$s .= "`$k`$k1$v1 AND ";
}
}
}
}
return $s;
}
文件:xiunobbs_4.0.4/xiunophp/db.func.php 行 303-342(insert/update 拼 SQL 也用 addslashes)
function db_array_to_update_sqladd($arr) {
$s = '';
foreach($arr as $k=>$v) {
$v = addslashes($v);
$v = (is_int($v) || is_float($v)) ? $v : "'$v'";
$s .= "`$k`=$v,";
}
return substr($s, 0, -1);
}
function db_array_to_insert_sqladd($arr) {
foreach($arr as $k=>$v) {
$k = addslashes($k);
$v = addslashes($v);
$keys[] = '`'.$k.'`';
$v = (is_int($v) || is_float($v)) ? $v : "'$v'";
$values[] = $v;
}
}
文件:xiunobbs_4.0.4/xiunophp/db_mysql.class.php 行 52-58(使用废弃 mysql_* 扩展,且 SET NAMES 设置字符集)
public function real_connect($host, $user, $password, $name, $charset = '', $engine = '') {
$link = @mysql_connect($host, $user, $password);
if(!$link) { $this->error(mysql_errno(), '连接数据库服务器失败:'.mysql_error()); return FALSE; }
if(!mysql_select_db($name, $link)) { $this->error(mysql_errno(), '选择数据库失败:'.mysql_error()); return FALSE; }
$charset AND $this->query("SET names $charset, sql_mode=''", $link);
return $link;
}
文件:xiunobbs_4.0.4/xiunophp/db_pdo_mysql.class.php 行 114-128(虽然使用 PDO,但 query/exec 均直接拼接 SQL 字符串,未使用 prepare/execute)
public function query($sql) {
if(!$this->rlink && !$this->connect_slave()) return FALSE;
$link = $this->link = $this->rlink;
try {
$t1 = microtime(1);
$query = $link->query($sql);
$t2 = microtime(1);
} catch (Exception $e) {
$this->error($e->getCode(), $e->getMessage());
return FALSE;
}
}
public function exec($sql) {
$n = $link->exec($sql);
}
风险等级与结论
高危
危害后果:
- 在 GBK/GB2312 等多字节字符集环境下,攻击者可通过宽字节注入绕过转义,对任意 SQL 语句进行注入,导致拖库、写马、提权、绕过登录。
addslashes 转义不如 mysql_real_escape_string 严谨,对部分字符处理存在差异,存在边缘绕过风险。
db_mysql.class.php 依赖 PHP 5.x 的 mysql_* 扩展,在 PHP 7+ 环境下直接不可用,且无法使用 mysql_set_charset 同步字符集认知,进一步放大宽字节注入风险。
- 即使使用 PDO 驱动,也并未启用预处理,无法享受 PDO 参数绑定的安全优势。
修复建议:
- 全面切换到 PDO 预编译语句
prepare() + bindParam()/bindValue(),禁止再使用字符串拼接 + addslashes。
- 在连接时通过
PDO::ATTR_EMULATE_PREPARES => false 关闭模拟预处理,确保语句真正由 server 端编译。
- 强制使用
utf8 或 utf8mb4 字符集,并通过 mysql_set_charset/PDO::query("SET NAMES utf8mb4") 显式同步 client/server 字符集认知。
- 移除
db_mysql.class.php 中 mysql_* 扩展支持,统一使用 db_pdo_mysql.class.php。
- 对所有进入 SQL 的字段做白名单校验(如表名、列名),数值做 intval 强转。