CVE-2026-100371 Overview
CVE-2026-100371 is a broken authorization vulnerability in InvoicePlane, a self-hosted open source application for managing invoices, clients, and payments. Version 1.7.2 introduced an authorization guard on Users::change_password() to prevent secondary administrators from directly resetting the primary administrator's password. That fix did not extend to Users::form(), and the user_email attribute was omitted from PROTECTED_FIELDS. A secondary administrator (user_type=1, user_id != 1) can rewrite the primary administrator's email address and then trigger the public password-recovery flow to receive the reset token, taking over user_id=1. The issue is classified as [CWE-863: Incorrect Authorization].
Critical Impact
A secondary administrator can achieve full account takeover of the primary administrator via an alternate password-recovery path, bypassing the fix delivered in PR #1638.
Affected Products
- InvoicePlane 1.7.2
- InvoicePlane versions relying on the pre-8616fa4Users::form() authorization logic
- Self-hosted InvoicePlane deployments exposing the users administration module to secondary administrators
Discovery Timeline
- 2026-09-28 - CVE-2026-100371 published to NVD
- 2026-09-30 - Last updated in NVD database
Technical Details for CVE-2026-100371
Vulnerability Analysis
InvoicePlane 1.7.2 shipped a targeted authorization guard on Users::change_password() to close a prior cross-administrator password-change flaw. The remediation protects only the direct password-change action. It leaves Users::form() — the general user-edit handler — without an equivalent object-level authorization check when a secondary administrator edits the primary administrator's record. The PROTECTED_FIELDS constant in Mdl_users.php blocks writes to user_type, user_active, and user_psalt, but not to user_email. Password recovery resolves an account by user_email, so mutating that attribute is equivalent to transferring identity ownership. This makes the recovery workflow the pivot for takeover.
Root Cause
The root cause is inconsistent object-level authorization across code paths that mutate the same privileged record. Users::form() treats a secondary administrator as authorized to edit arbitrary user rows, and the field allow-list omits the identity attribute that downstream recovery trusts. Authorization was enforced on the wrong operation rather than on the record boundary of user_id=1.
Attack Vector
An authenticated secondary administrator navigates to the user-edit form for user_id=1 and submits a modified user_email value. The attacker then requests a password reset from the public recovery endpoint using the attacker-controlled email. InvoicePlane emails a reset token to the attacker, who sets a new password and logs in as the primary administrator.
// Security patch: application/modules/users/controllers/Users.php
$id = $id ? (int) $id : null;
$current_user_id = (int) $this->session->userdata('user_id');
$is_self_edit = $id && $id === $current_user_id;
- $is_primary_admin = $current_user_id === 1;
+ $is_primary_admin = Mdl_Users::is_primary_administrator($current_user_id);
+ // Object-level authorization: only the primary administrator may edit
+ // another user's record. A peer administrator is limited to its own
+ // account, so it cannot rewrite user_id 1's role, email or password
+ // through this form (CWE-639 / CWE-269).
if ($id && !$is_self_edit && !$is_primary_admin) {
show_error(trans('access_denied'), 403);
Source: InvoicePlane commit 8616fa4
// Security patch: application/modules/users/models/Mdl_users.php
private const PROTECTED_FIELDS = ['user_type', 'user_active', 'user_psalt'];
+ /**
+ * The bootstrap root account. Only this account may edit its own record;
+ * a peer administrator must not be able to alter any of its attributes
+ * (password, email, role type, active flag, or otherwise).
+ */
+ public const PRIMARY_ADMINISTRATOR_ID = 1;
+
+ /**
+ * Identity- and privilege-bearing fields the primary administrator's record
+ * must never receive from a non-root session, regardless of code path.
+ */
+ private const PRIMARY_ADMIN_LOCKED_FIELDS = ['user_type', 'user_active', 'user_psalt', 'user_email', 'user_password'];
Source: InvoicePlane commit 8616fa4
Detect Identity Attacks the Moment Authentication Fails
Singularity™ Identity surfaces credential misuse, lateral movement, and privilege escalation in real time, so you can contain the breach before it spreads.
Stop Identity AttacksDetection Methods for CVE-2026-100371
Indicators of Compromise
- POST requests to /users/form/1 originating from a session whose user_id is not 1.
- Audit records showing a change to user_email on user_id=1 followed by a password-reset request for that new address.
- Successful login events for user_id=1 from an IP address or user-agent previously associated with a secondary administrator account.
- Recent modifications to ip_users.user_email where the row's user_id equals 1.
Detection Strategies
- Instrument the users controller to log every mutation to user_id=1, including the acting session.user_id, request path, and field diff.
- Correlate user_email changes on privileged accounts with subsequent password-recovery requests within a short time window.
- Alert when a non-root session issues a POST to users/form/{id} with id != session.user_id.
Monitoring Recommendations
- Enable web server access logging with request bodies redacted, and forward to a centralized SIEM for query.
- Monitor outbound mail queues for password-reset emails destined for domains that do not match the historical primary administrator address.
- Track administrative login sources and flag geolocation or ASN changes on user_id=1.
How to Mitigate CVE-2026-100371
Immediate Actions Required
- Upgrade InvoicePlane to a build that includes commit 8616fa4 or later.
- Audit the ip_users table for unexpected values in user_email, user_type, or user_active on user_id=1.
- Force a password reset for the primary administrator and rotate any API tokens tied to that account.
- Review the secondary administrator roster and revoke accounts that no longer require elevated access.
Patch Information
The issue is patched via commit 8616fa4. The fix replaces the current_user_id === 1 check with Mdl_Users::is_primary_administrator(), enforces object-level authorization in Users::form(), and expands the locked-field set to include user_email and user_password through the new PRIMARY_ADMIN_LOCKED_FIELDS constant. Review the InvoicePlane Security Advisory GHSA-77hm-22wp-96wp and the GitHub Pull Request #1638 discussion for background.
Workarounds
- Restrict the users module to the primary administrator by placing web server ACLs on /users/form/* until patching is complete.
- Reduce all user_type=1 accounts to non-administrative roles where operationally feasible.
- Disable the public password-recovery endpoint temporarily and process resets manually via database access.
# Configuration example: nginx location block restricting user edits to trusted IPs
location ~ ^/users/(form|change_password)/ {
allow 10.0.0.0/24;
deny all;
try_files $uri /index.php?$args;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

