Merge pull request #23 from GitHubSecurityLab/IAC_queries

feat(queries): Migrate queries from AdvancedSecurity repo
This commit is contained in:
Mathew Payne
2024-02-27 20:11:12 +00:00
committed by GitHub
11 changed files with 139 additions and 6 deletions

View File

@@ -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") }
}
/**

View File

@@ -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()
}
}

View File

@@ -6,6 +6,7 @@
* @kind problem
* @problem.severity warning
* @precision low
* @security-severity 9.3
* @id actions/untrusted-checkout
* @tags actions
* security

View 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
```

View 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"

View 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).

View 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()

View File

@@ -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:

View File

@@ -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]

View File

@@ -6,6 +6,7 @@ on:
push:
branches:
- master
permissions: {}
jobs:
cherry_pick:
runs-on: ubuntu-latest