mirror of
https://github.com/github/codeql.git
synced 2026-05-01 19:55:15 +02:00
Merge pull request #23 from GitHubSecurityLab/IAC_queries
feat(queries): Migrate queries from AdvancedSecurity repo
This commit is contained in:
@@ -54,8 +54,6 @@ class CompositeActionStmt extends Statement instanceof Actions::CompositeAction
|
||||
InputsStmt getInputsStmt() { result = this.(YamlMapping).lookup("inputs") }
|
||||
|
||||
OutputsStmt getOutputsStmt() { result = this.(YamlMapping).lookup("outputs") }
|
||||
|
||||
string getName() { result = this.getLocation().getFile().getRelativePath() }
|
||||
}
|
||||
|
||||
class RunsStmt extends Statement instanceof Actions::Runs {
|
||||
@@ -68,6 +66,8 @@ class RunsStmt extends Statement instanceof Actions::Runs {
|
||||
* A Github Actions Workflow
|
||||
*/
|
||||
class WorkflowStmt extends Statement instanceof Actions::Workflow {
|
||||
string getName() { result = super.getName() }
|
||||
|
||||
JobStmt getAJobStmt() { result = super.getJob(_) }
|
||||
|
||||
JobStmt getJobStmt(string id) { result = super.getJob(id) }
|
||||
@@ -79,6 +79,8 @@ class WorkflowStmt extends Statement instanceof Actions::Workflow {
|
||||
string getATriggerEvent() {
|
||||
exists(YamlNode n | n = super.getOn().(YamlMappingLikeNode).getNode(result))
|
||||
}
|
||||
|
||||
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
|
||||
}
|
||||
|
||||
class ReusableWorkflowStmt extends WorkflowStmt {
|
||||
@@ -91,8 +93,6 @@ class ReusableWorkflowStmt extends WorkflowStmt {
|
||||
InputsStmt getInputsStmt() { result = workflow_call.(YamlMapping).lookup("inputs") }
|
||||
|
||||
OutputsStmt getOutputsStmt() { result = workflow_call.(YamlMapping).lookup("outputs") }
|
||||
|
||||
string getName() { result = this.getLocation().getFile().getRelativePath() }
|
||||
}
|
||||
|
||||
class InputsStmt extends Statement instanceof YamlMapping {
|
||||
@@ -189,6 +189,8 @@ class JobStmt extends Statement instanceof Actions::Job {
|
||||
}
|
||||
|
||||
IfStmt getIfStmt() { result = super.getIf() }
|
||||
|
||||
Statement getPermissionsStmt() { result = this.(YamlMapping).lookup("permissions") }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,10 +83,10 @@ class DataFlowCallable instanceof Cfg::CfgScope {
|
||||
|
||||
string getName() {
|
||||
if this instanceof ReusableWorkflowStmt
|
||||
then result = this.(ReusableWorkflowStmt).getName()
|
||||
then result = this.(ReusableWorkflowStmt).getLocation().getFile().getRelativePath()
|
||||
else
|
||||
if this instanceof CompositeActionStmt
|
||||
then result = this.(CompositeActionStmt).getName()
|
||||
then result = this.(CompositeActionStmt).getLocation().getFile().getRelativePath()
|
||||
else none()
|
||||
}
|
||||
}
|
||||
|
||||
0
ql/src/Security/CWE-094/UntrustedCheckout.md
Normal file
0
ql/src/Security/CWE-094/UntrustedCheckout.md
Normal file
@@ -6,6 +6,7 @@
|
||||
* @kind problem
|
||||
* @problem.severity warning
|
||||
* @precision low
|
||||
* @security-severity 9.3
|
||||
* @id actions/untrusted-checkout
|
||||
* @tags actions
|
||||
* security
|
||||
|
||||
22
ql/src/Security/CWE-275/MissingActionsPermissions.md
Normal file
22
ql/src/Security/CWE-275/MissingActionsPermissions.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Actions Job and Workflow Permissions are not set
|
||||
|
||||
A GitHub Actions job or workflow hasn't set permissions to restrict privileges to the workflow job.
|
||||
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.
|
||||
|
||||
## Recommendation
|
||||
|
||||
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:
|
||||
|
||||
```yaml
|
||||
name: "My workflow"
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
# or
|
||||
jobs:
|
||||
my-job:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
```
|
||||
23
ql/src/Security/CWE-275/MissingActionsPermissions.ql
Normal file
23
ql/src/Security/CWE-275/MissingActionsPermissions.ql
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @name Workflow does not contain permissions
|
||||
* @description Workflows should contain permissions to provide a clear understanding has permissions to run the workflow.
|
||||
* @kind problem
|
||||
* @security-severity 5.0
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id actions/missing-workflow-permissions
|
||||
* @tags actions
|
||||
* maintainability
|
||||
* external/cwe/cwe-275
|
||||
*/
|
||||
|
||||
import actions
|
||||
|
||||
from WorkflowStmt workflow, JobStmt job
|
||||
where
|
||||
job = workflow.getAJobStmt() and
|
||||
(
|
||||
not exists(workflow.getPermissionsStmt()) and
|
||||
not exists(job.getPermissionsStmt())
|
||||
)
|
||||
select job, "Actions Job or Workflow does not set permissions"
|
||||
44
ql/src/Security/CWE-829/UnpinnedActionsTag.md
Normal file
44
ql/src/Security/CWE-829/UnpinnedActionsTag.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Unpinned tag for 3rd party Action in workflow
|
||||
|
||||
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."
|
||||
|
||||
## Recommendation
|
||||
|
||||
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.
|
||||
|
||||
## Example
|
||||
|
||||
In this example, the Actions workflow uses an unpinned version.
|
||||
|
||||
```yaml
|
||||
name: "Unpinned Action Example"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions-third-party-mirror/checkout@v3
|
||||
|
||||
- run: |
|
||||
./build.sh
|
||||
```
|
||||
|
||||
The Action is pinned in the example below.
|
||||
|
||||
```yaml
|
||||
name: "Pinned Action Example"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions-mirror-third-party/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c
|
||||
|
||||
- run: |
|
||||
./build.sh
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- GitHub: [Security hardening for GitHub Actions](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions)
|
||||
- Common Weakness Enumeration: [CWE-829](https://cwe.mitre.org/data/definitions/829.html).
|
||||
38
ql/src/Security/CWE-829/UnpinnedActionsTag.ql
Normal file
38
ql/src/Security/CWE-829/UnpinnedActionsTag.ql
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @name Unpinned tag for 3rd party Action in workflow
|
||||
* @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.
|
||||
* @kind problem
|
||||
* @security-severity 5.0
|
||||
* @problem.severity warning
|
||||
* @precision high
|
||||
* @id actions/unpinned-tag
|
||||
* @tags security
|
||||
* actions
|
||||
* external/cwe/cwe-829
|
||||
*/
|
||||
|
||||
import actions
|
||||
|
||||
bindingset[version]
|
||||
private predicate isPinnedCommit(string version) { version.regexpMatch("^[A-Fa-f0-9]{40}$") }
|
||||
|
||||
bindingset[repo]
|
||||
private predicate isTrustedOrg(string repo) {
|
||||
exists(string org | org in ["actions", "github", "advanced-security"] | repo.matches(org + "/%"))
|
||||
}
|
||||
|
||||
from StepUsesExpr uses, string repo, string version, WorkflowStmt workflow, string name
|
||||
where
|
||||
uses.getCallee() = repo and
|
||||
uses.getVersion() = version and
|
||||
uses.getEnclosingWorkflowStmt() = workflow and
|
||||
(
|
||||
workflow.getName() = name
|
||||
or
|
||||
not exists(workflow.getName()) and workflow.getLocation().getFile().getBaseName() = name
|
||||
) and
|
||||
not isPinnedCommit(version) and
|
||||
not isTrustedOrg(repo)
|
||||
select uses,
|
||||
"Unpinned 3rd party Action '" + name + "' step $@ uses '" + repo + "' with ref '" + version +
|
||||
"', not a pinned commit hash", uses, uses.toString()
|
||||
1
ql/src/test/.github/workflows/cross1.yml
vendored
1
ql/src/test/.github/workflows/cross1.yml
vendored
@@ -3,6 +3,7 @@ name: Issue Workflow
|
||||
on:
|
||||
issues:
|
||||
types: [opened,edited]
|
||||
permissions: {}
|
||||
jobs:
|
||||
#This job will check the issue to determine if it should be moved to a different repository
|
||||
redirectIssue:
|
||||
|
||||
1
ql/src/test/.github/workflows/cross2.yml
vendored
1
ql/src/test/.github/workflows/cross2.yml
vendored
@@ -2,6 +2,7 @@
|
||||
name: Issue Type Predicter
|
||||
# This workflow uses https://github.com/DynamoDS/IssuesTypePredicter to predict the type of a github issue
|
||||
|
||||
permissions: {}
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
1
ql/src/test/.github/workflows/cross3.yml
vendored
1
ql/src/test/.github/workflows/cross3.yml
vendored
@@ -6,6 +6,7 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
permissions: {}
|
||||
jobs:
|
||||
cherry_pick:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
Reference in New Issue
Block a user