Skip to content

Commit

Permalink
feat: add unverified_script_exec rule (#129)
Browse files Browse the repository at this point in the history
* add unverified_script_exec rule
* add safe patterns
---------
Co-authored-by: Becojo <[email protected]>
  • Loading branch information
becojo authored Jun 18, 2024
1 parent 422dab5 commit 4f6ca1b
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
22 changes: 22 additions & 0 deletions opa/opa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"github.com/boostsecurityio/poutine/models"
"github.com/open-policy-agent/opa/ast"

"fmt"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -178,3 +179,24 @@ func TestCapabilities(t *testing.T) {
}
}
}

func TestRulesMetadataLevel(t *testing.T) {
opa, err := NewOpa()
noOpaErrors(t, err)

query := `{rule_id: rule.level |
rule := data.rules[rule_id].rule;
not input[rule.level]
}`

var result map[string]string
err = opa.Eval(context.TODO(), query, map[string]interface{}{
"note": true,
"warning": true,
"error": true,
"none": true,
}, &result)
noOpaErrors(t, err)

assert.Empty(t, result, fmt.Sprintf("rules with invalid levels: %v", result))
}
75 changes: 75 additions & 0 deletions opa/rego/rules/unverified_script_exec.rego
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# METADATA
# title: Unverified Script Execution
# description: |-
# The pipeline executes a script or binary fetched from a remote
# server without verifying its integrity.
# custom:
# level: note
package rules.unverified_script_exec

import data.poutine
import data.poutine.utils
import rego.v1

rule := poutine.rule(rego.metadata.chain())

patterns.shell contains sprintf("(%s)", [concat("|", [
`(bash|source) <\(curl [^\)\n]+?\)`,
`(curl|wget|iwr)[^\n]{0,256}(\|(|.*?[^a-z])((ba)?sh|python|php|node|iex|perl)|chmod ([aug]?\+x|[75]))`,
`iex[^\n]{0,512}\.DownloadString\([^\)]+?\)`,
`deno (run|install) (-A|--allow-all)[^\n]{0,128}https://[^\s]{0,128}`,
])])

patterns.safe contains sprintf("(%s)", [concat("|", [
`https://raw\.githubusercontent\.com/[^/]+/[^/]+/[a-f0-9]{40}/`,
`https://github\.com/[^/]+/[^/]+/raw/[a-f0-9]{40}/`,
])])

results contains poutine.finding(rule, pkg_purl, _scripts[pkg_purl][_])

_unverified_scripts(script) = [sprintf("Command: %s", [match]) |
match := regex.find_n(patterns.shell[_], script, -1)[_]
not _is_safe(match)
]

_is_safe(match) = regex.match(patterns.safe[_], match)

_scripts[pkg.purl] contains {
"path": workflow.path,
"step": step_id,
"job": job.id,
"line": step.lines.run,
"details": details,
} if {
pkg := input.packages[_]
workflow := pkg.github_actions_workflows[_]
job := workflow.jobs[_]
step := job.steps[step_id]
details := _unverified_scripts(step.run)[_]
}

_scripts[pkg.purl] contains {
"path": action.path,
"step": step_id,
"line": step.lines.run,
"details": details,
} if {
pkg := input.packages[_]
action := pkg.github_actions_metadata[_]
step := action.runs.steps[step_id]
details := _unverified_scripts(step.run)[_]
}

_scripts[pkg.purl] contains {
"path": config.path,
"line": script.line,
"job": job.name,
"details": details,
} if {
some attr in {"before_script", "after_script", "script"}
pkg := input.packages[_]
config := pkg.gitlabci_configs[_]
job := array.concat(config.jobs, [config["default"]])[_]
script := job[attr][_]
details := _unverified_scripts(script.run)[_]
}
23 changes: 23 additions & 0 deletions scanner/inventory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ func TestFindings(t *testing.T) {
"github_action_from_unverified_creator_used",
"debug_enabled",
"job_all_secrets",
"unverified_script_exec",
})

findings := []opa.Finding{
Expand Down Expand Up @@ -308,6 +309,28 @@ func TestFindings(t *testing.T) {
Step: "3",
},
},
{
RuleId: "unverified_script_exec",
Purl: purl,
Meta: opa.FindingMeta{
Path: ".github/workflows/valid.yml",
Line: 70,
Job: "build",
Step: "12",
Details: "Command: curl https://example.com | bash",
},
},
{
RuleId: "unverified_script_exec",
Purl: purl,
Meta: opa.FindingMeta{
Path: ".github/workflows/valid.yml",
Line: 75,
Job: "build",
Step: "13",
Details: "Command: curl https://raw.githubusercontent.com/org/repo/main/install.sh | bash",
},
},
}

assert.Equal(t, len(findings), len(results.Findings))
Expand Down
16 changes: 16 additions & 0 deletions scanner/testdata/.github/workflows/valid.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ jobs:
run: |
# substring of go\ generate should not trigger
cargo generate
# unverified_script_exec
- id: 12
run: |
curl https://example.com | bash
# unverified_script_exec
- id: 13
run: |
curl https://raw.githubusercontent.com/org/repo/main/install.sh | bash
# safe unverified_script_exec
- id: 13
run: |
curl https://raw.githubusercontent.com/org/repo/0a727065ae5a2313e8e6acf172844e8ca30c1822/install.sh | bash
curl https://github.com/org/repo/raw/0a727065ae5a2313e8e6acf172844e8ca30c1822/install.sh | bash

0 comments on commit 4f6ca1b

Please sign in to comment.