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)
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
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).
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