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

A flaw was found in the Ansible Automation Platform automation-controller notification subsystem. CustomEmailBackend (awx/main/notifications/emailbackend.py:24) is a thin subclass of django.core.mail.backends.smtp.EmailBackend and does not override open()/sendmessages() to validate its connect target, so the user-controlled notificationconfiguration.host and .port reach smtplib.SMTP(host, port, timeout=...) with no allow-list and no private/loopback/ link-local/reserved rejection. An organization-scoped Notification Admin (awx.notificationadminrole, checked by NotificationTemplateAccess.canadd/canchange at awx/main/access.py:2578/2582 — a delegatable, non-superuser role) can create or PATCH an email notification template with host/port pointing at any internal address and POST the template's /test/ endpoint. The dispatcher runs the backend on the controller-task pod, which opens a raw TCP connection to the attacker-chosen host:port. The smtplib exception string is persisted verbatim into Notification.error (a plain TextField, awx/main/models/notifications.py:219) and is readable over the API, producing a three-state oracle: "[Errno 111] Connection refused" (closed), "timed out" (filtered), and "Connection unexpectedly closed: timed out" or an SMTPResponseException (open). Because the transport is a raw socket, the flaw reaches non-HTTP internal services (for example Redis, PostgreSQL, receptor) and the in-cluster Kubernetes API, and can send SMTP-protocol bytes into those listeners. In addition, the SMTP AUTH exchange transmits the template's stored, otherwise write-only password to the configured host, so a user who can change the host on a shared organization template can exfiltrate its stored SMTP password. The HTTP-based notification backends (webhook, grafana, mattermost, rocketchat) were hardened against this on the 2.7 branch via awx/main/notifications/urlvalidation.py::validateurl(); the email backend (and the IRC backend) were omitted from that hardening. That guard is present only on the 2.7 branch; on the 2.5, 2.6, and development branches it is absent and no notification backend validates its connect target.

Upstream: https://github.com/ansible/tower (awx) Affected file: awx/main/notifications/emailbackend.py:24 (CustomEmailBackend, no host validation); awx/main/models/notifications.py:219 (Notification.error oracle sink); awx/main/access.py:2559-2582 (notificationadminrole). Guard (2.7 only): awx/main/notifications/urlvalidation.py::validateurl (added by tower #7875).

1 / 2
Source: Red Hat
First published (updated )
Severity
5.3
AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

A flaw was found in the Ansible Automation Platform automation-controller webhook receivers. The Bitbucket Data Center webhook receiver at POST /api/controller/v2/{jobtemplates,workflowjobtemplates}/<pk>/bitbucketdc/ is intentionally unauthenticated (permissionclasses=(AllowAny,), authenticationclasses=()) and authorizes requests by verifying an HMAC over the request body keyed by the per-template webhookkey. BitbucketDcWebhookReceiver.mustchecksignature() returns False when the X-Event-Key request header is 'diagnostics:ping', because Bitbucket does not sign ping requests. This carve-out is evaluated only after the receiver has already resolved the target template: getobject() filters templates to those with webhookservice='bitbucketdc' and a non-empty webhookkey and raises PermissionDenied (HTTP 403) when no template matches, whereas a matching template proceeds past the skipped signature check to the ping short-circuit and returns HTTP 200 with the body "Webhook ignored". As a result, an unauthenticated remote attacker who sends a diagnostics:ping request to each template ID observes a 200-vs-403 response discrepancy that reveals exactly which Job Template and Workflow Job Template IDs have Bitbucket DC webhooks configured, with no credentials and no knowledge of the webhookkey. This is unauthenticated reconnaissance: it confirms valid template IDs, reveals which automation is wired to Bitbucket Data Center, and lets an attacker focus subsequent webhookkey brute-force or SCM-side spoofing attempts on the small set of templates that would actually accept signed payloads. The ping path performs no action and no secret values are disclosed.

Upstream: https://github.com/ansible/tower (awx) Affected file: awx/api/views/webhooks.py:306-308 (BitbucketDcWebhookReceiver.mustchecksignature), with webhooks.py:69-78 (getobject) and :144-146 (post ping short-circuit)

1 / 2
Source: Red Hat
First published (updated )

