Where
-Infinity
0
Severity
6.4
AV:N/AC:L/PR:L/UI:N/S:C/C:N/I:L/A:L

A flaw was found in Ansible Automation Platform's automation-controller (AWX). The Bulk Job Launch API (POST /api/v2/bulk/joblaunch/) authorizes the requested instancegroups with only a read-level permission check, whereas the standard single-job launch path requires use-level permission on the same field. A principal that holds read (but not use) permission on an instance group -- for example the built-in read-only System Auditor role -- together with execute permission on a job template can launch bulk jobs onto instance groups they are not authorized to use, bypassing execution-placement isolation.

1 / 2
Source: MITRE
First published (updated )
Severity
7

A flaw was found in automation-controller (AWX). In WorkflowJobTemplateNodeAccess (awx/main/access.py), canchange() delegates the execute-permission check on a node's unifiedjobtemplate to ujtexecute(), which returns True unconditionally when the node's current obj.unifiedjobtemplate is None, without ever validating the incoming data['unifiedjobtemplate'] against executerole. canadd() does not mark unifiedjobtemplate as mandatory, so a node can be created with unifiedjobtemplate=null (HTTP 201). PATCHing that empty node to any UnifiedJobTemplate id then succeeds (HTTP 200) with no executerole check on the new value. The direct-create path (POST a node with unifiedjobtemplate set) is correctly denied (403) via canadd -> checkrelated(rolefield= 'executerole'); the create-empty-then-PATCH sequence bypasses that guard. Consequently a user with adminrole on any single WorkflowJobTemplate (an organization admin, an Organization Workflow Admin, or an object-level WFJT admin) can bind and execute any Job Template, Project update, Inventory Source sync, System Job Template, or nested Workflow Job Template belonging to any other organization/tenant -- even ones for which direct GET/launch returns 403 -- running the victim template with the victim's attached credentials, inventory and project. The PATCH response summaryfields additionally leaks the victim template's name and description. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.x (victim WFJT executed end-to-end, launchedby attacker); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py: WorkflowJobTemplateNodeAccess.ujtexecute / canadd / canchange; checkrelated both-None handling).

First published (updated )
Severity
7

A flaw was found in automation-controller (AWX). In WorkflowJobTemplateNodeAccess (awx/main/access.py), canchange() delegates the execute-permission check on a node's unifiedjobtemplate to ujtexecute(), which returns True unconditionally when the node's current obj.unifiedjobtemplate is None, without ever validating the incoming data['unifiedjobtemplate'] against executerole. canadd() does not mark unifiedjobtemplate as mandatory, so a node can be created with unifiedjobtemplate=null (HTTP 201). PATCHing that empty node to any UnifiedJobTemplate id then succeeds (HTTP 200) with no executerole check on the new value. The direct-create path (POST a node with unifiedjobtemplate set) is correctly denied (403) via canadd -> checkrelated(rolefield= 'executerole'); the create-empty-then-PATCH sequence bypasses that guard. Consequently a user with adminrole on any single WorkflowJobTemplate (an organization admin, an Organization Workflow Admin, or an object-level WFJT admin) can bind and execute any Job Template, Project update, Inventory Source sync, System Job Template, or nested Workflow Job Template belonging to any other organization/tenant -- even ones for which direct GET/launch returns 403 -- running the victim template with the victim's attached credentials, inventory and project. The PATCH response summaryfields additionally leaks the victim template's name and description. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.x (victim WFJT executed end-to-end, launchedby attacker); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py: WorkflowJobTemplateNodeAccess.ujtexecute / canadd / canchange; checkrelated both-None handling).

First published (updated )
Severity
4

