Bug 2511901 (CVE-2026-71365)

Summary: CVE-2026-71365 awx: webhook status callback SSRF leaks the Git PAT
Product: [Other] Security Response Reporter: OSIDB Bzimport <bzimport>
Component: vulnerabilityAssignee: Product Security <prodsec-ir-bot>
Status: NEW --- QA Contact:
Severity: high Docs Contact:
Priority: high    
Version: unspecifiedCC: dschmidt, jlanda, kshier, security-response-team, simaishi, stcannon, teagle, yguenane
Target Milestone: ---Keywords: Security
Target Release: ---   
Hardware: All   
OS: Linux   
Whiteboard:
Fixed In Version: Doc Type: ---
Doc Text:
A server-side request forgery (SSRF) vulnerability was found in AWX's webhook status callback mechanism. When processing GitHub pull request webhooks, AWX extracts the status callback URL (pull_request.statuses_url) from the incoming webhook payload without validating the target host against the expected Git provider. This URL is persisted in job extra variables and later used to send authenticated status updates. A user with admin role on a webhook-enabled job template can read the template's webhook signing key, forge a signed GitHub webhook payload with an arbitrary statuses_url, and cause AWX to POST status updates to an attacker-controlled or internal URL. The status update request includes the configured Git Personal Access Token (PAT) in the Authorization header, resulting in credential leakage to the attacker-specified endpoint.
Story Points: ---
Clone Of: Environment:
Last Closed: Type: ---
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:

Description OSIDB Bzimport 2026-08-06 04:59:41 UTC
For GitHub pull_request webhooks, get_event_status_api() in awx/api/views/webhooks.py returns pull_request.statuses_url from the request body verbatim without any host validation:

def get_event_status_api(self):
    if self.get_event_type() != 'pull_request':
        return
    return self.request.data.get('pull_request', {}).get('statuses_url')

The value is persisted into job extra_vars and later used by update_webhook_status() in awx/main/models/mixins.py:

status_api = self.extra_vars_dict.get('awx_webhook_status_api')
headers = {k: v.format(self.webhook_credential.get_input('token')), 'Content-Type': 'application/json'}
response = requests.post(status_api, data=json.dumps(data), headers=headers, timeout=30)

There is no host allowlist for the expected Git provider endpoint and no private-network egress filtering before the request is sent. The webhook receiver relies on an HMAC secret (webhook_key), which is readable by users with the admin role on the job template via /api/v2/job_templates/{id}/webhook_key/. This allows a template admin to forge a signed webhook payload with statuses_url pointing to an attacker-controlled endpoint, and AWX will POST status updates including the Git PAT in the Authorization header to that endpoint.

Comment 2 errata-xmlrpc 2026-08-24 16:26:22 UTC
This issue has been addressed in the following products:

  Red Hat Ansible Automation Platform 2.5 for RHEL 9
  Red Hat Ansible Automation Platform 2.5 for RHEL 8

Via RHSA-2026:59135 https://access.redhat.com/errata/RHSA-2026:59135

Comment 3 errata-xmlrpc 2026-08-24 16:27:02 UTC
This issue has been addressed in the following products:

  Red Hat Ansible Automation Platform 2.6 for RHEL 9
  Red Hat Ansible Automation Platform 2.6 for RHEL 10

Via RHSA-2026:59136 https://access.redhat.com/errata/RHSA-2026:59136