Skip to content

Commit 409d35b

Browse files
authored
Merge pull request #23 from GitHubSecurityLab/IAC_queries
feat(queries): Migrate queries from AdvancedSecurity repo
2 parents c29f3a7 + fe976fa commit 409d35b

File tree

11 files changed

+139
-6
lines changed

11 files changed

+139
-6
lines changed

ql/lib/codeql/actions/Ast.qll

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,6 @@ class CompositeActionStmt extends Statement instanceof Actions::CompositeAction
5454
InputsStmt getInputsStmt() { result = this.(YamlMapping).lookup("inputs") }
5555

5656
OutputsStmt getOutputsStmt() { result = this.(YamlMapping).lookup("outputs") }
57-
58-
string getName() { result = this.getLocation().getFile().getRelativePath() }
5957
}
6058

6159
class RunsStmt extends Statement instanceof Actions::Runs {
@@ -68,6 +66,8 @@ class RunsStmt extends Statement instanceof Actions::Runs {
6866
* A Github Actions Workflow
6967
*/
7068
class WorkflowStmt extends Statement instanceof Actions::Workflow {
69+
string getName() { result = super.getName() }
70+
7171
JobStmt getAJobStmt() { result = super.getJob(_) }
7272

7373
JobStmt getJobStmt(string id) { result = super.getJob(id) }
@@ -79,6 +79,8 @@ class WorkflowStmt extends Statement instanceof Actions::Workflow {
7979
string getATriggerEvent() {
8080
exists(YamlNode n | n = super.getOn().(YamlMappingLikeNode).getNode(result))
8181
}
82+
83+
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
8284
}
8385

8486
class ReusableWorkflowStmt extends WorkflowStmt {
@@ -91,8 +93,6 @@ class ReusableWorkflowStmt extends WorkflowStmt {
9193
InputsStmt getInputsStmt() { result = workflow_call.(YamlMapping).lookup("inputs") }
9294

9395
OutputsStmt getOutputsStmt() { result = workflow_call.(YamlMapping).lookup("outputs") }
94-
95-
string getName() { result = this.getLocation().getFile().getRelativePath() }
9696
}
9797

9898
class InputsStmt extends Statement instanceof YamlMapping {
@@ -189,6 +189,8 @@ class JobStmt extends Statement instanceof Actions::Job {
189189
}
190190

191191
IfStmt getIfStmt() { result = super.getIf() }
192+
193+
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
192194
}
193195

194196
/**

ql/lib/codeql/actions/dataflow/internal/DataFlowPrivate.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,10 @@ class DataFlowCallable instanceof Cfg::CfgScope {
8383

8484
string getName() {
8585
if this instanceof ReusableWorkflowStmt
86-
then result = this.(ReusableWorkflowStmt).getName()
86+
then result = this.(ReusableWorkflowStmt).getLocation().getFile().getRelativePath()
8787
else
8888
if this instanceof CompositeActionStmt
89-
then result = this.(CompositeActionStmt).getName()
89+
then result = this.(CompositeActionStmt).getLocation().getFile().getRelativePath()
9090
else none()
9191
}
9292
}

ql/src/Security/CWE-094/UntrustedCheckout.md

Whitespace-only changes.

ql/src/Security/CWE-094/UntrustedCheckout.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* @kind problem
77
* @problem.severity warning
88
* @precision low
9+
* @security-severity 9.3
910
* @id actions/untrusted-checkout
1011
* @tags actions
1112
* security
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Actions Job and Workflow Permissions are not set
2+
3+
A GitHub Actions job or workflow hasn't set permissions to restrict privileges to the workflow job.
4+
A workflow job by default without the `permissions` key or a root workflow `permissions` will run with all the permissions which can be given to a workflow.
5+
6+
## Recommendation
7+
8+
Add the `permissions` key to the job or workflow (applied to all jobs) and set the permissions to the least privilege required to complete the task:
9+
10+
```yaml
11+
name: "My workflow"
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
# or
17+
jobs:
18+
my-job:
19+
permissions:
20+
contents: read
21+
pull-requests: write
22+
```
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @name Workflow does not contain permissions
3+
* @description Workflows should contain permissions to provide a clear understanding has permissions to run the workflow.
4+
* @kind problem
5+
* @security-severity 5.0
6+
* @problem.severity warning
7+
* @precision high
8+
* @id actions/missing-workflow-permissions
9+
* @tags actions
10+
* maintainability
11+
* external/cwe/cwe-275
12+
*/
13+
14+
import actions
15+
16+
from WorkflowStmt workflow, JobStmt job
17+
where
18+
job = workflow.getAJobStmt() and
19+
(
20+
not exists(workflow.getPermissionsStmt()) and
21+
not exists(job.getPermissionsStmt())
22+
)
23+
select job, "Actions Job or Workflow does not set permissions"
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Unpinned tag for 3rd party Action in workflow
2+
3+
The individual jobs in a GitHub Actions workflow can interact with (and compromise) other jobs. For example, a job querying the environment variables used by a later job, writing files to a shared directory that a later job processes, or even more directly by interacting with the Docker socket and inspecting other running containers and executing commands in them. This means that a compromise of a single action within a workflow can be very significant, as that compromised action would have access to all secrets configured on your repository, and may be able to use the `GITHUB_TOKEN` to write to the repository. Consequently, there is significant risk in sourcing actions from third-party repositories on GitHub. For information on some of the steps an attacker could take, see "Security hardening for GitHub Actions."
4+
5+
## Recommendation
6+
7+
Pin an action to a full length commit SHA. This is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload. When selecting a SHA, you should verify it is from the action's repository and not a repository fork.
8+
9+
## Example
10+
11+
In this example, the Actions workflow uses an unpinned version.
12+
13+
```yaml
14+
name: "Unpinned Action Example"
15+
16+
jobs:
17+
build:
18+
steps:
19+
- name: Checkout repository
20+
uses: actions-third-party-mirror/checkout@v3
21+
22+
- run: |
23+
./build.sh
24+
```
25+
26+
The Action is pinned in the example below.
27+
28+
```yaml
29+
name: "Pinned Action Example"
30+
31+
jobs:
32+
build:
33+
steps:
34+
- name: Checkout repository
35+
uses: actions-mirror-third-party/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
36+
37+
- run: |
38+
./build.sh
39+
```
40+
41+
## References
42+
43+
- GitHub: [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
44+
- Common Weakness Enumeration: [CWE-829](https://cwe.mitre.org/data/definitions/829.html).
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* @name Unpinned tag for 3rd party Action in workflow
3+
* @description Using a tag for a 3rd party Action that is not pinned to a commit can lead to executing an untrusted Action through a supply chain attack.
4+
* @kind problem
5+
* @security-severity 5.0
6+
* @problem.severity warning
7+
* @precision high
8+
* @id actions/unpinned-tag
9+
* @tags security
10+
* actions
11+
* external/cwe/cwe-829
12+
*/
13+
14+
import actions
15+
16+
bindingset[version]
17+
private predicate isPinnedCommit(string version) { version.regexpMatch("^[A-Fa-f0-9]{40}$") }
18+
19+
bindingset[repo]
20+
private predicate isTrustedOrg(string repo) {
21+
exists(string org | org in ["actions", "github", "advanced-security"] | repo.matches(org + "/%"))
22+
}
23+
24+
from StepUsesExpr uses, string repo, string version, WorkflowStmt workflow, string name
25+
where
26+
uses.getCallee() = repo and
27+
uses.getVersion() = version and
28+
uses.getEnclosingWorkflowStmt() = workflow and
29+
(
30+
workflow.getName() = name
31+
or
32+
not exists(workflow.getName()) and workflow.getLocation().getFile().getBaseName() = name
33+
) and
34+
not isPinnedCommit(version) and
35+
not isTrustedOrg(repo)
36+
select uses,
37+
"Unpinned 3rd party Action '" + name + "' step $@ uses '" + repo + "' with ref '" + version +
38+
"', not a pinned commit hash", uses, uses.toString()

ql/src/test/.github/workflows/cross1.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ name: Issue Workflow
33
on:
44
issues:
55
types: [opened,edited]
6+
permissions: {}
67
jobs:
78
#This job will check the issue to determine if it should be moved to a different repository
89
redirectIssue:

ql/src/test/.github/workflows/cross2.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name: Issue Type Predicter
33
# This workflow uses https://github.com/DynamoDS/IssuesTypePredicter to predict the type of a github issue
44

5+
permissions: {}
56
on:
67
issues:
78
types: [opened, edited]

0 commit comments

Comments
 (0)