Opened 3 weeks ago
Last modified 3 weeks ago
#37213 assigned Bug
`ForeignKeyRawIdWidget` renders labels for related objects outside the form field's queryset
| Reported by: | Natalia Bidart | Owned by: | Vismay |
|---|---|---|---|
| Component: | contrib.admin | Version: | 6.0 |
| Severity: | Normal | Keywords: | ForeignKeyRawIdWidget formfield_for_foreignkey not-security |
| Cc: | Vismay | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The Security Team received this report and concluded this is worth a public hardening:
ForeignKeyRawIdWidget.label_and_url_for_value() resolves the submitted value through the related model's default manager:
self.rel.model._default_manager.using(db).get(**{key: value})
This ignores the queryset assigned to the bound form field. When a ModelAdmin narrows a raw-id foreign key via formfield_for_foreignkey() (for example, restricting the queryset per request), the widget still renders the __str__() label and admin change link for any valid primary key, including values outside that narrowed queryset.
The admin add view seeds initial form data from request.GET, so a staff user with add permission on the referencing model can pass an arbitrary related-object primary key in the query string and see the rendered label and change URL, even for objects the field's queryset would reject on submit. Form validation still rejects the value on POST, and the related model's admin views still enforce their own permissions (the change view returns 403), so this is limited to the label and URL rendered at GET time.
The widget's display path should be consistent with the field's queryset: resolve the value through the bound field's queryset rather than the model's default manager, and omit the label and link when the value falls outside it (matching the existing behavior for a missing primary key).
Thanks Jaeyoung Jang for the report.
Change History (6)
comment:2 by , 3 weeks ago
| Triage Stage: | Unreviewed → Accepted |
|---|
comment:4 by , 3 weeks ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:5 by , 3 weeks ago
| Cc: | added |
|---|---|
| Has patch: | set |
comment:6 by , 3 weeks ago
Opened PR: https://github.com/django/django/pull/21612
I reproduced the issue locally and added a regression test in admin_widgets (it fails without the fix). The PR makes ForeignKeyRawIdWidget resolve the label and change link through the form field's queryset instead of the model's default manager, so values outside a narrowed queryset (e.g. set via formfield_for_foreignkey()) are no longer exposed.
To reproduce, using these from the admin tests:
Reproduction, given a staff user Alex with only
add_cartire, owning Car #1, and Robin owning Car #2: