A server-side request forgery (SSRF) vulnerability was found in galaxyng, the Ansible Galaxy server plugin for Pulp. An authenticated user with namespace management permissions can set a namespace avatar URL to an arbitrary address, including internal networks, loopback, or cloud instance metadata endpoints. A background worker fetches that URL without checking the destination, which lets the attacker probe internal services and enumerate reachable IP addresses. The HTTP client is also configured without an overall timeout, so a slow or non-responsive target can pin workers and cause a denial of service.
galaxyng is a Pulp plugin powering Ansible Galaxy and Ansible Automation Platform Private Automation Hub. A server-side request forgery (SSRF) exists in the namespace avatar download path.
In galaxyng/app/api/v3/serializers/namespace.py, avatarurl is a DRF URLField. That field validates URL syntax and scheme (http/https) only. It does not resolve the hostname or filter private, loopback, link-local, or cloud-metadata addresses. On namespace create, downloadlogo is always True. On update, downloadlogo is True whenever avatarurl is present. The serializer then dispatches a Pulp task that calls downloadavatar() in galaxyng/app/tasks/namespaces.py.
downloadavatar() builds an aiohttp session with ClientTimeout(total=None, sockconnect=600, sockread=600) and passes the user-supplied URL to pulpcore HttpDownloader with no address restriction and no redirect-target revalidation. The Pulp worker therefore requests whatever address the publisher supplied, including RFC1918, 127.0.0.0/8, 169.254.0.0/16, and cloud metadata endpoints.
The SSRF is blind: the response is stored only if it passes image validation (PIL ImageField or SVG). Non-image bodies are discarded, so this is not direct credential exfiltration. The attacker still gets a binary oracle via namespace avatarsha256 (updated vs unchanged), which is enough to enumerate reachable internal IPs and ports from the worker's network. The total=None timeout also lets a slow or hanging target pin a worker, which is a denial-of-service angle.
Privilege required is an authenticated user with galaxy.changenamespace or galaxy.addnamespace. In community Galaxy that includes a user's own namespace.
Affected: galaxyng (verified on current main, HEAD 2bc7189 at clone time). No upstream fix identified in the examined tree. Reporter: Arpit Jain (GitHub arpitjain099), independent security researcher. PSIRT ticket: PSIRTSUPT-22101 Distinct from GHSA-hc3x-6pf5-pmj3 (legacy role import path).
A command injection vulnerability was found in galaxyng's legacy role import functionality. The dogitcheckout() function in galaxyng/app/api/v1/tasks.py constructs shell commands via f-string interpolation using unsanitized git ref names (branch/tag names from the githubreference parameter) and executes them with subprocess.run(cmd, shell=True). An authenticated user who controls a git repository can create a branch or tag with shell metacharacters (such as ;, |, $(), &, >) in the name, and when the legacy role import processes this reference, the shell metacharacters are interpreted by the shell, achieving remote code execution on the pulp worker process.
The vulnerability requires GALAXYENABLELEGACYROLES to be set to True, which is NOT the default configuration in any shipped version of Red Hat Ansible Automation Platform (2.4 through 2.6). When this setting is False (the default), the v1 API routes are not registered in Django URL routing and the vulnerable endpoint returns 404. However, any deployment that explicitly enables legacy role support (e.g., community-galaxy profile or custom configurations) is exposed to authenticated RCE.
A secondary vector exists via the alternatecloneurl parameter, which accepts arbitrary URLs with no validation (enabling SSRF), though the git clone operation on that path uses shell=False, preventing shell injection via that specific parameter.