Skip to content

Commit 673e649

Browse files
committed
github actions queries
1 parent d685aff commit 673e649

26 files changed

+946
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
on: issue_comment
2+
3+
jobs:
4+
echo-body:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- run: |
8+
echo '${{ github.event.comment.body }}'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
on: issue_comment
2+
3+
jobs:
4+
echo-body:
5+
runs-on: ubuntu-latest
6+
steps:
7+
- env:
8+
BODY: ${{ github.event.issue.body }}
9+
run: |
10+
echo '$BODY'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# comment_pr.yml
2+
name: Comment on the pull request
3+
4+
# read-write repo token
5+
# access to secrets
6+
on:
7+
workflow_run:
8+
workflows: ["Receive PR"]
9+
types:
10+
- completed
11+
12+
jobs:
13+
upload:
14+
runs-on: ubuntu-latest
15+
if: >
16+
${{ github.event.workflow_run.event == 'pull_request' &&
17+
github.event.workflow_run.conclusion == 'success' }}
18+
steps:
19+
- name: 'Download artifact'
20+
uses: actions/[email protected]
21+
with:
22+
script: |
23+
var artifacts = await github.actions.listWorkflowRunArtifacts({
24+
owner: context.repo.owner,
25+
repo: context.repo.repo,
26+
run_id: ${{github.event.workflow_run.id }},
27+
});
28+
var matchArtifact = artifacts.data.artifacts.filter((artifact) => {
29+
return artifact.name == "pr"
30+
})[0];
31+
var download = await github.actions.downloadArtifact({
32+
owner: context.repo.owner,
33+
repo: context.repo.repo,
34+
artifact_id: matchArtifact.id,
35+
archive_format: 'zip',
36+
});
37+
var fs = require('fs');
38+
fs.writeFileSync('${{github.workspace}}/pr.zip', Buffer.from(download.data));
39+
- run: unzip pr.zip
40+
41+
- name: 'Comment on PR'
42+
uses: actions/github-script@v3
43+
with:
44+
github-token: ${{ secrets.GITHUB_TOKEN }}
45+
script: |
46+
var fs = require('fs');
47+
var issue_number = Number(fs.readFileSync('./NR'));
48+
await github.issues.createComment({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
issue_number: issue_number,
52+
body: 'Everything is OK. Thank you for the PR!'
53+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
on:
2+
pull_request_target
3+
4+
jobs:
5+
build:
6+
name: Build and test
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v2
10+
with:
11+
ref: ${{ github.event.pull_request.head.sha }}
12+
13+
- uses: actions/setup-node@v1
14+
- run: |
15+
npm install
16+
npm build
17+
18+
- uses: completely/fakeaction@v2
19+
with:
20+
arg1: ${{ secrets.supersecret }}
21+
22+
- uses: fakerepo/comment-on-pr@v1
23+
with:
24+
message: |
25+
Thank you!
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# receive_pr.yml
2+
name: Receive PR
3+
4+
# read-only repo token
5+
# no access to secrets
6+
on:
7+
pull_request:
8+
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v2
15+
16+
# imitation of a build process
17+
- name: Build
18+
run: /bin/bash ./build.sh
19+
20+
- name: Save PR number
21+
run: |
22+
mkdir -p ./pr
23+
echo ${{ github.event.number }} > ./pr/NR
24+
- uses: actions/upload-artifact@v2
25+
with:
26+
name: pr
27+
path: pr/
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
8+
<p>
9+
10+
Using user-controlled input in GitHub Actions may lead to
11+
code injection in contexts like <i>run:</i> or <i>script:</i>.
12+
13+
</p>
14+
15+
</overview>
16+
17+
<recommendation>
18+
19+
<p>
20+
21+
The best practice to avoid code injection vulnerabilities
22+
in GitHub workflows is to set the untrusted input value of the expression
23+
to an intermediate environment variable.
24+
25+
</p>
26+
27+
</recommendation>
28+
29+
<example>
30+
31+
<p>
32+
33+
The following example lets a user inject an arbitrary shell command:
34+
35+
</p>
36+
37+
<sample src="examples/comment_issue_bad.yml" />
38+
39+
<p>
40+
41+
The following example uses shell syntax to read
42+
the environment variable and will prevent the attack:
43+
44+
</p>
45+
46+
<sample src="examples/comment_issue_good.yml" />
47+
48+
</example>
49+
50+
<references>
51+
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-untrusted-input">Keeping your GitHub Actions and workflows secure: Untrusted input</a>.</li>
52+
</references>
53+
54+
</qhelp>
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/**
2+
* @name Injection from user-controlled Actions context
3+
* @description Using user-controlled GitHub Actions contexts like `run:` or `script:` may allow a malicious
4+
* user to inject code into the GitHub action.
5+
* @kind problem
6+
* @problem.severity error
7+
* @precision high
8+
* @id js/actions/injection
9+
* @tags actions
10+
* security
11+
* external/cwe/cwe-829
12+
*/
13+
14+
import javascript
15+
import experimental.semmle.javascript.Actions
16+
17+
bindingset[context]
18+
private predicate isExternalUserControlledIssue(string context) {
19+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*title\\b") or
20+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*issue\\s*\\.\\s*body\\b")
21+
}
22+
23+
bindingset[context]
24+
private predicate isExternalUserControlledPullRequest(string context) {
25+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*title\\b") or
26+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*body\\b") or
27+
context
28+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*label\\b") or
29+
context
30+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*repo\\s*\\.\\s*default_branch\\b") or
31+
context
32+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pull_request\\s*\\.\\s*head\\s*\\.\\s*ref\\b")
33+
}
34+
35+
bindingset[context]
36+
private predicate isExternalUserControlledReview(string context) {
37+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review\\s*\\.\\s*body\\b") or
38+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*review_comment\\s*\\.\\s*body\\b")
39+
}
40+
41+
bindingset[context]
42+
private predicate isExternalUserControlledComment(string context) {
43+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*comment\\s*\\.\\s*body\\b")
44+
}
45+
46+
bindingset[context]
47+
private predicate isExternalUserControlledGollum(string context) {
48+
context
49+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*pages(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*page_name\\b")
50+
}
51+
52+
bindingset[context]
53+
private predicate isExternalUserControlledCommit(string context) {
54+
context
55+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*message\\b") or
56+
context.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*message\\b") or
57+
context
58+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
59+
context
60+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*head_commit\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
61+
context
62+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*email\\b") or
63+
context
64+
.regexpMatch("\\bgithub\\s*\\.\\s*event\\s*\\.\\s*commits(?:\\[[0-9]\\]|\\s*\\.\\s*\\*)+\\s*\\.\\s*author\\s*\\.\\s*name\\b") or
65+
context.regexpMatch("\\bgithub\\s*\\.\\s*head_ref\\b")
66+
}
67+
68+
from Actions::Run run, string context, Actions::On on
69+
where
70+
run.getAReferencedExpression() = context and
71+
run.getStep().getJob().getWorkflow().getOn() = on and
72+
(
73+
exists(on.getNode("issues")) and
74+
isExternalUserControlledIssue(context)
75+
or
76+
exists(on.getNode("pull_request_target")) and
77+
isExternalUserControlledPullRequest(context)
78+
or
79+
(exists(on.getNode("pull_request_review_comment")) or exists(on.getNode("pull_request_review"))) and
80+
isExternalUserControlledReview(context)
81+
or
82+
(exists(on.getNode("issue_comment")) or exists(on.getNode("pull_request_target"))) and
83+
isExternalUserControlledComment(context)
84+
or
85+
exists(on.getNode("gollum")) and
86+
isExternalUserControlledGollum(context)
87+
or
88+
exists(on.getNode("pull_request_target")) and
89+
isExternalUserControlledCommit(context)
90+
)
91+
select run,
92+
"Potential injection from the " + context +
93+
" context, which may be controlled by an external user."
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
8+
<p>
9+
10+
Combining <i>pull_request_target</i> workflow trigger with an explicit checkout
11+
of an untrusted pull request is a dangerous practice
12+
that may lead to repository compromise.
13+
14+
</p>
15+
16+
</overview>
17+
18+
<recommendation>
19+
20+
<p>
21+
22+
The best practice is to handle the potentially untrusted pull request
23+
via the <i>pull_request</i> trigger so that it is isolated in
24+
an unprivileged environment. The workflow processing the pull request
25+
should then store any results like code coverage or failed/passed tests
26+
in artifacts and exit. The following workflow then starts on <i>workflow_run</i>
27+
where it is granted write permission to the target repository and access to
28+
repository secrets, so that it can download the artifacts and make
29+
any necessary modifications to the repository or interact with third party services
30+
that require repository secrets (e.g. API tokens).
31+
32+
</p>
33+
34+
</recommendation>
35+
36+
<example>
37+
38+
<p>
39+
40+
The following example allows unauthorized repository modification
41+
and secrets exfiltration:
42+
43+
</p>
44+
45+
<sample src="examples/pull_request_target_bad.yml" />
46+
47+
<p>
48+
49+
The following examples use two triggers to handle potentially untrusted
50+
pull request in a secure manner:
51+
52+
</p>
53+
54+
<sample src="examples/receive_pr.yml" />
55+
<sample src="examples/comment_pr.yml" />
56+
57+
</example>
58+
59+
<references>
60+
<li>GitHub Security Lab Research: <a href="https://securitylab.github.com/research/github-actions-preventing-pwn-requests">Keeping your GitHub Actions and workflows secure: Preventing pwn requests</a>.</li>
61+
</references>
62+
63+
</qhelp>

0 commit comments

Comments
 (0)