# [CRIT] [GHSA / CRITICAL] CVE-2026-47413: praisonai-platform: Any workspace member can add arbitrary user as owner via POST /workspaces/{id}/members

**Source:** GitHub Security Advisories
**Published:** 2026-06-01
**Article:** https://github.com/advisories/GHSA-8g2p-pqm3-fcfh

## Threat Profile

praisonai-platform: Any workspace member can add arbitrary user as owner via POST /workspaces/{id}/members

## Summary

**Type:** Privilege escalation / cross-tenant member injection. The `POST /workspaces/{workspace_id}/members` endpoint is gated only by `require_workspace_member(workspace_id)` (default `min_role="member"`) and forwards the request body's `user_id` and `role` straight into `MemberService.add(workspace_id, user_id, role)`, which has no caller-permission check. A user with the lo…

## Indicators of Compromise (high-fidelity only)

- **CVE:** `CVE-2026-47413`

## MITRE ATT&CK Techniques

- **T1190** — Exploit Public-Facing Application
- **T1204.002** — User Execution: Malicious File
- **T1068** — Exploitation for Privilege Escalation
- **T1078.004** — Valid Accounts: Cloud Accounts
- **T1078** — Valid Accounts
- **T1098** — Account Manipulation
- **T1556** — Modify Authentication Process

## Kill chain phases observed

_(none detected from narrative keywords)_

## Recommended hunts

### praisonai-platform: POST /workspaces/*/members with role=owner (CVE-2026-47413)

`UC_155_2` · phase: **exploit** · confidence: **High** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=true count min(_time) as first_seen max(_time) as last_seen from datamodel=Web.Web where Web.http_method="POST" Web.url="*/workspaces/*/members*" Web.status=201 by Web.src Web.user Web.url Web.http_user_agent Web.dest
| `drop_dm_object_name(Web)`
| where first_seen > relative_time(now(), "-7d")
```

**Defender KQL:**
```kql
// Defender Advanced Hunting does not natively ingest praisonai-platform HTTP logs.
// If the app is fronted by an MDA-monitored proxy that ships to CloudAppEvents, pivot there:
CloudAppEvents
| where Timestamp > ago(7d)
| where ActionType in~ ("HttpRequest","ApiCall")
| where RawEventData has "/workspaces/" and RawEventData has "/members"
| where RawEventData has_any ("\"role\":\"owner\"","role=owner")
| project Timestamp, AccountDisplayName, AccountObjectId, IPAddress, UserAgent, ObjectName, RawEventData
| order by Timestamp desc
```

### praisonai-platform: identity-swap chain — owner grant followed by login from the granted account

`UC_155_3` · phase: **actions** · confidence: **Medium** · AI-generated for this article

**Splunk SPL (CIM):**
```spl
| tstats summariesonly=true count from datamodel=Web.Web where Web.http_method="POST" Web.url="*/workspaces/*/members*" Web.status=201 by _time Web.src Web.user Web.url
| `drop_dm_object_name(Web)`
| rename _time as grant_time, src as grant_src, user as grantor, url as grant_url
| join type=inner grant_src [
    | tstats summariesonly=true count from datamodel=Authentication.Authentication where Authentication.action="success" by _time Authentication.src Authentication.user
    | `drop_dm_object_name(Authentication)`
    | rename _time as login_time, src as grant_src, user as new_user
  ]
| where login_time >= grant_time and login_time <= grant_time + 3600 and new_user != grantor
| table grant_time login_time grant_src grantor new_user grant_url
```

**Defender KQL:**
```kql
// Pivots on CloudAppEvents if the praisonai app is API-instrumented to MDA.
let GrantWindow = 1h;
let Grants = CloudAppEvents
    | where Timestamp > ago(24h)
    | where RawEventData has "/workspaces/" and RawEventData has "/members"
    | where RawEventData has_any ("\"role\":\"owner\"","role=owner")
    | project GrantTime = Timestamp, GrantorIp = IPAddress, GrantorObjectId = AccountObjectId, GrantUrl = ObjectName, GrantRaw = RawEventData;
CloudAppEvents
| where Timestamp > ago(24h)
| where ActionType in~ ("Logon","LogIn","UserLoggedIn")
| join kind=inner Grants on $left.IPAddress == $right.GrantorIp
| where Timestamp between (GrantTime .. GrantTime + GrantWindow)
| where AccountObjectId != GrantorObjectId
| project GrantTime, LoginTime = Timestamp, DelaySec = datetime_diff('second', Timestamp, GrantTime), GrantorIp, GrantorObjectId, NewIdentity = AccountObjectId, NewIdentityName = AccountDisplayName, GrantUrl
| order by GrantTime desc
```

### Article-specific behavioural hunt — [GHSA / CRITICAL] CVE-2026-47413: praisonai-platform: Any workspace member can a

`UC_155_1` · phase: **exploit** · confidence: **High**

**Splunk SPL (CIM):**
```spl
``` Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47413: praisonai-platform: Any workspace member can a ```
| tstats `summariesonly` count earliest(_time) AS firstTime latest(_time) AS lastTime
    from datamodel=Endpoint.Processes
    where (Processes.process_name IN ("member_service.py","deps.py"))
    by Processes.dest, Processes.user, Processes.process_name,
       Processes.process, Processes.parent_process_name, Processes.process_path
| `drop_dm_object_name(Processes)`
| `security_content_ctime(firstTime)`
| append [
| tstats `summariesonly` count
    from datamodel=Endpoint.Filesystem
    where Filesystem.action IN ("created","modified")
      AND (Filesystem.file_name IN ("member_service.py","deps.py"))
    by Filesystem.dest, Filesystem.user, Filesystem.process_name,
       Filesystem.file_path, Filesystem.file_name
| `drop_dm_object_name(Filesystem)`
]
```

**Defender KQL:**
```kql
// Article-specific bespoke detection — [GHSA / CRITICAL] CVE-2026-47413: praisonai-platform: Any workspace member can a
// Hunts the actual binaries / paths / commandline fragments named
// in the article instead of a generic technique-class template.
DeviceProcessEvents
| where Timestamp > ago(30d)
| where (FileName in~ ("member_service.py", "deps.py"))
| project Timestamp, DeviceName, AccountName, FileName,
          FolderPath, ProcessCommandLine,
          InitiatingProcessFileName, InitiatingProcessCommandLine
| order by Timestamp desc

// File-creation events for the named binaries / paths
DeviceFileEvents
| where Timestamp > ago(30d)
| where ActionType in ("FileCreated","FileModified")
| where (FileName in~ ("member_service.py", "deps.py"))
| project Timestamp, DeviceName, AccountName, FolderPath,
          FileName, ActionType, InitiatingProcessFileName,
          InitiatingProcessCommandLine
| order by Timestamp desc
```

### IOC-driven hunts (use shared templates)

These are standard IOC-substitution hunts — the canonical SPL and KQL live once in [`_TEMPLATES.md`](../_TEMPLATES.md), so we don't repeat the same boilerplate on every CVE / hash / network-IOC briefing.

- **Asset exposure — vulnerability matches article CVE(s)** ([template](../_TEMPLATES.md#asset-exposure)) — phase: **recon**, confidence: **High**
  - CVE(s): `CVE-2026-47413`


## Why this matters

Severity classified as **CRIT** based on: CVE present, 4 use case(s) fired, 7 technique(s) inferred. Read the full article for actor attribution, tooling details, and any defanged IOCs in the body that aren't visible in the RSS summary.
