Unintended Deployments to PyPI Servers

Summary

On June 15, 2021 an exploitable vulnerability in the deployment tooling for PyPI was discovered by a PyPI administrator.

This vulnerability allowed for arbitrary code which passed the continuous integration suite to be deployed to the servers that run PyPI without approval or merge to the warehouse codebase.

Two instances of unmerged and unapproved changes being deployed were discovered:

In both cases, there was no malicious intent and the changes would later be approved and merged by PyPI administrators.

In review and audit, PyPI administrators were able to confirm that no other actors attempted or succeeded in initiating an unapproved deployment.

Analysis

The root cause of this vulnerability was misinterpretation of the check_suite event from GitHub. Initially it was thought that the value for repository->full_name in the payload was the repository from which the commit under test originated, when in actuality it is the repository in which the check suite ran.

installation_id = hook.payload['installation']['id']
repository_name = hook.payload['repository']['full_name']
branch_names = [hook.payload['check_suite']['head_branch']]

applications = Application.query.filter(and_(
    Application.auto_deploy_branch.in_(branch_names),
    Application.github_app_installation_id == installation_id,
    Application.github_repository == repository_name,
)).all()

When filtering the repository name and branch to determine if a deployment was required, as above, this allowed for any Pull Request opened against the repository originating from any branch called main to initiate a deploy as long as the continuous integration run succeeded.

Mitigation

Because the payload of the check_suite hook does not contain the necessary information to determine the original repository to which the branch and commit belong, our deployment tooling began processing push events.

The push event is only fired for branches belonging to the repository, but can be further verified by checking the value of hook.payload['repository']['full_name'] and hook.payload['ref'] to ensure that it originated from the authentic warehouse repository.

push events which could potentially initiate a deployment are marked as such, in this case that they originate from the specific repository and branch configured (pypa/warehouse:main).

All further check_suite events are filtered on wether or a not an associated push event was marked as deployable.

This was validated via a test Pull Request. No deployment was initiated until after merge.

Audit

The deployment tooling for PyPI keeps a full history of all inbound hooks it has received, and the actions taken after processing.

In review, we were able to identify the two unintended deployments using this log and review them. No other instances, malicious or accidental, of this were observed.

Timeline

  • 2020-08-21 Deployment tooling updated to use the check_suite hook rather than status hook from GitHub to initiate deploys.
  • 2021-03-17 First instance (PR #9245) of unintentional deploy
  • 2021-06-15 Second instance (PR #9669) of unintentional deploy
  • 2021-06-15 PyPI Administrator alerts team to suspicious deployment notifications on PR #9669
  • 2021-06-15 Deployment tooling for PyPI disabled
  • 2021-06-15 Fix developed and tested
  • 2021-06-15 Deployment tooling for PyPI re-enabled