August 9, 2026
IDOR via Comma-Injection: How Concatenating Two IDs Leaked Cross-Tenant PII
TL;DR: A permissions endpoint expected a single numeric user ID in one POST parameter. Sending two IDs separated by a comma — victim first…

By s0ufm3l
1 min read
TL;DR: A permissions endpoint expected a single numeric user ID in one POST parameter. Sending two IDs separated by a comma — victim first, then a valid ID from my own tenant — bypassed the ownership check entirely. The backend processed the request and both added a user I wasn't authorized to touch and returned their full name, email, and internal tenant ID in the response.
The Feature
The target app is a document-vault: folders, files and there are ACLs (Access Control Lists) applied to them . Folder owners can grant other users Read, Write, or FullControl permissions through a "Permissions" panel.
Under the hood, every action in this panel goes through a single JSON-RPC-style endpoint:
POST /rpc/api.php
Content-Type: application/json
{
"method": "addObjectAccess",
"data": [{
"data": [{
"user_group_id": 1012,
"name": "firstName lastName",
...
}],
"id": "..."
}],
"type": "rpc",
"tid": 55
}POST /rpc/api.php
Content-Type: application/json
{
"method": "addObjectAccess",
"data": [{
"data": [{
"user_group_id": 1012,
"name": "firstName lastName",
...
}],
"id": "..."
}],
"type": "rpc",
"tid": 55
}user_group_id is the field that tells the backend which user to add and apply an ACL to it .
First Attempt: Blocked (as expected)
The obvious test is: can I add a user who isn't in my tenant ?
"user_group_id": "victim_id""user_group_id": "victim_id"Response: Access Denied. Good — there's a server-side ownership check confirming the target user is visible to / owned by the requesting tenant.
The Bypass:
I tried to see how the server is behaving when I send special characters in the "user_group_id", and I noticed that the response is different when sending a comma in the parameter, and then instead of sending a single ID, I sent two IDs joined by a comma — the victim's ID first, followed by a legitimate user ID, the order of the IDs is important as I noticed it seems that the server is only checking the last parameter if it belong to my tenant or not:
"user_group_id": "victim_id,tenant_user_id""user_group_id": "victim_id,tenant_user_id"And this was accepted.
Confirming the Leak
After sending the crafted addObjectAccess request, I called the companion method, getObjectAcl, to read back the folder's ACL:
{
"method": "getObjectAcl",
"data": [{ "id": "FOLDER_ID" }],
"tid": 589
}{
"method": "getObjectAcl",
"data": [{ "id": "FOLDER_ID" }],
"tid": 589
}The response now included a full ACL entry for the victim account — someone with no relationship to my tenant at all:
{
"id": "512",
"name": "V...",
"first_name": "V...",
"last_name": "...",
"email": "v...@othercompany-example.com",
"tenant_id": "184",
"group_type": "Provider",
"user_group_id": "512"
}{
"id": "512",
"name": "V...",
"first_name": "V...",
"last_name": "...",
"email": "v...@othercompany-example.com",
"tenant_id": "184",
"group_type": "Provider",
"user_group_id": "512"
}Full name, email address, and internal tenant ID for a user in a completely different organization — retrievable by anyone who could guess or enumerate a valid user_id and had at least one folder with FullControl permissions in their own tenant.
Impact
- Confidentiality: Disclosure of PII (full name, email, internal tenant/user ID) belonging to users in unrelated tenants.
- Integrity: Ability to add arbitrary external users to a folder's ACL, bypassing tenant isolation .
Takeaways for Bug Bounty
- When a parameter takes a single ID and gets rejected for an unauthorized value, don't stop — try:
- comma/semicolon/pipe-delimited multi-value injection
- arrays (
["victim_id"],{"id": ["a","b"]})