- Регистрация
- 20.01.2011
- Сообщения
- 7,665
- Розыгрыши
- 0
- Реакции
- 135
Hello world
A workflow is a configurable automated process that will run one or more jobs. Workflows are defined with YAML files and will run when triggered by an event in a repository, manually, or at a defined schedule.Workflows are defined in the .github/workflows directory of a repository, and one can have multiple workflows, each of which can perform a different set of tasks. For example, it is possible to have a workflow to build and test pull requests, another to deploy an application every time a release is created, and yet another workflow to add a label every time someone opens a new issue.
A workflow must contain the following basic components:
- One or more events that will trigger the workflow.
- One or more jobs, each of which will execute on a runner machine and run a series of one or more steps.
- Each step can either run a defined script or run an action, which is a reusable extension that can simplify a workflow.
Here is a Hello world workflow:
Код:
name: Hello world
on:
push:
jobs:
hello:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Hello world"
In the previous example the configured trigger is push, meaning on every push, the workflow will be triggered:
An event is a specific activity in a repository that triggers a workflow run. For example, activity can originate from GitHub when someone creates a pull request, opens an issue, or pushes a commit to a repository. It's also possible to trigger a workflow to run on a schedule or manually. Some event can be dangerous and can result in vulnerabilities, more on this in the next articles.
A job represents a series of tasks within a workflow, all executed on the same runner. These tasks can either be shell scripts or actions. The execution of steps is sequential, with each step relying on the completion of the previous one. As all steps share the same runner, data can be transparently passed from one to the next. For instance, you might first build your application in one step and then test the built application in the subsequent step.
Job dependencies can be configured, allowing coordination with other jobs. By default, jobs operate independently and run concurrently. When a job is dependent on another, it waits for the completion of the dependent job before initiating. Consider a scenario with multiple build jobs for diverse architectures without dependencies, and a packaging job dependent on those builds. The build jobs execute simultaneously, and upon successful completion, the packaging job follows suit.
It is possible to use this flexibility to create custom actions tailored to specific needs or leverage existing actions available in the GitHub Marketplace, thus enhancing the efficiency and simplicity of workflows.
A runner acts as the server responsible for executing workflows upon triggering. Each runner is capable of running one job at a time. GitHub extends support for various operating systems, including Ubuntu Linux, Microsoft Windows, and macOS runners, enabling workflows to run on newly provisioned virtual machines for each execution.
GitHub also offers the possibility to use self-hosted runners. This could be interesting in some cases as if attackers manage to compromise a self-hosted runner they might be able to access internal networks.
GitHub context
Contexts serve as a mean to retrieve information regarding various aspects of workflow runs, including variables, runner environments, jobs, and steps. Each context is an object that contains properties, which can be strings or other objects. This mechanism provides a structured way to access and utilize diverse information within the context of a workflow run.Contexts can be accessed using the following expression syntax:
Код:
${{ <context> }}
Код:
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: echo ${{ github.repository }}
Some contexts are interesting from an attacker perspective:
- env: contains environment variables set in a workflow, job, or step.
- secrets: contains the names and values of secrets that are available to a workflow run.
- github: information about the workflow run.
- steps: information about the steps that have been run in the current job.
- needs: contains the outputs of all jobs that are defined as a dependency of the current job.
The following workflow is affected by a script injection vulnerability:
Код:
name: Issue
on:
issues:
jobs:
hello:
runs-on: ubuntu-latest
steps:
- run: |
echo "New issue: ${{ github.event.issue.title }}"
Due to the way that the ${{}} gets expanded, this causes the workflow to execute the following command:
Код:
echo "New issue: $(id)"
Given that expression evaluation in GitHub Actions is language-independent, the risk of injection is not confined to Bash scripts. The ${{}} syntax, extends to other languages, such as JavaScript, where a syntactically valid construct could also be exploited for injection purposes.
This vulnerability opens the door for potential malicious activities. Attackers leveraging this injection could therefore execute actions with more severe consequences, such as uploading sensitive secrets to a website under their control, introducing new code to the repository, introducing backdoor vulnerabilities or initiating a supply chain attack. More details regarding this vulnerability in the following articles.
GitHub secrets
GitHub Actions allow developers to store secrets at three different places:- At the organization level, either globally or for selected repositories (only available for GitHub organizations).
- Per repository.
- Per repository for a specific environment.
Код:
22: steps:
23: - name: Check out repository
24: uses: actions/checkout@v3
25: with:
26: ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
Workflow permissions
At the beginning of each workflow job, GitHub automatically creates a unique GITHUB_TOKEN secret for jobs to authenticate to GitHub.Upon enabling GitHub Actions, a GitHub App is automatically installed in the repository. The GITHUB_TOKEN secret corresponds to an access token specific to this GitHub App install. Using this installation access token allows authentication on behalf of the GitHub App residing in the repository. The token's permissions are restricted to the repository containing the workflow.
Before each job begins, GitHub fetches an installation access token for the job. The GITHUB_TOKEN expires when a job finishes or after a maximum of 24 hours.
In this example the tokens will be different in each job but not in each step:
Код:
name: permissions
on:
push:
env:
TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
job1:
runs-on: ubuntu-latest
steps:
- name: step1
run: |
echo "${TOKEN:0:9}[...]"
- name: step2
run: |
echo "${TOKEN:0:9}[...]"
job2:
runs-on: ubuntu-latest
steps:
- run: |
echo "${TOKEN:0:9}[...]"
The token is available in the github.token and secrets.GITHUB_TOKEN contexts.
The default configuration grants the GITHUB_TOKEN read-only permission to the repository. This was not always the default setting, as it was modified at the close of 2022. Prior to this change, the token possessed both read and write permissions for the repository. However, GitHub did not enforce this alteration, resulting in all GitHub organizations created before 2023 providing default write access to the GITHUB_TOKEN. This behavior proves advantageous during exploitation, and configuration options exist at both the organization and repository levels to tailor these settings:
Depending on the trigger, the GITHUB_TOKEN will have different permissions. This will be detailed in the next chapter.
Permissions can also be restricted directly in the workflow file to adjust the default access granted to the GITHUB_TOKEN, either by adding or removing access as needed. This ensures that only the essential access required is granted. Permissions are defined at either the top level, applying them to all jobs in the workflow, or within specific jobs. When a specific permissions key is set to a particular job, all actions and run commands in that job using the GITHUB_TOKEN will inherit the specified access rights.
The following permissions can be defined:
Код:
permissions:
actions: read|write|none
checks: read|write|none
contents: read|write|none
deployments: read|write|none
id-token: read|write|none
issues: read|write|none
discussions: read|write|none
packages: read|write|none
pages: read|write|none
pull-requests: read|write|none
repository-projects: read|write|none
security-events: read|write|none
statuses: read|write|none
Код:
permissions: read-all
permissions: write-all
Код:
permissions: {}
Код:
permissions:
pull-requests: read
Interesting permissions are:
- contents: Work with the contents of the repository. For example, contents: read permits an action to list the commits, and contents:write allows the action to create a release.
- id-token: Fetch an OpenID Connect (OIDC) token.
- pull-requests: Work with pull requests. For example, pull-requests: write permits an action to add a label to a pull request.
- issues: Work with issues. For example, issues: write permits an action to add a comment to an issue.
First time contributors
Forking a public repository allows anyone to propose changes to the GitHub Actions workflows by submitting a pull request. While workflows from forks are restricted from accessing sensitive data like secrets, they can pose a challenge for maintainers if they are altered for malicious purposes.In order to mitigate this potential issue, workflows triggered by pull requests from certain external contributors to public repositories may not run automatically and could require approval. By default, initial approval is necessary for all first-time contributors before their workflows can be executed. This precautionary measure is implemented to enhance security and prevent potential misuse of workflows.
This will result in the workflow being paused until someone approves it:
This restriction can be easily bypassed. An attacker could first make an innocent pull request fixing a legitimate bug or typo in the documentation. If this pull request gets accepted then the next workflows will automatically run.
Workflow triggers
As previously explained GitHub workflows can be triggered using different events. Some are particularly interesting as they can provide a privileged context to an external attacker. This section describes some triggers.PUSH
The push trigger is the most commonly used trigger. It runs a workflow when a commit or tag is pushed. However, from an attacker perspective this is not useful since to trigger such events, an attacker would need write privileges on the targeted repository which will not be the initial assumption for this research. Every attack proposed in this paper will be exploited from an external attacker that does not have any privilege on the targeted repository.PULL_REQUEST
The pull_request trigger will run a workflow when activity on a pull request in the repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated.
Код:
on:
pull_request:
types: [opened, reopened]
Код:
on:
pull_request:
# this will not work
permissions:
contents: write
Код:
name: "PR"
on:
pull_request:
jobs:
init:
runs-on: ubuntu-latest
name: "init"
steps:
- run: |
echo "Secret value: ${{ secrets.SUPER_SECRET }}"
Yet, there are instances where the necessity arises to execute a workflow triggered by diverse pull request events, demanding not only read, but also write access to the repository, or access to its secrets. Consider a scenario where a workflow aims to apply labels to a pull request based on certain properties. Such a workflow requires a GITHUB_TOKEN with write privileges on at least the pull-request permission. This kind of event is covered by the pull_request_target trigger as explained in the next section.
PULL_REQUEST_TARGET
As the pull_request trigger, the pull_request_target trigger will run a workflow when activity on a pull request in the repository occurs. For example, if no activity types are specified, the workflow runs when a pull request is opened or reopened or when the head branch of the pull request is updated.However, in this case the GITHUB_TOKEN is granted read/write repository permission unless the permissions key is specified and the workflow can access secrets, even when it is triggered from a fork. This makes it a very interesting trigger as it can be triggered from a fork and still has access to sensitive elements. Particular attention must be paid to this type of workflow to prevent malicious users to exploit weaknesses and gain access to sensitive information or tokens.
With a trigger configured on the previous workflow, an attacker could gain access to the defined secret:
Код:
name: "PR"
on:
pull_request_target:
jobs:
init:
runs-on: ubuntu-latest
name: "init"
steps:
- run: |
echo "Secret value: ${{ secrets.SUPER_SECRET }}"
The secret is hidden in the output log to prevent any leak, but we can observe that the token is passed to the runner.
This trigger is even more dangerous as it is not subject to the first time contributors protection. An external attacker could trigger a pull_request_target workflow without first being a contributor.
WORKFLOW_RUN
The workflow_run event takes place when a workflow run is either requested or completed. It enables the execution of a workflow based on the initiation or conclusion of another one. Notably, the workflow triggered by the workflow_run event has the capability to access secrets and write tokens, even if the preceding one did not possess such privileges. This is interesting in situations where the initial workflow intentionally lacks privileges, but subsequent privileged actions are required in a later workflow.It is possible to access the workflow_run event payload associated with the workflow that triggered the target one. As an illustration, if the triggering workflow generates artifacts, a workflow initiated by the workflow_run event can retrieve and utilize these artifacts as needed. This feature enables smooth interaction and data sharing between workflows, enhancing flexibility and extensibility in GitHub Actions.
For example this workflow is provided in the GitHub documentation as an example:
name: Upload data
Код:
on:
pull_request:
jobs:
upload:
runs-on: ubuntu-latest
steps:
- name: Save PR number
env:
PR_NUMBER: ${{ github.event.number }}
run: |
mkdir -p ./pr
echo $PR_NUMBER > ./pr/pr_number
- uses: actions/upload-artifact@v3
with:
name: pr_number
path: pr/
Код:
name: Use the data
on:
workflow_run:
workflows: [Upload data]
types:
- completed
jobs:
download:
runs-on: ubuntu-latest
steps:
- name: 'Download artifact'
uses: actions/github-script@v6
with:
script: |
[...]
By default, the workflow triggered by the workflow_run event has the same capability as the triggering one. Since an attacker can only control workflows triggered with the pull_request event the resulting workflow will not have write privileges on the different permissions.
For example, here we have a workflow configured to run when a workflow called trigger is launched:
If an external user creates the trigger workflow the GITHUB_TOKEN associated in the pr.yml workflow will have write privileges on the repository even if the triggering one did not:
Note that this event will only trigger a run if the workflow file is on the default branch.
GitHub artifacts
Workflow artifacts serve as a mechanism to retain data beyond the completion of a job, facilitating data sharing among different jobs within workflows. An artifact, in this context, refers to a file or a group of files generated during the execution of a workflow. This functionality proves particularly useful for preserving outputs such as build and test results after the conclusion of a workflow run.For example:
Код:
- name: upload artifact
uses: actions/upload-artifact@v3
with:
name: artifact-name
path: artifact.txt
Importantly, all actions and workflows invoked within a run possess write access to the artifacts associated with that specific run, ensuring seamless access and utilization of shared data. Also, any external user could generate and store artifacts in the targeted repository. This means that a workflow triggered by a workflow_run event could download arbitrary data controlled by an external attacker. In some cases, this can lead to critical vulnerabilities.
Conclusion
In this first article we explored the mechanics of GitHub Actions, we expose the different elements that are present in a GitHub workflow. Some of them will play a crucial role when it comes to exploitation. The next articles will be focused on the exploitation part. We will detail several critical misconfigurations that we found during our research on several popular open-source projects.Для просмотра ссылки Войди