Skip to content

Commit 5269205

Browse files
authored
docs: unverified_script_exec (#130)
Co-authored-by: Becojo <[email protected]>
1 parent 6f567c8 commit 5269205

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
title: "Unverified Script Execution"
3+
slug: unverified_script_exec
4+
url: /rules/unverified_script_exec/
5+
rule: unverified_script_exec
6+
severity: note
7+
---
8+
9+
## Description
10+
11+
The pipeline executes a script or binary fetched from a remote server without verifying its integrity. This pattern commonly appears in the form `curl $URL | bash` (referred as _curl pipe bash_) where a remote script is downloaded and executed in a subsequent command. Other commands may be used in place of `curl` to download the script, such as `wget` or `Invoke-WebRequest`, as well as other interpreters than `bash` such as `python`, `powershell`, `php`, etc.
12+
13+
Piping `curl` into `bash` is a common way to quickly get started with a software on a development machine. Although this is convenient and generally safe to do when the script is sourced from a trusted domain, the likelihood of downloading a compromised script increases when the frequency of execution is higher, such as in a CI pipeline. For production build environments, executing remote scripts leave little to no control and visibility over the code that is being executed. This obscures the provenance of build dependencies and tamper with the build environment in unpredictable ways.
14+
15+
## Remediation
16+
17+
### Anti-Pattern
18+
19+
```sh
20+
curl https://git.io/get_helm.sh | bash
21+
curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 && chmod +x ./kind
22+
bash <(curl -s https://codecov.io/bash)
23+
deno run --allow-all https://raw.githubusercontent.com/org/repo/main/ci.ts
24+
iex ((New-Object System.Net.WebClient).DownloadString("https://get.pulumi.com/install.ps1"))
25+
iwr -useb get.scoop.sh | iex
26+
```
27+
28+
### Prefer Using a Package Manager
29+
30+
If the script is meant to install software, consider installing it through a package manager operating system of the pipeline (`apt`, `apk`, `brew`, `rpm`, etc.).
31+
The CI provider's plugin ecosystem (GitHub Actions, Gitlab CI/CD components) may already have a plugin that can install the software in a more robust way. Otherwise, consider using the software in a container image.
32+
33+
### Download the Script From a Public Repository
34+
35+
Installation scripts that are hosted on a custom domain add unnecessary risks when sourcing the script. Often the custom domain simply redirects to a file hosted on a public repository.
36+
37+
Using a `HEAD` request with `curl` can be used to reveal the redirection URL:
38+
```sh
39+
$ curl -I https://get.rvm.io/ | grep -i location
40+
Location: https://raw.githubusercontent.com/rvm/rvm/master/binscripts/rvm-installer
41+
```
42+
43+
Installation scripts that are hosted in a public repository benefit from publicly auditable events and a version control system that provides transparency on the script's history of changes. It also allows consumers of the script to download the file from a specific commit, providing an additional integrity measure that ensures the script does not change unexpectedly.
44+
45+
Instead of downloading a script from a mutable reference, such as a branch or tag:
46+
```sh
47+
curl -f https://raw.githubusercontent.com/anchore/grype/main/install.sh | bash
48+
```
49+
50+
Resolve the mutable reference to a commit SHA using `git ls-remote`:
51+
```sh
52+
git ls-remote https://github.com/anchore/grype main
53+
239741f535c59d6e1b9faee61f64ebcf4361d2c5 refs/heads/main
54+
```
55+
56+
Then, in a CI workflow, replace `main` with the commit SHA to execute the script from an immutable reference:
57+
```sh
58+
curl -f https://raw.githubusercontent.com/anchore/grype/239741f535c59d6e1b9faee61f64ebcf4361d2c5/install.sh | bash
59+
```
60+
61+
### Use `curl --fail`
62+
63+
Remote servers can sometimes fail to properly serve a request and could return a response that is not the expected content. For example, in case of an intermittent server error, an HTML page may be returned instead of a bash script. Piping HTML into `bash` may have unintended consequences. By using curl's `--fail` option, it ensures the command does not output the response when the request fails, thus reducing the risk of executing unexpected content.
64+
65+
```sh
66+
$ curl https://example.com/foo.sh | bash
67+
bash: line 1: syntax error near unexpected token `newline'
68+
bash: line 1: `<?xml version="1.0" encoding="iso-8859-1"?>'
69+
70+
$ curl --fail https://example.com/foo.sh | bash
71+
curl: (22) The requested URL returned error: 500
72+
```
73+
74+
### Enforce Integrity With Checksum Verification
75+
76+
To ensure the content of the script does not change after it is included in a CI workflow, a checksum can be computed and verified before executing the script. This approach is best when used with remote scripts that are known to be immutable. Otherwise, the checksums will need to be updated each time the remote script changes.
77+
78+
First, compute the digest of the script:
79+
```sh
80+
$ curl https://raw.githubusercontent.com/anchore/grype/239741f535c59d6e1b9faee61f64ebcf4361d2c5/install.sh | sha256sum
81+
a8c6d3c0f110f7243bb379f9baf46b382a1b7704221a0d4591b810fe741176e3 -
82+
```
83+
84+
Then, in a CI pipeline, the script should first be downloaded to file and then only be executed if the checksum matches the value computed earlier:
85+
```sh
86+
curl -fo install.sh https://raw.githubusercontent.com/anchore/grype/239741f535c59d6e1b9faee61f64ebcf4361d2c5/install.sh
87+
echo "a8c6d3c0f110f7243bb379f9baf46b382a1b7704221a0d4591b810fe741176e3 install.sh" | sha256sum -c \
88+
&& bash install.sh
89+
```

0 commit comments

Comments
 (0)