Kimdir01/library-qr-csrf-xss-cve
GitHub: Kimdir01/library-qr-csrf-xss-cve
针对图书馆二维码系统的安全概念验证,披露了 CSRF 禁用、存储型 XSS、弱 UID 生成及缺少授权等十余项漏洞详情。
Stars: 0 | Forks: 0
# GitHub 安全公告:图书馆二维码系统 (CodeIgniter 4) — CSRF 被禁用、存储型 XSS、弱 UID
## 公告信息
| 字段 | 值 |
|-------|-------|
| **严重程度** | 严重 / 高 |
| **CWE** | CWE-352 (CSRF 被禁用) / CWE-79 (存储型 XSS) / CWE-327 (弱加密) |
| **CVSS v3.1** | 9.8 (全局 CSRF 被禁用) / 8.5 (存储型 XSS) / 5.0 (弱 UID) |
| **CVSS 向量** | AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H |
## 摘要
图书馆二维码系统 (Sistem Perpustakaan QR Code, CodeIgniter 4) 存在全局禁用 CSRF 保护的问题(在全局 `before` 过滤器链中 `csrf` 过滤器被注释掉了)、通过未转义的图书/会员数据输出导致的系统性存储型 XSS(任何地方都没有使用 `esc()`)、使用 SHA-1 且仅有 1001 种可能熵值的弱 UID 生成方式,以及所有 admin 控制器缺少授权/存在 IDOR。
## 漏洞详情
### 受影响的包/仓库
- **仓库:** `sistem-perpustakaan-qr-code`
- **受影响版本:** 当前版本
- **修复版本:** 无 (0-day)
**提交哈希 (已审计):** `a7084f0e8f001d340c68a3b4ced6f263a3bdfd58`
## CVE-REQUEST-001:全局 CSRF 保护被禁用 (CVSS 9.8)
**文件:** `app/Config/Filters.php`,第 37-39 行
```
'before' => [
// 'honeypot',
// 'csrf', // <-- COMMENTED OUT!
'forcehttps',
'pagecache',
],
```
`csrf` 过滤器在全局 `before` 链中被注释掉了。CodeIgniter 4 不会验证 POST/PUT/DELETE 请求上的 CSRF token,即使视图仍然渲染了 `= csrf_field() ?>` —— 服务端验证也永远不会执行。
**受影响的操作(均存在 CSRF 漏洞):**
- 创建/编辑/删除图书
- 创建/编辑/删除会员(完整 PII)
- 创建/删除借阅(借书)
- 还书
- 缴纳罚款(可能部分支付)
- 创建/更新/删除 admin 用户
- 更改罚款设置
- 对类别和书架进行 CRUD 操作
## CVE-REQUEST-002:图书和会员数据中的存储型 XSS (CVSS 8.5)
**根本原因:** 所有视图都没有使用 CI4 的 `esc()` 函数。`= ?>` 语法不会自动转义。
**通过 `string` 验证字段的 XSS:**
- 图书标题 (`required|string|max_length[127]` — 接受任何 HTML/JS)
- 图书出版社 (`required|string|max_length[64]`)
- 会员地址 (`required|string|min_length[5]|max_length[511]`)
**受影响的视图(所有输出均未转义):**
- `books/index.php:85` — `= "{$book['title']} ({$book['year']})"; ?>`
- `books/show.php:66-69` — 标题、作者、出版社、年份
- `members/index.php:67-72` — 会员姓名、电子邮件、电话、地址
- `members/show.php:67-68` — 会员详情
- `loans/index.php:92` — 图书标题
- `home/book.php:61` — 搜索结果
**PoC:** 创建图书时,将标题设置为:
```
```
任何查看图书列表的 admin 都会触发该 payload。
### 搜索中的反射型 XSS (CVSS 8.1):
**7 个文件**包含 `value="= $search ?? ''; ?>"` — 搜索 GET 参数未经转义直接输出:
- `books/index.php:36`, `members/index.php:28`, `loans/index.php:33`, `returns/index.php:32`, `fines/index.php:40`, `home/book.php:26`
PoC: `/admin/books?search=">`
## CVE-REQUEST-003:弱 UID 生成 (SHA-1 + rand 1001) (CVSS 5.0)
**文件:** `app/Controllers/Members/MembersController.php`,第 193-199 行
```
$uid = sha1(
$this->request->getVar('first_name')
. $this->request->getVar('email')
. $this->request->getVar('phone')
. rand(0, 1000) // Only 1001 possible values!
. md5($this->request->getVar('gender'))
);
```
只有 1001 种可能的随机值。如果 first_name、email 和 phone 是已知的(通常是可猜测的),攻击者可以通过尝试所有 1001 个 `rand()` 值来暴力破解 UID。
## CVE-REQUEST-004:所有 Admin 路由缺少授权 / 存在 IDOR (CVSS 6.5)
**文件:** `app/Config/Routes.php`,第 39 行
```
$routes->group('admin', ['filter' => 'session'], ...);
```
`admin/*` 路由仅应用了 `session` 过滤器(检查用户是否已登录)。没有基于角色的授权:
- `MembersController::show()` — 任何 admin 都可以通过 UID 查看任何会员的完整 PII、借阅历史、罚款
- `LoansController::show()` — 任何 admin 都可以查看任何借阅记录
- `ReturnsController::create()` — 任何 admin 都可以处理任何借阅的还书
- `FinesController::pay()/update()` — 任何 admin 都可以修改/支付任何罚款
只有 `fines/settings` 和 `users` 路由具有 `group:superadmin` 过滤。
## CVE-REQUEST-005:库存验证中的竞态条件 (CVSS 5.3)
**文件:** `app/Controllers/Loans/LoansController.php`,第 283-359 行
`create()` 方法在插入借阅记录之前不会重新验证图书库存。如果两个 admin 同时提交同一本书的借阅,即使只有一本可借,两者也都会成功。没有针对会员的借阅限制。
## 其他发现(共 14 项):
| # | 漏洞 | CVSS | 类型 |
|---|--------------|------|------|
| 4 | AJAX 搜索端点泄露完整的会员 PII | 6.5 | 信息泄露 |
| 6 | 删除二维码/图书封面时的路径遍历 | 6.1 | 路径遍历 |
| 9 | Dashboard 加载所有数据且没有分页 | 5.3 | 信息泄露 |
| 10 | 图书封面上传至 Web 可访问目录 | 5.3 | 文件上传 |
| 11 | 通过 GET 参数重新生成二维码 | 3.5 | 业务逻辑 |
| 12 | 撤销还书反转罚款且无审计 | 4.3 | 业务逻辑 |
| 13 | Session matchIP=false,Cookie 安全性弱 | 3.7 | Session 管理 |
| 14 | .env.example 中的默认 root 凭据 | 3.3 | 硬编码密钥 |
## 影响
- 通过 CSRF 实现**完全接管应用程序**(任何 admin 操作均可被伪造)
- 通过影响所有 admin 的存储型 XSS 实现 **Session 劫持**
- 通过 IDOR + AJAX 枚举导致**会员 PII 泄露**
- 通过针对 fines/pay 的 CSRF + IDOR 进行**罚款篡改**
- 通过竞态条件实现**图书失窃**(借阅图书而不减少库存)
## 补丁 / 修复
1. **严重:** 在 `Filters.php` 全局 `before` 数组中取消对 `'csrf'` 的注释
2. 在视图中所有的 `= ?>` 输出中添加 `esc()`(尤其是图书标题、会员数据)
3. 将 `sha1(...rand(0,1000))` 替换为 `bin2hex(random_bytes(16))`
4. 添加所有权/权限检查(不仅仅是 `session` 过滤器)
5. 添加并强制执行每个会员的借阅限制
6. 重命名 `delete()` 还书方法,要求使用带 CSRF 的 POST 请求
7. 在 `public/uploads/` 中添加 `.htaccess` 拒绝访问 `.php`
8. 在输出前验证 `$search` 参数
## 披露时间线
- **2026-06-28:** 所有漏洞由 Fatullayev Asadbek (Kimdir01) 发现
- **待定:** 报告给维护者
- **待定:** 通过 GitHub 请求 CVE
- **待定:** 发布公告
## 致谢
- 发现者:Fatullayev Asadbek | GitHub: Kimdir01
- 独立安全研究员
## 参考
- CWE-352:跨站请求伪造
- CWE-79:跨站脚本攻击
- CWE-327:使用破损或有风险的加密算法
- CWE-862:缺少授权
标签:CodeIgniter, CSRF, Web安全, 存储型XSS, 漏洞证明, 蓝队分析, 越权访问