Логотип exploitDog
Консоль
Логотип exploitDog

exploitDog

github логотип

GHSA-8fq9-273g-6mrg

Опубликовано: 17 июн. 2026
Источник: github
Github: Прошло ревью
CVSS3: 9.6

Описание

Avo: Missing Authorization in Avo Association Attach Endpoint Allows Unauthorized Relationship Manipulation and Privilege Escalation

Summary

A critical missing authorization flaw exists in Avo's association attach workflow. The UI and GET /resources/:resource/:id/:related/new path can check attach_<association>?, but the actual write endpoint, POST /resources/:resource/:id/:related, does not run the same authorization check before mutating the association.

As a result, an authenticated low-privileged Avo user can bypass hidden/disabled attach controls and directly attach related records to a parent record by sending a crafted POST request. In applications where associations represent teams, tenants, roles, projects, users, memberships, ownership, or other authorization-bearing relationships, this can lead to privilege escalation and cross-tenant data exposure.

Details

The association attach route writes relationships through Avo::AssociationsController#create:

# config/routes.rb post "/:resource_name/:id/:related_name", to: "associations#create", as: "associations_create"

The controller registers an attach authorization callback only for new, not for create:

# app/controllers/avo/associations_controller.rb before_action :set_attachment_record, only: [:create, :destroy] before_action :authorize_index_action, only: :index before_action :authorize_attach_action, only: :new before_action :authorize_detach_action, only: :destroy

The new action is only the form-rendering step. The actual mutation happens in create:

def create if create_association create_success_action else create_fail_action end end

create_association then attaches the attacker-supplied related record to the parent:

def create_association association_name = BaseResource.valid_association_name(@record, association_from_params) perform_action_and_record_errors do if through_reflection? && additional_params.present? new_join_record.save elsif has_many_reflection? || through_reflection? @record.send(association_name) << @attachment_record else @record.send(:"#{association_name}=", @attachment_record) @record.save! end end end

The only attach-specific authorization helper is:

def authorize_attach_action authorize_if_defined "attach_#{@field.id}?" end

Because this helper is bound only to new, a policy that denies attach_users?, attach_teams?, attach_roles?, or similar methods blocks the UI/form path but does not protect the write path.

This is inconsistent with the detach path, which does authorize the mutating destroy action:

before_action :authorize_detach_action, only: :destroy

The bug is especially dangerous because Avo already treats association authorization as an access-control boundary in UI components:

# lib/avo/concerns/checks_assoc_authorization.rb method_name = :"#{policy_method}_#{association_name}?".to_sym if service.has_method?(method_name, raise_exception: false) service.authorize_action(method_name, record:, raise_exception: false) else !Avo.configuration.explicit_authorization end

However, server-side enforcement is missing on the actual attach POST endpoint.

Proof of Concept

Prerequisites:

  1. A Rails application mounts Avo, for example at /admin.
  2. Avo authorization is enabled.
  3. A low-privileged user can authenticate to Avo.
  4. A parent record and a related record are both reachable by ID.
  5. The relevant policy denies attaching the relationship, for example:
def attach_users? false end

Example target scenario:

  • Parent resource: projects
  • Parent ID: 1
  • Related association: users
  • Related user ID to attach: 42
  • Expected policy: low-privileged users must not be able to attach users to projects.

The UI/form request may be blocked:

GET /admin/resources/projects/1/users/new

But the direct write endpoint can still be invoked:

POST /admin/resources/projects/1/users Content-Type: application/x-www-form-urlencoded authenticity_token=<CSRF>&fields[related_id]=42

Run the attached PoC:

python poc_avo_association_attach_bypass.py \ --base-url http://localhost:3000 \ --avo-root /admin \ --cookie "_app_session=<LOW_PRIVILEGED_SESSION_COOKIE>" \ --parent-resource projects \ --parent-id 1 \ --related-name users \ --related-id 42 \ --check-new

If GET /new is forbidden or redirected but the direct POST succeeds, the authorization bypass is confirmed.

To perform the actual attach:

python poc_avo_association_attach_bypass.py \ --base-url http://localhost:3000 \ --avo-root /admin \ --cookie "_app_session=<LOW_PRIVILEGED_SESSION_COOKIE>" \ --parent-resource projects \ --parent-id 1 \ --related-name users \ --related-id 42 \ --confirm-attach

Expected vulnerable result:

  • The low-privileged user can attach the related record despite attach_<association>? being denied.
  • The parent record now includes the related record.

Impact

This vulnerability allows unauthorized relationship manipulation through Avo.

Depending on the affected association, the impact can include:

  • Privilege escalation by attaching a user to an admin group, privileged project, tenant, organization, role, or membership record.
  • Cross-tenant data exposure when tenant/user/project membership determines record visibility.
  • Integrity loss by changing ownership, assignment, access-control relationships, or business workflow state.
  • Policy bypass even when Avo UI controls correctly hide the attach button or deny the attach form.

Recommended Fix

Enforce attach authorization on the mutating endpoint.

At minimum:

before_action :authorize_attach_action, only: [:new, :create]

Additionally:

  1. Authorize against the parent record and the selected related record before writing the relationship.
  2. Ensure create fails closed when attach_<association>? is missing and explicit_authorization is enabled.
  3. Add regression tests that directly POST to /resources/:resource_name/:id/:related_name while attach_<association>? returns false.
  4. Verify has_many, has_one, has_many :through, and has_and_belongs_to_many association paths all enforce the same server-side authorization.

Пакеты

Наименование

avo

rubygems
Затронутые версииВерсия исправления

<= 3.32.0

3.32.1

Наименование

avo

rubygems
Затронутые версииВерсия исправления

>= 4.0.0.beta.1, < 4.0.0.beta.51

4.0.0.beta.51

EPSS

Процентиль: 38%
0.00448
Низкий

9.6 Critical

CVSS3

Дефекты

CWE-639
CWE-862
CWE-863

Связанные уязвимости

CVSS3: 9.6
nvd
2 месяца назад

Avo is a framework to create admin panels for Ruby on Rails apps. Prior to 3.32.1 and 4.0.0.beta.51, Avo's association attach workflow checks attach_<association>? in the UI and GET /resources/:resource/:id/:related/new path, but the actual write endpoint, POST /resources/:resource/:id/:related, does not run the same authorization check before mutating the association through Avo::AssociationsController#create. An authenticated low-privileged Avo user can bypass hidden or disabled attach controls and directly attach related records to a parent record by sending a crafted POST request, which can lead to privilege escalation and cross-tenant data exposure where associations represent authorization-bearing relationships. This issue is fixed in versions 3.32.1 and 4.0.0.beta.51.

EPSS

Процентиль: 38%
0.00448
Низкий

9.6 Critical

CVSS3

Дефекты

CWE-639
CWE-862
CWE-863