itsourcecode Baptism Information Management System 1.0 的编辑页面把 URL 中的 id 直接接到 SQL 条件后面。输入原本只负责选择一条洗礼记录,却取得了改写查询结构的能力;公开报告用一行常量构造的联合查询,让指定标记出现在编辑表单里。
CVE-2026-108926 对应 VDB-416749,报告者为 YuLi。下面把官方下载包中的查询、公开请求及响应证据接起来,并说明为何一段看似只返回单条记录的数据库封装仍会让联合查询结果进入页面。
漏洞记录与验证范围
- 影响版本
- itsourcecode Baptism Information Management System 1.0
- CWE
- CWE-89
- disclosed
| 项目 | 已确认信息 |
|---|---|
| 入口 | GET /ebaptism.php |
| 输入 | URL 查询参数 id,代码区分大小写 |
| 查询对象 | bapt_pers_spon 视图 |
| 分类 | CWE-89,SQL 注入 |
| 评分来源 | CVE 记录中的 CNA:VulDB |
| 修复版本 | 所核对的公告未列出 |
官方记录还给出 CVSS 3.1 基础分 6.3;本文和漏洞墙统一使用 CNA 的 CVSS 4.0 记录 5.3,不混用不同版本的分数。该向量包含 PR:L,并带有 E:P。
源码取自产品页面链接的 baptism.zip,SHA-256 为 22294a9f4470b22da0c31f0dc59f655bdb08452899d13c3e50705a2cdb48c3ab。以下行号属于这个下载快照;它为代码路径提供静态证据,不代表已经比对了报告者当时部署的全部文件。公开请求和截图属于报告中的实测材料;本次另做了源码核对、十六进制解码及列数检查,没有重跑原应用环境。
从参数到页面的完整路径
$id = $_GET['id'];
$mydb->setQuery("SELECT * from bapt_pers_spon where b_id=".$id);
$res = $mydb->loadSingleResult();这里没有引号需要先闭合:id 直接进入数值表达式位置。正常输入 1 得到 WHERE b_id=1;加入 SQL 运算符、子查询或联合查询时,数据库解析的是新语法,而不是一个不透明的记录编号。
includes/database.php:40-47 显示,setQuery() 只把字符串存入 sql_string,executeQuery() 再传给 mysql_query()。这两层封装没有把值绑定到占位符,方法名也不意味着输入经过类型检查。
function loadSingleResult() {
$cur = $this->executeQuery();
while ($row = mysql_fetch_object($cur)) {
$data = $row;
}
mysql_free_result($cur);
return $data;
}这个“单结果”方法实际遍历结果集,并不断覆盖 $data,最终返回最后读到的行。公开响应中,标记出现在 bapt_id 隐藏字段及其旁边的标签;相应输出位置是 ebaptism.php:42-43 的 $res->bapt_id。因此证据链不是“请求中带了 UNION”就结束,而是输入改变查询结果,返回对象再进入页面。
没有 ORDER BY 的查询不保证一般性的行顺序。截图证明该次请求的标记被选中并渲染,其他数据集或执行计划仍应按实际结果验证,别把这个封装行为当成所有环境下稳定的排序机制。
联合查询为何是 28 列
以下请求保留报告中的参数值,目标主机已替换为文档域名。它只返回预先选定的常量标记,不读取业务数据;这是公开报告的请求摘录,并非新增在线请求。
GET /ebaptism.php?id=1+UNION+ALL+SELECT+CONCAT(0x716b627071,0x616b744970674e674f594a6a4a7366766a454744786e6f7775576a737a7a6654636b657858765a73,0x7171766b71),NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL--+- HTTP/1.1
Content-Type: application/json
Host: target.exampleUNION ALL 两侧的投影数量需要一致。该 payload 包含一个 CONCAT(...) 表达式和 27 个 NULL,总计 28 列。官方下载包中的视图定义同样有 28 个投影,第一项是 bapt_id,与响应中的标记位置相吻合。
projection_count = 28
null_count = 27
prefix = qkbpq
value = aktIpgNgOYJjJsfvjEGDxnowuWjszzfTckexXvZs
suffix = qqvkq
marker = qkbpqaktIpgNgOYJjJsfvjEGDxnowuWjszzfTckexXvZsqqvkq三个十六进制字符串分别是前缀、常量值和后缀,拼接后得到完整标记。这里没有密码、令牌或数据库秘密。其余列使用 NULL 做列数与类型兼容填充;尾部 --+- 经查询字符串解码后提供 MySQL 行注释需要的空白。
截图中,标记作为表达式计算结果进入了隐藏字段和标签,而不是原样出现完整 SQL。这与源码中的字符串拼接和数据库结果输出相互支持。纯页面反射通常只会返回输入文本;若要独立复核,应同时保存正常记录页面、注入页面及数据库查询证据。
最小复核与回归条件
在一次性测试数据集里先确认正常记录可显示,再重放上面的常量请求。将 HTTP 会话、数据库内容与应用文件版本固定,只修改 id。观察完整标记是否出现在 bapt_id 对应位置,而不只是出现在日志或请求调试面板。
| 输入或状态 | 修复后的验收要求 |
|---|---|
| 已授权且存在的整数编号 | 返回对应记录 |
| 合法但不存在的编号 | 稳定的未找到响应 |
| 缺失、数组、负数或超范围编号 | 在查询前拒绝 |
| 含 SQL 语法的编号 | 不成为查询结构 |
| 无有效会话或无记录权限 | 在访问数据前终止 |
| 数据库异常 | 页面不暴露 SQL 与连接信息 |
原报告一处写“有效凭据登录后”,另一处写“无需认证”,其请求摘录也没有会话头。缺少会话头的摘录本身不证明完整认证流程;本文保留 CNA 的 PR:L 评估,不据此另行声明未认证利用。部署时要独立检查服务端鉴权和对象权限。
影响与边界
已展示的直接影响是攻击者控制的 SQL 表达式结果进入编辑页面。视图关联洗礼记录、个人详情和赞助人资料,查询边界失守可能让应用数据库权限范围内的数据被读取或影响;具体可触及的对象取决于账号权限和语句上下文。
单个常量回显并未展示整库导出、任意文件写入或操作系统命令执行。公开报告中较宽泛的“完全控制系统”描述需要额外证据,本文不将其列为已实现效果。受影响版本按 CNA 保持为 1.0,也不从一个快照外推全部衍生项目。
修复查询边界
下面是替换模式,不是厂商补丁。示例使用现代 PHP 的 PDO MySQL 接口,假设连接已建立;旧 mysql_* 应用需要先迁移数据库访问层,而非只复制这一段代码。
<?php
// Illustrative replacement; $pdo is an existing PDO MySQL connection.
$raw = $_GET['id'] ?? null;
$id = is_string($raw)
? filter_var($raw, FILTER_VALIDATE_INT, [
'options' => ['min_range' => 1, 'max_range' => 2147483647]
])
: false;
if ($id === false) {
http_response_code(400);
exit('Invalid id');
}
// Enforce session and object authorization before this query.
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$stmt = $pdo->prepare(
'SELECT * FROM bapt_pers_spon WHERE b_id = ?'
);
$stmt->bindValue(1, $id, PDO::PARAM_INT);
$stmt->execute();
$res = $stmt->fetch(PDO::FETCH_OBJ);
if ($res === false) {
http_response_code(404);
exit('Not found');
}整数校验约束业务域,参数绑定隔离 SQL 语法,两者作用不同。只把 id 强制转换为整数会把部分非法输入悄悄变成有效编号;明确拒绝更利于审计。生产代码还应统一捕获数据库异常,返回通用错误,并在服务端记录关联标识。
SQL 修复之外,页面仍须做记录级访问控制,并对输出到 HTML 的文本做上下文编码。前者决定用户能读哪条记录,后者处理 HTML 输出,两者都不替代参数绑定。修复后按上表回归,同时对所有调用 setQuery() 的动态条件做同类审计。
披露时间线
| 日期 | 事件 | 证据含义 |
|---|---|---|
| 2026-09-24 | GitHub 报告 #1 创建 | 平台创建时间;不等同于厂商收件日期 |
| 2026-10-11 | CNA 时间线标记 Advisory disclosed | 与漏洞墙披露日期一致 |
| 2026-10-12 | CVE 记录发布 | 官方记录的 datePublished |
VulDB 提交编号为 997397,获批条目为 VDB-416749。现有材料没有完整厂商沟通记录,也没有可核实的修复发布版本;这些字段保持未确认。GitHub 创建时间早于 CNA 披露日期,二者记录的事件不同,不把它们合并成一个“首次公开”日期。