Skip to content

Commit b833ac6

Browse files
authored
ADO Pwn Request (#169)
1 parent b18fbbb commit b833ac6

12 files changed

+158
-7
lines changed

docs/content/en/rules/img.png

19.3 KB
Loading

docs/content/en/rules/img_1.png

82.2 KB
Loading

docs/content/en/rules/untrusted_checkout_exec.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,8 +194,25 @@ jobs:
194194
})
195195
```
196196
197+
### Azure DevOps
198+
199+
#### Caveat
200+
False positives are likely given that static analysis of solely the pipeline file is not enough to confirm exploitability
201+
202+
#### Recommended
203+
##### Azure DevOps Settings
204+
Organization Setting:
205+
![img.png](img.png)
206+
207+
Avoid activating the following settings to prevent issues:
208+
![img_1.png](img_1.png)
209+
210+
211+
197212
## See Also
198213
- [Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)
199214
- [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)
200215
- [The tale of a Supply Chain near-miss incident](https://boostsecurity.io/blog/the-tale-of-a-supply-chain-near-miss-incident)
201216
- [Living Off The Pipeline](https://boostsecurityio.github.io/lotp/)
217+
- https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#important-security-considerations
218+
- https://learn.microsoft.com/en-us/azure/devops/pipelines/security/misc?view=azure-devops#dont-provide-secrets-to-fork-builds

models/azure_pipelines.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import (
99
type AzurePipeline struct {
1010
Path string `json:"path" yaml:"-"`
1111

12-
Stages []AzureStage `json:"stages"`
13-
Pr AzurePr `json:"pr"`
14-
Variables map[string]string `json:"variables"`
12+
Stages []AzureStage `json:"stages"`
13+
Pr AzurePr `json:"pr"`
14+
Variables AzurePipelineVariables `json:"variables"`
1515
}
1616

1717
func (o AzurePipeline) IsValid() bool {
@@ -128,3 +128,32 @@ func (o *AzureStep) UnmarshalYAML(node *yaml.Node) error {
128128
*o = AzureStep(s)
129129
return nil
130130
}
131+
132+
type AzurePipelineVariable struct {
133+
Name string `yaml:"name"`
134+
Value string `yaml:"value"`
135+
}
136+
137+
type AzurePipelineVariables struct {
138+
Map map[string]string `json:"map"`
139+
}
140+
141+
func (v *AzurePipelineVariables) UnmarshalYAML(value *yaml.Node) error {
142+
v.Map = make(map[string]string)
143+
144+
var mapFormat map[string]string
145+
if err := value.Decode(&mapFormat); err == nil {
146+
v.Map = mapFormat
147+
return nil
148+
}
149+
150+
var listFormat []AzurePipelineVariable
151+
if err := value.Decode(&listFormat); err == nil {
152+
for _, variable := range listFormat {
153+
v.Map[variable.Name] = variable.Value
154+
}
155+
return nil
156+
}
157+
158+
return fmt.Errorf("variables must be either a map or a list of objects")
159+
}

models/azure_pipelines_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func TestAzurePipeline(t *testing.T) {
4545
},
4646
},
4747
},
48-
Variables: map[string]string{
48+
Variables: AzurePipelineVariables{map[string]string{
4949
"system.debug": "true",
50-
},
50+
}},
5151
},
5252
},
5353
{

opa/rego/rules/debug_enabled.rego

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ results contains poutine.finding(rule, pkg.purl, {
9696
}) if {
9797
pkg := input.packages[_]
9898
pipeline := pkg.azure_pipelines[_]
99-
pipeline.variables[key]
99+
pipeline.variables.map[key]
100100
key == "system.debug"
101-
pipeline.variables[key] == "true"
101+
pipeline.variables.map[key] == "true"
102102
}

opa/rego/rules/untrusted_checkout_exec.rego

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,46 @@ _workflows_runs_from_pr contains [pkg.purl, workflow] if {
9191

9292
utils.filter_workflow_events(parent, github.workflow_run.parent.events)
9393
}
94+
95+
# Azure Devops
96+
97+
results contains poutine.finding(rule, pkg_purl, {
98+
"path": pipeline_path,
99+
"job": job,
100+
"step": s.step_idx,
101+
"line": s.step.lines[attr],
102+
"details": sprintf("Detected usage of `%s`", [cmd]),
103+
}) if {
104+
[pkg_purl, pipeline_path, s, job] := _steps_after_untrusted_checkout_ado[_]
105+
regex.match(
106+
sprintf("([^a-z]|^)(%v)", [concat("|", build_commands[cmd])]),
107+
s.step[attr],
108+
)
109+
}
110+
111+
_steps_after_untrusted_checkout_ado contains [pkg.purl, pipeline.path, s, job] if {
112+
pkg := input.packages[_]
113+
pipeline := pkg.azure_pipelines[_]
114+
pipeline.pr.disabled == false
115+
stage := pipeline.stages[_]
116+
117+
checkout := find_ado_checkout(stage)[_]
118+
s := steps_after(checkout)[_]
119+
job := stage.jobs[s.job_idx].job
120+
}
121+
122+
steps_after(checkout) := steps if {
123+
steps := {{"step": s, "job_idx": checkout.job_idx, "step_idx": k} |
124+
s := checkout.stage.jobs[checkout.job_idx].steps[k]
125+
k > checkout.step_idx
126+
}
127+
}
128+
129+
find_ado_checkout(stage) := xs if {
130+
xs := {{"job_idx": j, "step_idx": i, "stage": stage} |
131+
s := stage.jobs[j].steps[i]
132+
s[step_attr]
133+
step_attr == "checkout"
134+
s[step_attr] == "self"
135+
}
136+
}

scanner/inventory_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,28 @@ func TestFindings(t *testing.T) {
375375
Details: "system.debug",
376376
},
377377
},
378+
{
379+
RuleId: "untrusted_checkout_exec",
380+
Purl: purl,
381+
Meta: opa.FindingMeta{
382+
Path: "azure-pipelines-2.yml",
383+
Line: 14,
384+
Job: "",
385+
Step: "2",
386+
Details: "Detected usage of `npm`",
387+
},
388+
},
389+
{
390+
RuleId: "untrusted_checkout_exec",
391+
Purl: purl,
392+
Meta: opa.FindingMeta{
393+
Path: "azure-pipelines-4.yml",
394+
Line: 11,
395+
Job: "",
396+
Step: "2",
397+
Details: "Detected usage of `npm`",
398+
},
399+
},
378400
}
379401

380402
assert.Equal(t, len(findings), len(results.Findings))

scanner/testdata/azure-pipelines-1.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ steps:
77
echo "Hello, pr!"
88
- bash: |
99
curl $(URL) | bash
10+
- script: npm install
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
pr:
2+
- main
3+
4+
pool:
5+
vmImage: ubuntu-latest
6+
7+
variables:
8+
- name: trustedSourceUrl
9+
value: https://gist.githubusercontent.com/fproulx-boostsecurity/fef312cd7d54b9420b10fd50d0793191/raw/a5f417b88fa2184a9726b274daf18d29da6c79ad/id
10+
11+
steps:
12+
- checkout: self
13+
- script: bash script.sh
14+
- script: npm install

0 commit comments

Comments
 (0)