A flaw was found in automation-controller (AWX). Project.scmbranch (awx/main/models/credential path awx/main/models/projects.py:98-111, a CharField with no validators) and scmrefspec are accepted as free text with no rejection of a leading dash (no validatescmbranch/validatescmrefspec and no leading-dash guard anywhere in awx/api/serializers.py, awx/main, or awx/playbooks). During project sync, RunProjectUpdate.buildextravarsfile (awx/main/tasks/jobs.py:1481-1518) copies scmbranch (:1486/:1508) and scmrefspec (:1517-1518) verbatim into the project-update extravars, and awx/playbooks/projectupdate.yml (- hosts: localhost, connection: local, :28-30) passes them to the ansible.builtin.git module as version: "{{ scmbranch | quote }}" (:50) and refspec: "{{ scmrefspec | default(omit) }}" (:51). The git module's switchversion() builds [/usr/bin/git, checkout, --force, <version>] with no -- end-of-options sentinel (upstream lib/ansible/modules/git.py), so a value beginning with -- is parsed by git as an option flag. The | quote filter is ansible's shlex.quote and does NOT defend against this: an all-shell-safe string such as --pathspec-from-file=/etc/passwd is returned unchanged (matching the live proof, whose command line shows no quotes). Setting scmbranch to --pathspec-from-file=/var/run/secrets/kubernetes.io/serviceaccount/token makes git read the file line-by-line as pathspecs and echo each line on stderr as error: pathspec '<line>' did not match any file(s) known to git; ansible-runner captures stderr into the job event stream, returned via GET /api/controller/v2/projectupdates/{id}/stdout/?format=txt — an arbitrary-file-read oracle on the sync host. Because projectupdate.yml runs on localhost and, on the tested OpenShift Operator deployment, that host is the automation-controller-task control-plane pod (confirmed: the leaked token's pod name matches a control node in /api/controller/v2/ping/), the read reaches control-plane secrets: the system:serviceaccount:ansible-automation-platform:automation-controller JWT (pod/secret CRUD in the AAP namespace -> full platform takeover), /etc/tower/SECRETKEY (offline decryption of every stored Credential), and /etc/tower/conf.d/.py (PostgreSQL credentials). Any user able to create or edit a project (Organization projectadmin, or Project Admin on a single project) can trigger this on project create/edit auto-sync; system administrator is not required, and any org's admin reads secrets governing all tenants on the control plane. scmrefspec (fed to git fetch, no quote at all) is a second option-injection sink to fix together. On deployments where project updates run in an isolated execution environment without control-plane secrets, the primitive is limited to that context. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.1; still present on devel (fix prepared, not merged) Upstream: github.com/ansible/awx (awx/main/models/projects.py scmbranch/scmrefspec; awx/api/serializers.py Project serializers; awx/main/tasks/jobs.py RunProjectUpdate.buildextravarsfile; awx/playbooks/projectupdate.yml) and github.com/ansible/ansible (ansible.builtin.git switchversion -- add --).

First published (updated )
Severity
4

A flaw was found in automation-controller (AWX). POST/PATCH /api/controller/v2/credentialtypes/ validates the injectors JSON by RENDERING every string under injectors.{env,file,extravars} through a Jinja2 sandbox synchronously in the uWSGI web request worker (awx/main/fields.py:748-785, CredentialTypeInjectorField.validate -> validatetemplatestring: sandbox.ImmutableSandboxedEnvironment(undefined=StrictUndefined).fromstring(tmpl).render(...) at line 750). The sandbox blocks server-side-template-injection code-execution gadgets (class, globals, |attr('...'), str.format dunder walk — all verified rejected live and consistent with ImmutableSandboxedEnvironment), so this is not remote code execution and has no confidentiality or integrity impact. However, nothing bounds total iteration count, output size, or wall-clock time: nested {% for %} loops multiply freely and string multiplication is unrestricted, so a single request can render billions of iterations in-process. Live timing on the target scaled linearly (90,000 iterations 0.62s; 18,000,000 iterations 1.22s; 72,000,000 iterations 3.71s), proving the render runs synchronously in automation-controller-web; a nested-loop request of roughly 8.1e9 iterations exceeds the uWSGI harakiri timeout and kills the worker, and a number of concurrent requests at or above the worker count drives every web worker into a kill/respawn loop, taking the Controller API and UI (job launch, callbacks, inventory sync, RBAC, UI) offline for as long as the attack is sustained. Separately, the validation try/except catches only UndefinedError, SecurityError, and TemplateSyntaxError (fields.py:751/757/759); a loader-less {% extends %} or {% include %} raises TypeError('no loader for this environment specified'), and large string operations raise OverflowError — neither is caught, so the exception propagates as a raw Django 500 error page (verified live), a robustness defect and a second confirmation that attacker templates execute in the web process. The endpoint is restricted to superusers (awx/main/access.py:1078-1098 CredentialTypeAccess with no canadd override -> BaseAccess.canadd returns user.issuperuser), so this is a defense-in-depth / availability hardening issue rather than a privilege boundary break. There is no wall-clock, iteration, or output bound anywhere around the render on the devel branch (no MAXRANGE or timeout present). This is the automation-controller sibling of the already-reported EDA credential-types injector-validation denial of service and the Controller notificationtemplates message-validation denial of service, in a distinct code path (awx.main.fields.CredentialTypeInjectorField) reached from a distinct endpoint. Discovered internally (pentest-aap); verified live on AAP 2.7 / automation-controller 4.8.1; still present on devel (fix prepared, not merged). Upstream: github.com/ansible/awx (awx/main/fields.py CredentialTypeInjectorField.validate / validatetemplatestring).

First published (updated )
Severity
7.6
AV:N/AC:L/PR:H/UI:N/S:C/C:L/I:H/A:N

A flaw was found in Ansible Automation Platform's automation-controller. Custom Credential Types let an author define injectors.env (environment variables set in the execution environment when a credential of that type is attached to a job) and injectors.file (files rendered into the execution environment whose path is exposed to other injectors as {{ tower.filename }}). The env-variable names are validated only by a deny-list: CredentialTypeInjectorField.validateenvvarallowed (awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE" and names present in the ENVBLOCKLIST frozenset (awx/main/constants.py:48-70). The stated purpose of this control is to stop injectors from hijacking the runner process (it blocks PATH, PYTHONPATH, VIRTUALENV and all ANSIBLE configuration variables). The deny-list is incomplete: it does not include BASHENV, ENV, LDPRELOAD, LDLIBRARYPATH, LDAUDIT, PYTHONSTARTUP, PYTHONWARNINGS, GITSSHCOMMAND, PERL5OPT, or similar loader/ process-hijacking variables, so those names pass validation. An attacker can therefore define a credential type whose file injector writes a shell script and whose env injector sets BASHENV to {{ tower.filename }}. Every non-interactive bash process spawned during a job (Ansible modules shell out constantly) then sources and executes the attacker's script, yielding arbitrary code execution inside the execution-environment container — independent of the playbook content — for any job that attaches a credential of the malicious type, with access to the secrets of all co-attached credentials in the same job environment and to any inventory host the job can reach. The same weak check is repeated at the runtime injection sink (awxplugins.interfaces temporaryprivateinjectapi.py, which re-checks only ENVBLOCKLIST), so the fix must be applied in both places. Creating a custom credential type requires Controller superuser (CredentialTypeAccess inherits BaseAccess), so this is primarily a defense-in-depth bypass of a control whose explicit purpose is to constrain exactly this behavior; however, an organization-level Credential Admin (not a superuser) can weaponize an already-existing malicious custom type, and in Gateway-managed AAP 2.5+ the platform-admin role is explicitly not host/EE root, so code execution in the execution environment via a configuration API crosses a real trust boundary.

Upstream: https://github.com/ansible/tower (private) / awxplugins.interfaces Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70; awxplugins/interfaces/temporaryprivateinjectapi.py:263-288

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

A flaw was found in Ansible Automation Platform's automation-controller. Custom Credential Types let an author define injectors.env (environment variables set in the execution environment when a credential of that type is attached to a job) and injectors.file (files rendered into the execution environment whose path is exposed to other injectors as {{ tower.filename }}). The env-variable names are validated only by a deny-list: CredentialTypeInjectorField.validateenvvarallowed (awx/main/fields.py:689-701) rejects names beginning with "ANSIBLE" and names present in the ENVBLOCKLIST frozenset (awx/main/constants.py:48-70). The stated purpose of this control is to stop injectors from hijacking the runner process (it blocks PATH, PYTHONPATH, VIRTUALENV and all ANSIBLE configuration variables). The deny-list is incomplete: it does not include BASHENV, ENV, LDPRELOAD, LDLIBRARYPATH, LDAUDIT, PYTHONSTARTUP, PYTHONWARNINGS, GITSSHCOMMAND, PERL5OPT, or similar loader/ process-hijacking variables, so those names pass validation. An attacker can therefore define a credential type whose file injector writes a shell script and whose env injector sets BASHENV to {{ tower.filename }}. Every non-interactive bash process spawned during a job (Ansible modules shell out constantly) then sources and executes the attacker's script, yielding arbitrary code execution inside the execution-environment container — independent of the playbook content — for any job that attaches a credential of the malicious type, with access to the secrets of all co-attached credentials in the same job environment and to any inventory host the job can reach. The same weak check is repeated at the runtime injection sink (awxplugins.interfaces temporaryprivateinjectapi.py, which re-checks only ENVBLOCKLIST), so the fix must be applied in both places. Creating a custom credential type requires Controller superuser (CredentialTypeAccess inherits BaseAccess), so this is primarily a defense-in-depth bypass of a control whose explicit purpose is to constrain exactly this behavior; however, an organization-level Credential Admin (not a superuser) can weaponize an already-existing malicious custom type, and in Gateway-managed AAP 2.5+ the platform-admin role is explicitly not host/EE root, so code execution in the execution environment via a configuration API crosses a real trust boundary.

Upstream: https://github.com/ansible/tower (private) / awxplugins.interfaces Affected files: awx/main/fields.py:689-701; awx/main/constants.py:48-70; awxplugins/interfaces/temporaryprivateinjectapi.py:263-288

First published (updated )
Severity
4

A flaw was found in automation-controller (AWX). Binding a Credential to a resource is a use-level action because the credential's secrets are decrypted and consumed at run time; the product enforces userole on credential foreign keys throughout (ProjectAccess, CredentialInputSourceAccess, InstanceGroupAccess, job- launch and workflow-node paths). However ExecutionEnvironmentAccess.canadd and canchange (awx/main/access.py:1316-1338) validate only the organization FK (via executionenvironmentadminrole) and never call checkrelated on the credential FK, and ExecutionEnvironmentSerializer.validatecredential (serializers.py: 1327-1330) only enforces kind == 'registry' with no RBAC. Consequently a non- superuser holding Organization ExecutionEnvironment Admin (plus Member) in one organization can POST or PATCH /api/controller/v2/executionenvironments/ with a credential id referencing any Container-Registry credential in any other organization, including one for which direct GET returns 403. When a job runs with that execution environment, awx/main/tasks/jobs.py:320-327 calls cred.getinput('password'), decrypting the foreign credential's plaintext registry password into containerauthdata (podman authfile); for container-group tasks awx/main/tasks/receptor.py builds a Kubernetes imagePullSecret from the same plaintext. This crosses the tenant boundary and discloses another organization's registry credentials. A related instance affects Project.signaturevalidation credential, a second credential foreign key ProjectAccess does not use-check. Discovered internally (vanilla-harness); verified live on AAP 2.7 / automation- controller 4.8.1; still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py: ExecutionEnvironmentAccess.canadd / canchange; contrast ProjectAccess; sink awx/main/tasks/jobs.py containerauthdata).

First published (updated )
Severity
7

A flaw was found in automation-controller (AWX). Four compounding issues let a holder of object-level NotificationTemplate admin recover the plaintext of notification credentials for fixed-endpoint backends: (1) NotificationTemplate.displaynotificationconfiguration() (awx/main/models/ notifications.py) masks only password subfields of the CURRENT notification type, so after a type change the previous type's password subfield is returned with its full Fernet ciphertext; (2) NotificationTemplateSerializer.validate() (awx/api/ serializers.py) returns early when notificationconfiguration is absent from the request body, allowing a type-only change that keeps the stale ciphertext; (3) NotificationTemplate.save() skips re-encryption of any value beginning with "$encrypted$" and the serializer's forward sentinel tests exact equality with "$encrypted$", so a full ciphertext replanted into a different password subfield is stored verbatim; (4) getencryptionkey() (awx/main/utils/encryption.py) derives the Fernet key from the secret key, primary key, and field name only -- not the subfield -- so a ciphertext produced for one subfield decrypts as any other subfield of the same object. Chaining these, an attacker switches the template type to leak the ciphertext, replants it into a webhook password with an attacker-controlled URL, and triggers /test/; the controller decrypts and sends the plaintext to the attacker in an HTTP Basic auth header. Verified live on AAP 2.7 / automation-controller 4.8.1; still present on devel. Upstream: github.com/ansible/awx (awx/main/models/notifications.py; awx/api/serializers.py; awx/main/utils/encryption.py; awx/main/notifications/webhookbackend.py)

First published (updated )
Severity
7

A flaw was found in automation-controller (AWX). Four compounding issues let a holder of object-level NotificationTemplate admin recover the plaintext of notification credentials for fixed-endpoint backends: (1) NotificationTemplate.displaynotificationconfiguration() (awx/main/models/ notifications.py) masks only password subfields of the CURRENT notification type, so after a type change the previous type's password subfield is returned with its full Fernet ciphertext; (2) NotificationTemplateSerializer.validate() (awx/api/ serializers.py) returns early when notificationconfiguration is absent from the request body, allowing a type-only change that keeps the stale ciphertext; (3) NotificationTemplate.save() skips re-encryption of any value beginning with "$encrypted$" and the serializer's forward sentinel tests exact equality with "$encrypted$", so a full ciphertext replanted into a different password subfield is stored verbatim; (4) getencryptionkey() (awx/main/utils/encryption.py) derives the Fernet key from the secret key, primary key, and field name only -- not the subfield -- so a ciphertext produced for one subfield decrypts as any other subfield of the same object. Chaining these, an attacker switches the template type to leak the ciphertext, replants it into a webhook password with an attacker-controlled URL, and triggers /test/; the controller decrypts and sends the plaintext to the attacker in an HTTP Basic auth header. Verified live on AAP 2.7 / automation-controller 4.8.1; still present on devel. Upstream: github.com/ansible/awx (awx/main/models/notifications.py; awx/api/serializers.py; awx/main/utils/encryption.py; awx/main/notifications/webhookbackend.py)

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 )
Severity
4

