Skip to content

Commit

Permalink
ADO Pwn Request (#169)
Browse files Browse the repository at this point in the history
  • Loading branch information
SUSTAPLE117 committed Jul 24, 2024
1 parent b18fbbb commit b833ac6
Show file tree
Hide file tree
Showing 12 changed files with 158 additions and 7 deletions.
Binary file added docs/content/en/rules/img.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/content/en/rules/img_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions docs/content/en/rules/untrusted_checkout_exec.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,25 @@ jobs:
})
```
### Azure DevOps
#### Caveat
False positives are likely given that static analysis of solely the pipeline file is not enough to confirm exploitability
#### Recommended
##### Azure DevOps Settings
Organization Setting:
![img.png](img.png)
Avoid activating the following settings to prevent issues:
![img_1.png](img_1.png)
## See Also
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
- [Erosion of Trust: Unmasking Supply Chain Vulnerabilities in the Terraform Registry](https://boostsecurity.io/blog/erosion-of-trust-unmasking-supply-chain-vulnerabilities-in-the-terraform-registry)
- [The tale of a Supply Chain near-miss incident](https://boostsecurity.io/blog/the-tale-of-a-supply-chain-near-miss-incident)
- [Living Off The Pipeline](https://boostsecurityio.github.io/lotp/)
- https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#important-security-considerations
- https://learn.microsoft.com/en-us/azure/devops/pipelines/security/misc?view=azure-devops#dont-provide-secrets-to-fork-builds
35 changes: 32 additions & 3 deletions models/azure_pipelines.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import (
type AzurePipeline struct {
Path string `json:"path" yaml:"-"`

Stages []AzureStage `json:"stages"`
Pr AzurePr `json:"pr"`
Variables map[string]string `json:"variables"`
Stages []AzureStage `json:"stages"`
Pr AzurePr `json:"pr"`
Variables AzurePipelineVariables `json:"variables"`
}

func (o AzurePipeline) IsValid() bool {
Expand Down Expand Up @@ -128,3 +128,32 @@ func (o *AzureStep) UnmarshalYAML(node *yaml.Node) error {
*o = AzureStep(s)
return nil
}

type AzurePipelineVariable struct {
Name string `yaml:"name"`
Value string `yaml:"value"`
}

type AzurePipelineVariables struct {
Map map[string]string `json:"map"`
}

func (v *AzurePipelineVariables) UnmarshalYAML(value *yaml.Node) error {
v.Map = make(map[string]string)

var mapFormat map[string]string
if err := value.Decode(&mapFormat); err == nil {
v.Map = mapFormat
return nil
}

var listFormat []AzurePipelineVariable
if err := value.Decode(&listFormat); err == nil {
for _, variable := range listFormat {
v.Map[variable.Name] = variable.Value
}
return nil
}

return fmt.Errorf("variables must be either a map or a list of objects")
}
4 changes: 2 additions & 2 deletions models/azure_pipelines_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ func TestAzurePipeline(t *testing.T) {
},
},
},
Variables: map[string]string{
Variables: AzurePipelineVariables{map[string]string{
"system.debug": "true",
},
}},
},
},
{
Expand Down
4 changes: 2 additions & 2 deletions opa/rego/rules/debug_enabled.rego
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ results contains poutine.finding(rule, pkg.purl, {
}) if {
pkg := input.packages[_]
pipeline := pkg.azure_pipelines[_]
pipeline.variables[key]
pipeline.variables.map[key]
key == "system.debug"
pipeline.variables[key] == "true"
pipeline.variables.map[key] == "true"
}
43 changes: 43 additions & 0 deletions opa/rego/rules/untrusted_checkout_exec.rego
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,46 @@ _workflows_runs_from_pr contains [pkg.purl, workflow] if {

utils.filter_workflow_events(parent, github.workflow_run.parent.events)
}

# Azure Devops

results contains poutine.finding(rule, pkg_purl, {
"path": pipeline_path,
"job": job,
"step": s.step_idx,
"line": s.step.lines[attr],
"details": sprintf("Detected usage of `%s`", [cmd]),
}) if {
[pkg_purl, pipeline_path, s, job] := _steps_after_untrusted_checkout_ado[_]
regex.match(
sprintf("([^a-z]|^)(%v)", [concat("|", build_commands[cmd])]),
s.step[attr],
)
}

_steps_after_untrusted_checkout_ado contains [pkg.purl, pipeline.path, s, job] if {
pkg := input.packages[_]
pipeline := pkg.azure_pipelines[_]
pipeline.pr.disabled == false
stage := pipeline.stages[_]

checkout := find_ado_checkout(stage)[_]
s := steps_after(checkout)[_]
job := stage.jobs[s.job_idx].job
}

steps_after(checkout) := steps if {
steps := {{"step": s, "job_idx": checkout.job_idx, "step_idx": k} |
s := checkout.stage.jobs[checkout.job_idx].steps[k]
k > checkout.step_idx
}
}

find_ado_checkout(stage) := xs if {
xs := {{"job_idx": j, "step_idx": i, "stage": stage} |
s := stage.jobs[j].steps[i]
s[step_attr]
step_attr == "checkout"
s[step_attr] == "self"
}
}
22 changes: 22 additions & 0 deletions scanner/inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,28 @@ func TestFindings(t *testing.T) {
Details: "system.debug",
},
},
{
RuleId: "untrusted_checkout_exec",
Purl: purl,
Meta: opa.FindingMeta{
Path: "azure-pipelines-2.yml",
Line: 14,
Job: "",
Step: "2",
Details: "Detected usage of `npm`",
},
},
{
RuleId: "untrusted_checkout_exec",
Purl: purl,
Meta: opa.FindingMeta{
Path: "azure-pipelines-4.yml",
Line: 11,
Job: "",
Step: "2",
Details: "Detected usage of `npm`",
},
},
}

assert.Equal(t, len(findings), len(results.Findings))
Expand Down
1 change: 1 addition & 0 deletions scanner/testdata/azure-pipelines-1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ steps:
echo "Hello, pr!"
- bash: |
curl $(URL) | bash
- script: npm install
14 changes: 14 additions & 0 deletions scanner/testdata/azure-pipelines-2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pr:
- main

pool:
vmImage: ubuntu-latest

variables:
- name: trustedSourceUrl
value: https://gist.githubusercontent.com/fproulx-boostsecurity/fef312cd7d54b9420b10fd50d0793191/raw/a5f417b88fa2184a9726b274daf18d29da6c79ad/id

steps:
- checkout: self
- script: bash script.sh
- script: npm install
14 changes: 14 additions & 0 deletions scanner/testdata/azure-pipelines-3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
pr:
- main

pool:
vmImage: ubuntu-latest

variables:
- name: trustedSourceUrl
value: https://gist.githubusercontent.com/fproulx-boostsecurity/fef312cd7d54b9420b10fd50d0793191/raw/a5f417b88fa2184a9726b274daf18d29da6c79ad/id

steps:
- script: bash script.sh
- script: npm install
- checkout: self
11 changes: 11 additions & 0 deletions scanner/testdata/azure-pipelines-4.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pool:
vmImage: ubuntu-latest

variables:
- name: trustedSourceUrl
value: https://gist.githubusercontent.com/fproulx-boostsecurity/fef312cd7d54b9420b10fd50d0793191/raw/a5f417b88fa2184a9726b274daf18d29da6c79ad/id

steps:
- checkout: self
- script: bash script.sh
- script: npm install

0 comments on commit b833ac6

Please sign in to comment.