A flaw was found in automation-controller (AWX). GET /api/controller/v2/hosts/ accepts a hostfilter query parameter that HostList.getqueryset (awx/api/views/ init.py:1938-1944) parses with SmartFilter.queryfromstring and intersects with the RBAC-filtered host queryset via qs &= filterqs; that intersection restricts only which Host rows are returned, not which related rows the lookup's JOINs traverse. SmartFilter.BoolOperand (awx/main/utils/filters.py:151-170) validates the user-supplied lookup PATH only by calling FieldLookupBackend(). getfieldfromlookup(Host, k) and then executes Host.objects.filter({k: v}) directly, including regex and icontains lookups. FieldLookupBackend (django- ansible-base ansiblebase/restfilters/utils.py:9-69) rejects only fields in PASSWORDFIELDS/encryptedfields, fields marked preventsearch, and traversal loops -- it performs no authorization check on the rows the relation traversal reaches. JobEvent.eventdata/stdout and AdHocCommandEvent.eventdata/stdout (awx/main/models/events.py:215,253,695,705) are not wrapped in preventsearch(), unlike extravars, jobenv, jobargs and surveypasswords which are. Consequently a user with only the Read role on an inventory can submit hostfilter values such as lastjobjobeventseventdataregex=<pattern>, jobeventsasprimaryhoststdouticontains=<substring>, or adhoccommandeventseventdataregex=<pattern> and read the returned host count as a one-bit oracle. Since regex is accepted, this allows efficient character-by- character extraction of the eventdata/stdout of jobs and ad-hoc commands the caller is explicitly denied (HTTP 403) via the direct API, for any job that ever ran against a host in a readable inventory -- including jobs in other organizations when a host spans inventories across organizations. Job output routinely contains plaintext credentials, API tokens, command output and host facts. This is a horizontal privilege escalation from inventory-read to read-arbitrary-job-output. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation- controller 4.8.1 (the forbidden job's playbookuuid was extracted exactly); still present on devel. Upstream: github.com/ansible/awx (awx/api/views/init.py HostList.getqueryset; awx/main/utils/filters.py SmartFilter.BoolOperand; sensitive fields in awx/main/models/events.py) and github.com/ansible/django-ansible-base (ansiblebase/restfilters/utils.py getfieldsfrompath).

First published (updated )
Severity
4

A flaw was found in automation-controller (AWX). Write-only survey password values stored on Schedules and WorkflowJobTemplateNodes are encrypted at rest and masked as $encrypted$ on read. LaunchConfigurationBaseSerializer.validate (awx/api/serializers.py) replaces an incoming $encrypted$ with the stored DB ciphertext and revalidates prompts against the job template's current surveyspec; SurveyJobTemplateMixin.acceptorignorevariables (awx/main/models/mixins.py) decrypts the stored password before validation, and surveyelementvalidation interpolates the decrypted plaintext into the "value ... is too small/too large" min/max error for text/textarea/password questions. The error dict is returned verbatim as the HTTP 400 body. A user holding only the delegated JobTemplate Admin role can POST a tightened surveyspec (e.g. "max":1) and then PATCH a schedule of that job template -- either echoing $encrypted$ for the variable, or simply re-stating unifiedjobtemplate to force full-prompt revalidation without knowing the variable name (serializers.py forces full revalidation when unifiedjobtemplate is present) -- and read the stored plaintext password of a schedule created by a different, higher-privileged user (ScheduleAccess.canchange grants a JT admin write on all schedules of the template regardless of creator). The same base serializer backs WorkflowJobTemplateNodeSerializer, so workflow nodes are equally affected. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1; still present on devel. Upstream: github.com/ansible/awx (api/serializers.py LaunchConfigurationBaseSerializer; main/models/mixins.py surveyelementvalidation / acceptorignorevariables; main/access.py ScheduleAccess.canchange)

First published (updated )
Severity
7
SSRF