A flaw was found in automation-controller (AWX). OrganizationGalaxyCredentialsList (awx/api/views/organization.py) validates only that the attached credential's kind is galaxyapitoken, and OrganizationAccess.canattach (awx/main/access.py) has no branch for the galaxycredentials relationship, so it falls through to BaseAccess.canattach, which requires only canchange on the organization (org admin) and read access on the credential -- not userole. Consequently a user who administers any one organization and holds only read on a Galaxy/Hub credential in another organization (for example a system/platform auditor) can POST to /api/controller/v2/organizations/{id}/galaxycredentials/ to bind that foreign credential. RunProjectUpdate.buildenv (awx/main/tasks/jobs.py) then iterates organization.galaxycredentials, decrypts each token, and exports it as ANSIBLEGALAXYSERVERSERVERnTOKEN so ansible-galaxy authenticates to the credential owner's Hub URL. The token is masked in the API jobenv, but it is used server-side on the attacker's behalf against the victim's Hub -- a read-only role gains cross-tenant use of another org's secret. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1 (a platform auditor attached a foreign org's credential (204) and a subsequent project sync exported the victim Hub URL + masked token); still present on devel. Upstream: github.com/ansible/awx (awx/main/access.py OrganizationAccess. canattach / BaseAccess.canattach; awx/api/views/organization.py OrganizationGalaxyCredentialsList; awx/main/tasks/jobs.py RunProjectUpdate.buildenv)

