LatePoint 预订插件账户接管(CVE-2026-118066)
LatePoint 中的未认证 SQL 注入让攻击者可以接管客户账户。5.7.7 已修复,5.7.8 完成加固。
TL;DR
- 未认证攻击者可以把字符串
IS NOT NULL作为密码重置令牌发送,从而重置 LatePoint 客户账户的密码。Wordfence 报告了该漏洞,CVE 记录致谢 Jakub Herman,并于 2026 年 11 月 7 日发布。 - LatePoint 5.7.6 及更早版本受影响。该插件有 100,000 个活跃安装。漏洞位于把哨兵值转换为 SQL 的查询构建器中,可通过两个公开 AJAX 路由访问。
- CVE 记录将其评为 CVSS v3.1 9.1 严重。未报告在野利用,截至 2026 年 11 月 7 日该 CVE 不在 CISA KEV 目录中。
- 请更新到 5.7.8。如果无法更新,请封锁文中记录的两个请求模式,并审计客户密码重置;参见“如何修复?”。
我受影响吗?
只有同时满足以下所有条件时,您才会受影响:
- 站点运行 LatePoint 插件 5.7.6 或更早版本。
- 插件处于启用状态,因此其公开 AJAX 路由会响应请求。
- 至少有一条
account_nonse非空的客户记录。LatePoint 会为客户创建该值,因此只有一个预订的站点也满足条件。 - 攻击者能够获知客户电子邮件地址。公告中的攻击链从订单意图中读取一个地址;公开的预订确认或员工目录也可能提供地址。
- 对于关联 WordPress 账户的影响,
wp_users_as_customers选项已启用。
易受攻击的代码位于插件的核心客户类和查询类中。不需要任何可选模块。这两个路由注册给未登录访客,因此攻击者不需要站点账户。
| 分支 | 受影响版本 | 修复版本 |
|---|---|---|
| LatePoint | 5.7.6 及更早版本 | 5.7.7(2026 年 11 月 2 日),随后 5.7.8(2026 年 11 月 3 日)完成模型修复 |
CVE 记录将 5.7.6 及更早版本列为受影响。5.7.7 封锁了两个已记录的请求路径。5.7.8 移除了查询构建器中的字符串字面量分支,因此请使用 5.7.8。
使用 WP-CLI 检查已安装版本:
wp plugin get latepoint --field=version
没有 WP-CLI 时,读取插件头部:
grep -m1 "Version:" wp-content/plugins/latepoint/latepoint.php
任何不高于 5.7.6 的版本都受影响。
如何修复?
- 通过 WordPress 管理后台将 LatePoint 更新到 5.7.8,或运行
wp plugin update latepoint。插件页面将 5.7.8 列为当前版本,发布于 2026 年 11 月 3 日。 - 使用
wp plugin get latepoint --field=version确认新版本。 - 如果站点运行插件文件的缓存副本,请清除缓存并确认插件头部显示 5.7.8。
- 检查更新前的客户密码重置和登录情况。检查步骤见“如何检查是否已遭入侵?”。
如果无法更新,请在可以更新之前禁用 LatePoint。禁用插件会移除预订页面和客户中心页面,因此请安排维护窗口。
Web 应用防火墙规则可以减少暴露,但无法修复代码。需要封锁的两个请求模式是:
route_name=customer_cabinet__change_password,且password_reset_token值包含IS NOT NULL。route_name=steps__start_from_order_intent,且order_intent_key值包含IS NOT NULL。
对 URL 解码后的参数值进行不区分大小写的匹配。攻击者可以改变编码,因此只匹配字符串 IS+NOT+NULL 的规则会漏掉其他形式。只有当真实令牌包含空格时,该规则才会封锁合法重置,而插件的十六进制令牌从不包含空格。
供应商未列出任何变通方法。限制可访问客户中心的账户没有帮助,因为这些路由按设计就是公开的。
根本原因是什么?
当 WHERE 值等于字符串字面量 IS NOT NULL 时,查询构建器将其视为 SQL 关键字,而不是要绑定的值。LatePoint 的 OsModel::build_conditions_query() 将每个值与该字符串比较,并把关键字追加到 SQL 文本中。该分支不添加占位符,也不添加绑定值。
5.7.6 中 lib/models/model.php 的易受攻击分支:
} elseif ( $value === 'IS NULL' ) {
// IS NULL
$temp_query = $this->with_table_name( $column ) . ' IS NULL ';
} elseif ( $value === 'IS NOT NULL' ) {
// IS NOT NULL
$temp_query = $this->with_table_name( $column ) . ' IS NOT NULL ';
} elseif ( is_array( $value ) && ! empty( $value ) ) {
$temp_query = $this->with_table_name( $column ) . ' IN ' . OsModel::where_in_array_to_string( $value );
} else {
// Add to list of query values
$where_values[] = $value;
}
客户辅助函数将请求值直接传给该构建器:
public static function get_by_account_nonse( $account_nonse ) {
if ( empty( $account_nonse ) ) {
return false;
}
$account_nonse = sanitize_text_field( $account_nonse );
$customer = new OsCustomerModel();
return $customer->where( [ 'account_nonse' => $account_nonse ] )->set_limit( 1 )->get_results_as_models();
}
sanitize_text_field() 会去除标签和多余空白。它会保留空格和 SQL 关键字,因此 IS NOT NULL 原样通过。辅助函数随后构建条件为 account_nonse IS NOT NULL 的查询。所有 account_nonse 非空的客户都会匹配,set_limit(1) 返回一行。
密码重置端点使用该辅助函数作为认证检查:
} elseif ( ! empty( $params['password_reset_token'] ) ) {
$params['password_reset_token'] = sanitize_text_field( $params['password_reset_token'] );
$customer = OsCustomerHelper::get_by_account_nonse( $params['password_reset_token'] );
// Validate token expiration (15 minutes)
if ( $customer ) {
$created_at = OsMetaHelper::get_customer_meta_by_key( 'password_reset_token_created_at', $customer->id );
端点将返回的客户视为重置令牌的所有者。它不会将令牌与存储值进行比较。唯一剩下的检查是 15 分钟的新鲜度时间戳,攻击者可以通过为目标地址请求重置来启动它。
订单意图路由中也存在相同模式。OsOrderIntentHelper::get_order_intent_by_intent_key() 将 intent_key 传给 where(),因此 params[order_intent_key]=IS NOT NULL 变成 intent_key IS NOT NULL,并返回第一个订单意图。预订表单随后显示该记录的客户姓名和电子邮件。
5.7.5 版本在另一个查询中加入了 is_scalar() 检查,即 OsStepsHelper::set_customer_object() 中的客户 uuid。该检查拒绝嵌套数组,但接受标量字符串 IS NOT NULL,因此没有关闭此路径。
如何被利用?
- 攻击者获知客户电子邮件地址。公告中的攻击链将
params[order_intent_key]=IS NOT NULL发送到公开路由steps__start_from_order_intent。查询返回第一个订单意图,预订表单显示该记录的客户姓名和电子邮件。 - 攻击者通过公开路由
customer_cabinet__request_password_reset_token请求重置该地址,参数为params[password_reset_email]=<email>。LatePoint 向客户发送令牌,并为该客户保存password_reset_token_created_at。15 分钟窗口开始。 - 攻击者向公开路由
customer_cabinet__change_password发送 POST 请求,参数为params[password_reset_token]=IS NOT NULL和新密码。端点通过哨兵查找客户,并返回第一个匹配行,公告将其描述为 ID 最小的客户。 - 端点读取该客户的重置时间戳。由于重置请求,时间戳是新鲜的,因此 15 分钟检查通过。端点随后使用攻击者的密码调用
update_password()。 - 攻击者以该客户身份登录。账户现在暴露客户的预订、订单和个人数据。如果启用了
wp_users_as_customers,可以通过同一客户记录访问关联的 WordPress 账户。
公告记录了请求参数。改编自公告,密码更改请求的形式如下:
POST /wp-admin/admin-ajax.php
Content-Type: application/x-www-form-urlencoded
action=latepoint_route_call&route_name=customer_cabinet__change_password¶ms[password_reset_token]=IS+NOT+NULL¶ms[password]=<new-password>¶ms[password_confirmation]=<new-password>
订单意图请求使用相同形式,路由为 route_name=steps__start_from_order_intent,参数为 params[order_intent_key]=IS+NOT+NULL。
攻击者不需要邮件发送的令牌。哨兵替代了令牌查找,重置请求只是启动新鲜度计时器。
攻击如何展开?
未认证攻击者将请求字符串转换为 SQL 条件,接管第一个匹配的客户,并启动端点检查的重置计时器。
攻击者能做什么?
未认证攻击者可以按请求接管一个 LatePoint 客户账户,并读取或修改该客户的预订、订单和个人数据。被覆盖的账户是哨兵查询返回的第一行,公告将其描述为 ID 最小的客户。
影响取决于以下条件:
- 站点运行 LatePoint 5.7.6 或更早版本,且插件已启用。
- 攻击者可以访问公开 AJAX 路由。
- 客户记录的
account_nonse非空。 - 攻击者可以获知客户电子邮件地址并为其请求重置。
- 密码更改请求到达时,客户的重置时间戳在 15 分钟窗口内。
- 对于关联的 WordPress 账户,
wp_users_as_customers已启用。
CVE 记录将机密性和完整性评为高,可用性评为无。攻击者可以读取客户数据并更改密码,但该漏洞不执行代码,也不改变安全范围。插件仅将 WordPress 账户关联到客户安全角色,因此关联账户是订阅者或客户,而不是管理员,除非站点更改了该配置。
公告没有展示通往 WordPress 管理员账户的路径。它描述的是客户账户接管,在启用该选项时还包括关联的 WordPress 订阅者或客户账户。
为什么修复有效?
修复阻止请求字符串到达 SQL 关键字分支。LatePoint 5.7.7 在两个已记录入口加入严格的字符检查。LatePoint 5.7.8 从查询构建器中移除字面量 IS NOT NULL 分支,因此只有内部标记对象可以请求该关键字。
lib/controllers/customer_cabinet_controller.php 中的 5.7.7 防护:
} elseif ( ! empty( $params['password_reset_token'] ) ) {
$params['password_reset_token'] = sanitize_text_field( $params['password_reset_token'] );
- $customer = OsCustomerHelper::get_by_account_nonse( $params['password_reset_token'] );
+ // Reset token is a hex key - a value with spaces/operators (e.g. "IS NOT NULL") is never valid.
+ if ( is_string( $params['password_reset_token'] ) && ctype_alnum( $params['password_reset_token'] ) ) {
+ $customer = OsCustomerHelper::get_by_account_nonse( $params['password_reset_token'] );
+ }
lib/controllers/steps_controller.php 中的 5.7.7 防护:
public function start_from_order_intent() {
- $order_intent = OsOrderIntentHelper::get_order_intent_by_intent_key( $this->params['order_intent_key'] );
+ // Intent key is a hex token - a value with spaces/operators (e.g. "IS NOT NULL") is never valid.
+ $order_intent_key = sanitize_text_field( $this->params['order_intent_key'] ?? '' );
+ $order_intent = ( is_string( $order_intent_key ) && ctype_alnum( $order_intent_key ) ) ? OsOrderIntentHelper::get_order_intent_by_intent_key( $order_intent_key ) : new OsOrderIntentModel();
ctype_alnum() 拒绝 IS NOT NULL 中的空格,因此跳过查找,请求安全失败。同一版本还加入了标记对象 null_condition() 和 not_null_condition(),以及辅助函数 where_null() 和 where_not_null(),让内部代码无需请求字符串即可表达 NULL 比较。
lib/models/model.php 中的 5.7.8 更改移除了字面量分支:
- } elseif ( $value === 'IS NULL' || $value === self::null_condition() ) {
+ } elseif ( $value === self::null_condition() || ( $value === 'IS NULL' && self::is_legacy_null_condition() ) ) {
// IS NULL
$temp_query = $this->with_table_name( $column ) . ' IS NULL ';
- } elseif ( $value === 'IS NOT NULL' || $value === self::not_null_condition() ) {
+ } elseif ( $value === self::not_null_condition() ) {
// IS NOT NULL
$temp_query = $this->with_table_name( $column ) . ' IS NOT NULL ';
} elseif ( is_array( $value ) && ! empty( $value ) ) {
在 5.7.8 之后,字符串 IS NOT NULL 落入绑定值分支。IS NULL 字面量仅保留给旧版 LatePoint Pro 附加组件,且仅在附加组件版本低于 1.7.7 时使用。CVE 记录将 5.7.7 视为修复版本。5.7.8 的更改关闭了任何其他将请求数据传入 where() 的调用者的模型级路径。
如何检查是否已遭入侵?
首先检查记录请求正文的日志,例如 Web 应用防火墙审计日志或反向代理请求日志。LatePoint 从 POST 正文读取路由和参数,因此标准访问日志只记录对 admin-ajax.php 的请求。如果攻击者在查询字符串中发送参数,访问日志会包含它们。
grep -Ei 'route_name=(customer_cabinet__change_password|steps__start_from_order_intent)' /var/log/apache2/*access*.log /var/log/nginx/*access*.log /var/log/modsec_audit.log 2>/dev/null
在匹配行中查找哨兵值,使用 URL 解码形式并忽略大小写。该值可能显示为 IS+NOT+NULL、IS%20NOT%20NULL 或混合编码。命中是线索,不是证据,因为扫描器也可能发送相同字符串。
检查您未预期的客户重置时间戳:
wp db query "SELECT object_id, meta_value FROM wp_latepoint_customer_meta WHERE meta_key='password_reset_token_created_at' ORDER BY meta_value DESC LIMIT 50;"
将 wp_ 替换为您的表前缀。LatePoint 发送重置邮件时会写入重置时间戳。将这些行与您的支持记录进行比较。客户未请求的重置,随后出现来自陌生地址的登录,是更强的信号。
LatePoint 不保留客户密码更改的审计日志。将客户的密码更改报告与重置行进行比较,并检查同一时间段内客户中心登录的 Web 日志。
供应商未发布入侵指标。失败的尝试不会留下供应商特定的痕迹,成功的尝试可能只留下更改后的密码和重置时间戳。
时间线
- 2026 年 10 月 27 日:CVE-2026-118066 被预留。
- 2026 年 11 月 2 日:LatePoint 5.7.7 发布,包含两个入口防护和标记辅助函数。
- 2026 年 11 月 3 日:LatePoint 5.7.8 发布,从查询构建器中移除字面量
IS NOT NULL分支。 - 2026 年 11 月 7 日:CVE 记录于 11:26 UTC 发布,公告 GHSA-rmmm-8g4j-3qgc 于 12:31 UTC 发布。
常见问题
攻击者需要站点账户吗?
不需要。这两个路由注册给未登录访客,密码更改端点在收到重置令牌时跳过 nonce 检查。攻击者只需要对站点的网络访问。
5.7.7 足够吗?
它封锁了两个已记录的请求路径。5.7.7 的查询构建器仍将字面量 IS NOT NULL 字符串视为 SQL,因此将请求数据传入 where() 的其他调用者仍可到达该分支。5.7.8 移除了该分支。请更新到 5.7.8。
攻击者能接管 WordPress 管理员账户吗?
默认不能。该漏洞接管的是 LatePoint 客户记录。插件仅在启用 wp_users_as_customers 时将客户关联到 WordPress 账户,并将该关联限制为客户安全角色。更改了角色映射的站点应检查其配置。
是否已在野利用?
截至 2026 年 11 月 7 日未报告在野利用,且该 CVE 不在 CISA KEV 目录中。公告包含请求参数,因此该技术已公开。
如果无法立即更新怎么办?
在可以更新之前禁用 LatePoint。如果预订流程必须保持在线,请在 Web 应用防火墙封锁两个请求模式,并每天审计重置时间戳。防火墙规则减少暴露,但不修复查询构建器。
请求失败是否证明站点安全?
不能。哨兵返回第一个匹配的客户,因此即使攻击者为重置计时器猜错了电子邮件,请求也可能成功。失败的密码更改仍可能匹配了某个客户,只在新鲜度检查时失败。请同时检查日志和重置行。
参考文献
- GitHub 公告 GHSA-rmmm-8g4j-3qgc,2026 年 11 月 7 日发布,包含受影响范围、易受攻击代码引用和请求参数:https://github.com/advisories/GHSA-rmmm-8g4j-3qgc(2026 年 11 月 7 日检查)
- CVE-2026-118066 记录,2026 年 11 月 7 日发布,CVSS v3.1 9.1 和 CWE-287:https://www.cve.org/CVERecord?id=CVE-2026-118066(2026 年 11 月 7 日检查)
- Wordfence 威胁情报公告,CVE-2026-118066,CNA 记录:https://www.wordfence.com/threat-intel/vulnerabilities/id/2f7f5dc8-8cf7-493d-b012-89ff36a4dc02(2026 年 11 月 7 日检查)
- LatePoint 更新日志,5.7.7 于 2026 年 11 月 2 日,5.7.8 于 2026 年 11 月 3 日:https://wordpress.org/plugins/latepoint/#developers(2026 年 11 月 7 日检查)
- LatePoint
lib/models/model.php5.7.6,IS NOT NULL分支:https://plugins.trac.wordpress.org/browser/latepoint/tags/5.7.6/lib/models/model.php#L411(2026 年 11 月 7 日检查) - LatePoint
lib/helpers/customer_helper.php5.7.6,get_by_account_nonse():https://plugins.trac.wordpress.org/browser/latepoint/tags/5.7.6/lib/helpers/customer_helper.php#L228(2026 年 11 月 7 日检查) - LatePoint
lib/controllers/customer_cabinet_controller.php5.7.6,change_password():https://plugins.trac.wordpress.org/browser/latepoint/tags/5.7.6/lib/controllers/customer_cabinet_controller.php#L468(2026 年 11 月 7 日检查) - LatePoint
lib/controllers/steps_controller.php5.7.6,start_from_order_intent():https://plugins.trac.wordpress.org/browser/latepoint/tags/5.7.6/lib/controllers/steps_controller.php#L187(2026 年 11 月 7 日检查) - LatePoint 变更集 3779410,公告引用的安全更改:https://plugins.trac.wordpress.org/changeset?reponame=&old=3779410%40latepoint&new=3779410%40latepoint(2026 年 11 月 7 日检查)
- LatePoint
lib/models/model.php5.7.8,标记对象分支:https://plugins.trac.wordpress.org/browser/latepoint/tags/5.7.8/lib/models/model.php#L455(2026 年 11 月 7 日检查) - WordPress.org 插件 API,
latepoint,100,000 个活跃安装和 1,841,969 次下载:https://wordpress.org/plugins/latepoint/(2026 年 11 月 7 日检查) - CISA 已知被利用漏洞目录 JSON 源,2026 年 11 月 7 日检查 CVE-2026-118066(未列出):https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026 年 11 月 7 日检查)