A flaw was found in automation-controller (AWX). The external credential test endpoints execute the credential plugin backend in-process in the controller web service (awx/api/views/init.py CredentialExternalTest / CredentialTypeExternalTest -> callbackendwitherrorhandling -> plugin.backend()). CredentialExternalTest uses objpermissiontype='use' and merges request-body 'inputs' over the stored credential inputs, so a user with only the use role can override serverurl. The Thycotic Secret Server plugin (awx-plugins src/awxplugins/credentials/tss.py tssbackend) passes serverurl verbatim into the delinea/thycotic SDK, which issues GET {serverurl}/api/v1/ healthcheck and GET {serverurl}/health and, on a healthy response, POST {serverurl}/oauth2/token, with no allow-list, scheme, IP-range, or DNS-rebind check. Errors are wrapped as a bare SecretServerError (blind), but latency differs by target state (refused ~0.5s vs filtered ~12s until the gateway 503s), yielding a reliable timing oracle for scanning in-cluster services, the Kubernetes API, link-local metadata (169.254.169.254), and localhost daemons from the control-plane pod. This is the same root-cause class as the previously reported HashiCorp Vault / Centrify / CyberArk CCP credential-plugin SSRFs; Thycotic Secret Server is an additional sink. Discovered internally; verified live on AAP 2.6/2.7 / automation-controller 4.8.1; still present on devel (both awx and awx-plugins). Upstream: github.com/ansible/awx (awx/api/views/init.py CredentialExternalTest, CredentialTypeExternalTest, callbackendwitherrorhandling); github.com/ansible/awx-plugins (src/awxplugins/credentials/tss.py tssbackend)

First published (updated )
Severity
4

A flaw was found in automation-controller (AWX). In awx/api/serializers.py, BulkJobLaunchSerializer.validate() authorizes the instancegroups many-to-many field via checklistpermission(InstanceGroup, ...) with no action argument, which the helper interprets as a read-level check (user.getqueryset(model)). The equivalent single-job launch path (awx/main/access.py, JobLaunchConfigAccess.canadd) checks the same field at use level (useinstancegroup / userole) and raises HTTP 403 on failure. The bulk view enforces only IsAuthenticated, so the under-scoped serializer check is the sole authorization for instance-group placement. Because user.getqueryset(InstanceGroup) returns all instance groups for a System Auditor (and any read-visible group for other users), a caller with execute on a job template and read (not use) on an instance group can POST to /api/v2/bulk/joblaunch/ and have the job actually placed on that group, bypassing execution-placement isolation. The maintainers' own inline comments ("# TODO: change to userole for conflict" and "duplicated with BulkJobLaunchSerializer, check when changing permission levels") mark the gap. Inventory and Credential fields on the same bulk path are correctly checked at use level; instancegroups is the outlier. Upstream: github.com/ansible/awx (serializers.py BulkJobLaunchSerializer) Present at: tag 24.6.1 (commit 94e5795) and devel HEAD

First published (updated )

A flaw was found in automation-controller (AWX). InventoryInputInventoriesList (awx/api/views/inventory.py) validates only that the attached inventory is not itself constructed, and InventoryAccess.canattach (awx/main/access.py) special- cases only the instancegroups relationship (requiring userole); the inputinventories relationship falls through to BaseAccess.canattach, which requires only canchange on the constructed inventory and read access on the source inventory -- not userole. Consequently a user who administers a constructed inventory in one organization and holds only Inventory Read on an inventory in another organization (for example an organization auditor, or a system auditor with any inventory-admin org) can POST to /api/controller/v2/inventories/{constructedid}/inputinventories/ to attach the foreign inventory; the constructed inventory sync then clones every host and host variable (including secrets) into the attacker's inventory, and the attacker -- as admin of the constructed inventory -- can launch ad hoc shell commands against those hosts or bind the inventory to a job template. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1 (a read-only user attached a foreign inventory (204), cloned a secret host variable, and had an ad hoc shell command accepted and dispatched against the foreign host); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py InventoryAccess. canattach / BaseAccess.canattach; awx/api/views/inventory.py InventoryInputInventoriesList)

First published (updated )

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203