First published (updated )
Severity
7

A flaw was found in automation-controller (AWX). InventorySourceOptions. sourcevars (awx/main/models/inventory.py) is declared as a plain TextField without a preventsearch() wrapper, unlike protected fields such as CustomInventoryScript.script and Credential.inputs. django-ansible-base's FieldLookupBackend permits contains/icontains/startswith/regex lookups that traverse model joins without per-hop RBAC. Because the credentialtypes list endpoint is readable by any authenticated user, an attacker holding zero roles/organization/superuser/auditor privileges -- who is correctly denied on the inventorysources endpoints -- can query credentialtypes?credentialsorganizationinventoriesinventorysources sourcevarscontains=<needle> (and the inventoryupdates variant) and read the result count as a boolean oracle (count>0 iff some tenant's sourcevars contains the needle). Using regex the full plaintext is recovered efficiently. sourcevars is the documented location for inline dynamic- inventory-plugin authentication (e.g. awsaccesskey/awssecretkey, subscriptionid/clientid/secret/tenant, username/password, token, apikey), so the flaw discloses cross-tenant cloud-provider credentials to a zero- privilege user. The field is defined on InventorySourceOptions and inherited by InventorySource and InventoryUpdate. This is an instance of a broader class in which unfiltered FieldLookupBackend joins on the globally-readable credentialtypes list reach fields lacking preventsearch. Discovered internally; verified live on AAP 2.7 / automation-controller 4.8.1. Upstream: github.com/ansible/awx (main/models/inventory.py) + django-ansible-base (FieldLookupBackend)

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