Skip to content

Commit a69fa5c

Browse files
author
Alvaro Muñoz
authored
Merge pull request #62 from github/actions_download_artifact
feat(queries): Add actions/download-artifact as a source of Artifact Poisoning
2 parents 80d2bbd + d548aef commit a69fa5c

File tree

7 files changed

+98
-4
lines changed

7 files changed

+98
-4
lines changed

ql/lib/codeql/actions/security/ArtifactPoisoningQuery.qll

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,21 @@ abstract class UntrustedArtifactDownloadStep extends Step {
1818

1919
class GitHubDownloadArtifactActionStep extends UntrustedArtifactDownloadStep, UsesStep {
2020
GitHubDownloadArtifactActionStep() {
21-
// By default, the permissions are scoped so they can only download Artifacts within the current workflow run.
22-
// To elevate permissions for this scenario, you can specify a github-token along with other repository and run identifiers
2321
this.getCallee() = "actions/download-artifact" and
24-
this.getArgument("run-id").matches("%github.event.workflow_run.id%") and
25-
exists(this.getArgument("github-token"))
22+
(
23+
// By default, the permissions are scoped so they can only download Artifacts within the current workflow run.
24+
// To elevate permissions for this scenario, you can specify a github-token along with other repository and run identifiers
25+
this.getArgument("run-id").matches("%github.event.workflow_run.id%") and
26+
exists(this.getArgument("github-token"))
27+
or
28+
// There is an artifact upload step in the same workflow which can be influenced by an attacker on a checkout step
29+
exists(UsesStep checkout, UsesStep upload |
30+
this.getEnclosingWorkflow().getAJob().(LocalJob).getAStep() = checkout and
31+
checkout.getCallee() = "actions/checkout" and
32+
checkout.getAFollowingStep() = upload and
33+
upload.getCallee() = "actions/upload-artifact"
34+
)
35+
)
2636
}
2737

2838
override string getPath() {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: elevate
2+
on:
3+
- pull_request_target
4+
5+
jobs:
6+
job1:
7+
runs-on: ubuntu-latest
8+
permissions:
9+
contents: read
10+
steps:
11+
- uses: actions/checkout@v4
12+
with:
13+
ref: ${{ github.event.pull_request.head.sha }}
14+
- run: |
15+
bash script.sh
16+
- uses: actions/upload-artifact@v4
17+
with:
18+
name: results
19+
path: results
20+
retention-days: 1
21+
22+
job2:
23+
runs-on: ubuntu-latest
24+
needs: job1
25+
permissions:
26+
contents: write
27+
steps:
28+
- uses: actions/download-artifact@v4
29+
with:
30+
name: results
31+
- run: python test.py
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: elevate
2+
on:
3+
- pull_request
4+
5+
jobs:
6+
job1:
7+
runs-on: ubuntu-latest
8+
permissions:
9+
contents: read
10+
steps:
11+
- uses: actions/checkout@v4
12+
with:
13+
ref: ${{ github.event.pull_request.head.sha }}
14+
- run: |
15+
bash script.sh
16+
- uses: actions/upload-artifact@v4
17+
with:
18+
name: results
19+
path: results
20+
retention-days: 1
21+
22+
job2:
23+
runs-on: ubuntu-latest
24+
needs: job1
25+
permissions:
26+
contents: write
27+
steps:
28+
- uses: actions/download-artifact@v4
29+
with:
30+
name: results
31+
- run: python test.py

ql/test/query-tests/Security/CWE-829/ArtifactPoisoningCritical.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ edges
1313
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:23:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | |
1414
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | |
1515
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | |
16+
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | |
17+
| .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | provenance | |
1618
nodes
1719
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
1820
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | semmle.label | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
@@ -42,6 +44,10 @@ nodes
4244
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n |
4345
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | semmle.label | Uses Step |
4446
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
47+
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
48+
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
49+
| .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
50+
| .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
4551
subpaths
4652
#select
4753
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
@@ -58,3 +64,4 @@ subpaths
5864
| .github/workflows/artifactpoisoning52.yml:19:14:23:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n | .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:23:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning52.yml:19:14:23:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n |
5965
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n |
6066
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | sed -f config foo.md > bar.md\n |
67+
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | python test.py |

ql/test/query-tests/Security/CWE-829/ArtifactPoisoningMedium.expected

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ edges
1313
| .github/workflows/artifactpoisoning52.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning52.yml:19:14:23:40 | echo "PACKAGES_FILE_LIST<<EOF" >> "${GITHUB_ENV}"\nls \| grep -E "*.(tar.gz\|zip)$" >> "${GITHUB_ENV}"\nls \| grep -E "*.(txt\|md)$" >> "${GITHUB_ENV}"\necho "EOF" >> "${GITHUB_ENV}"\n | provenance | |
1414
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | provenance | |
1515
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | provenance | |
16+
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | provenance | |
17+
| .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | provenance | |
1618
nodes
1719
| .github/workflows/artifactpoisoning11.yml:13:9:32:6 | Uses Step | semmle.label | Uses Step |
1820
| .github/workflows/artifactpoisoning11.yml:38:11:38:77 | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build | semmle.label | ./sonarcloud-data/x.py build -j$(nproc) --compiler gcc --skip-build |
@@ -42,5 +44,10 @@ nodes
4244
| .github/workflows/artifactpoisoning53.yml:18:14:23:29 | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n | semmle.label | {\n echo 'JSON_RESPONSE<<EOF'\n ls \| grep -E "*.(tar.gz\|zip)$"\n echo EOF\n} >> "$GITHUB_ENV"\n |
4345
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | semmle.label | Uses Step |
4446
| .github/workflows/artifactpoisoning71.yml:17:14:18:40 | sed -f config foo.md > bar.md\n | semmle.label | sed -f config foo.md > bar.md\n |
47+
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
48+
| .github/workflows/artifactpoisoning81.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
49+
| .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | semmle.label | Uses Step |
50+
| .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | semmle.label | python test.py |
4551
subpaths
4652
#select
53+
| .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | Potential artifact poisoning in $@, which may be controlled by an external user. | .github/workflows/artifactpoisoning82.yml:31:14:31:27 | python test.py | python test.py |

ql/test/query-tests/Security/CWE-829/UntrustedCheckoutCritical.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ edges
2626
| .github/workflows/artifactpoisoning53.yml:13:9:15:6 | Run Step | .github/workflows/artifactpoisoning53.yml:15:9:18:6 | Run Step |
2727
| .github/workflows/artifactpoisoning53.yml:15:9:18:6 | Run Step | .github/workflows/artifactpoisoning53.yml:18:9:23:29 | Run Step |
2828
| .github/workflows/artifactpoisoning71.yml:9:9:16:6 | Uses Step | .github/workflows/artifactpoisoning71.yml:16:9:18:40 | Run Step |
29+
| .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:14:9:16:6 | Run Step |
30+
| .github/workflows/artifactpoisoning81.yml:14:9:16:6 | Run Step | .github/workflows/artifactpoisoning81.yml:16:9:22:2 | Uses Step |
31+
| .github/workflows/artifactpoisoning81.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning81.yml:31:9:31:28 | Run Step |
32+
| .github/workflows/artifactpoisoning82.yml:11:9:14:6 | Uses Step | .github/workflows/artifactpoisoning82.yml:14:9:16:6 | Run Step |
33+
| .github/workflows/artifactpoisoning82.yml:14:9:16:6 | Run Step | .github/workflows/artifactpoisoning82.yml:16:9:22:2 | Uses Step |
34+
| .github/workflows/artifactpoisoning82.yml:28:9:31:6 | Uses Step | .github/workflows/artifactpoisoning82.yml:31:9:31:28 | Run Step |
2935
| .github/workflows/auto_ci.yml:20:9:27:6 | Uses Step | .github/workflows/auto_ci.yml:27:9:32:6 | Uses Step |
3036
| .github/workflows/auto_ci.yml:27:9:32:6 | Uses Step | .github/workflows/auto_ci.yml:32:9:37:6 | Run Step |
3137
| .github/workflows/auto_ci.yml:32:9:37:6 | Run Step | .github/workflows/auto_ci.yml:37:9:40:6 | Run Step |

ql/test/query-tests/Security/CWE-829/UntrustedCheckoutMedium.expected

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
| .github/workflows/artifactpoisoning81.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
2+
| .github/workflows/artifactpoisoning82.yml:11:9:14:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
13
| .github/workflows/dependabot1.yml:15:9:19:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
24
| .github/workflows/dependabot1.yml:39:9:43:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |
35
| .github/workflows/dependabot2.yml:33:9:38:6 | Uses Step | Potential unsafe checkout of untrusted pull request on privileged workflow. |

0 commit comments

Comments
 (0)