Skip to content

Commit 5d7bc95

Browse files
Mikhail Swiftmikhailswift
authored andcommitted
feat: block known sensitive environment variables
Uses the list from https://github.com/Puliczek/awesome-list-of-secrets-in-environment-variables/blob/main/raw_list.txt but allows for the configuration of custom block lists. Signed-off-by: Mikhail Swift <mikhail@testifysec.com>
1 parent b55fb8b commit 5d7bc95

5 files changed

Lines changed: 224 additions & 32 deletions

File tree

pkg/attestation/commandrun/commandrun.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os/exec"
2222

2323
"github.com/testifysec/witness/pkg/attestation"
24+
"github.com/testifysec/witness/pkg/attestation/environment"
2425
"github.com/testifysec/witness/pkg/cryptoutil"
2526
)
2627

@@ -62,8 +63,17 @@ func WithSilent(silent bool) Option {
6263
}
6364
}
6465

66+
func WithEnvironmentBlockList(blockList map[string]struct{}) Option {
67+
return func(cr *CommandRun) {
68+
cr.environmentBlockList = blockList
69+
}
70+
}
71+
6572
func New(opts ...Option) *CommandRun {
66-
cr := &CommandRun{}
73+
cr := &CommandRun{
74+
environmentBlockList: environment.DefaultBlockList(),
75+
}
76+
6777
for _, opt := range opts {
6878
opt(cr)
6979
}
@@ -91,9 +101,10 @@ type CommandRun struct {
91101
ExitCode int `json:"exitcode"`
92102
Processes []ProcessInfo `json:"processes,omitempty"`
93103

94-
silent bool
95-
materials map[string]cryptoutil.DigestSet
96-
enableTracing bool
104+
silent bool
105+
materials map[string]cryptoutil.DigestSet
106+
enableTracing bool
107+
environmentBlockList map[string]struct{}
97108
}
98109

99110
func (rc *CommandRun) Attest(ctx *attestation.AttestationContext) error {

pkg/attestation/commandrun/tracing_linux.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"strings"
2828

2929
"github.com/testifysec/witness/pkg/attestation"
30+
"github.com/testifysec/witness/pkg/attestation/environment"
3031
"github.com/testifysec/witness/pkg/cryptoutil"
3132
"github.com/testifysec/witness/pkg/log"
3233
"golang.org/x/sys/unix"
@@ -37,11 +38,12 @@ const (
3738
)
3839

3940
type ptraceContext struct {
40-
parentPid int
41-
mainProgram string
42-
processes map[int]*ProcessInfo
43-
exitCode int
44-
hash []crypto.Hash
41+
parentPid int
42+
mainProgram string
43+
processes map[int]*ProcessInfo
44+
exitCode int
45+
hash []crypto.Hash
46+
environmentBlockList map[string]struct{}
4547
}
4648

4749
func enableTracing(c *exec.Cmd) {
@@ -52,10 +54,11 @@ func enableTracing(c *exec.Cmd) {
5254

5355
func (r *CommandRun) trace(c *exec.Cmd, actx *attestation.AttestationContext) ([]ProcessInfo, error) {
5456
pctx := &ptraceContext{
55-
parentPid: c.Process.Pid,
56-
mainProgram: c.Path,
57-
processes: make(map[int]*ProcessInfo),
58-
hash: actx.Hashes(),
57+
parentPid: c.Process.Pid,
58+
mainProgram: c.Path,
59+
processes: make(map[int]*ProcessInfo),
60+
hash: actx.Hashes(),
61+
environmentBlockList: r.environmentBlockList,
5962
}
6063

6164
if err := pctx.runTrace(); err != nil {
@@ -175,7 +178,13 @@ func (p *ptraceContext) handleSyscall(pid int, regs unix.PtraceRegs) error {
175178

176179
environ, err := os.ReadFile(envinLocation)
177180
if err == nil {
178-
procInfo.Environ = cleanString(string(environ))
181+
allVars := strings.Split(string(environ), "\x00")
182+
filteredEnviron := make([]string, 0)
183+
environment.FilterEnvironmentArray(allVars, p.environmentBlockList, func(_, _, varStr string) {
184+
filteredEnviron = append(filteredEnviron, varStr)
185+
})
186+
187+
procInfo.Environ = strings.Join(filteredEnviron, " ")
179188
}
180189

181190
cmdline, err := os.ReadFile(cmdlineLocation)
@@ -201,8 +210,8 @@ func (p *ptraceContext) handleSyscall(pid int, regs unix.PtraceRegs) error {
201210
if err != nil {
202211
return err
203212
}
204-
procInfo := p.getProcInfo(pid)
205213

214+
procInfo := p.getProcInfo(pid)
206215
digestSet, err := cryptoutil.CalculateDigestSetFromFile(file, p.hash)
207216
if err != nil {
208217
return err
@@ -271,33 +280,30 @@ func cleanString(s string) string {
271280

272281
func getPPIDFromStatus(status []byte) (int, error) {
273282
statusStr := string(status)
274-
275283
lines := strings.Split(statusStr, "\n")
276-
277284
for _, line := range lines {
278285
if strings.Contains(line, "PPid:") {
279286
parts := strings.Split(line, ":")
280287
ppid := strings.TrimSpace(parts[1])
281288
return strconv.Atoi(ppid)
282289
}
283290
}
291+
284292
return 0, nil
285293
}
286294

287295
func getSpecBypassIsVulnFromStatus(status []byte) bool {
288296
statusStr := string(status)
289-
290297
lines := strings.Split(statusStr, "\n")
291-
292298
for _, line := range lines {
293299
if strings.Contains(line, "Speculation_Store_Bypass:") {
294300
parts := strings.Split(line, ":")
295301
isVuln := strings.TrimSpace(parts[1])
296302
if strings.Contains(isVuln, "vulnerable") {
297303
return true
298304
}
299-
300305
}
301306
}
307+
302308
return false
303309
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2021 The Witness Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package environment
16+
17+
// sourced from https://github.com/Puliczek/awesome-list-of-secrets-in-environment-variables/blob/main/raw_list.txt
18+
func DefaultBlockList() map[string]struct{} {
19+
return map[string]struct{}{
20+
"AWS_ACCESS_KEY_ID": {},
21+
"AWS_SECRET_ACCESS_KEY": {},
22+
"AMAZON_AWS_ACCESS_KEY_ID": {},
23+
"AMAZON_AWS_SECRET_ACCESS_KEY": {},
24+
"ALGOLIA_API_KEY": {},
25+
"AZURE_CLIENT_ID": {},
26+
"AZURE_CLIENT_SECRET": {},
27+
"AZURE_USERNAME": {},
28+
"AZURE_PASSWORD": {},
29+
"MSI_ENDPOINT": {},
30+
"MSI_SECRET": {},
31+
"binance_api": {},
32+
"binance_secret": {},
33+
"BITTREX_API_KEY": {},
34+
"BITTREX_API_SECRET": {},
35+
"CF_PASSWORD": {},
36+
"CF_USERNAME": {},
37+
"CODECLIMATE_REPO_TOKEN": {},
38+
"COVERALLS_REPO_TOKEN": {},
39+
"CIRCLE_TOKEN": {},
40+
"DIGITALOCEAN_ACCESS_TOKEN": {},
41+
"DOCKER_EMAIL": {},
42+
"DOCKER_PASSWORD": {},
43+
"DOCKER_USERNAME": {},
44+
"DOCKERHUB_PASSWORD": {},
45+
"FACEBOOK_APP_ID": {},
46+
"FACEBOOK_APP_SECRET": {},
47+
"FACEBOOK_ACCESS_TOKEN": {},
48+
"FIREBASE_TOKEN": {},
49+
"FOSSA_API_KEY": {},
50+
"GH_TOKEN": {},
51+
"GH_ENTERPRISE_TOKEN": {},
52+
"GOOGLE_APPLICATION_CREDENTIALS": {},
53+
"GOOGLE_API_KEY": {},
54+
"CI_DEPLOY_USER": {},
55+
"CI_DEPLOY_PASSWORD": {},
56+
"GITLAB_USER_LOGIN": {},
57+
"CI_JOB_JWT": {},
58+
"CI_JOB_JWT_V2": {},
59+
"CI_JOB_TOKEN": {},
60+
"HEROKU_API_KEY": {},
61+
"HEROKU_API_USER": {},
62+
"MAILGUN_API_KEY": {},
63+
"MCLI_PRIVATE_API_KEY": {},
64+
"MCLI_PUBLIC_API_KEY": {},
65+
"NGROK_TOKEN": {},
66+
"NGROK_AUTH_TOKEN": {},
67+
"NPM_AUTH_TOKEN": {},
68+
"OKTA_CLIENT_ORGURL": {},
69+
"OKTA_CLIENT_TOKEN": {},
70+
"OKTA_OAUTH2_CLIENTSECRET": {},
71+
"OKTA_OAUTH2_CLIENTID": {},
72+
"OKTA_AUTHN_GROUPID": {},
73+
"OS_USERNAME": {},
74+
"OS_PASSWORD": {},
75+
"PERCY_TOKEN": {},
76+
"SAUCE_ACCESS_KEY": {},
77+
"SAUCE_USERNAME": {},
78+
"SENTRY_AUTH_TOKEN": {},
79+
"SLACK_TOKEN": {},
80+
"SNYK_TOKEN": {},
81+
"square_access_token": {},
82+
"square_oauth_secret": {},
83+
"STRIPE_API_KEY": {},
84+
"STRIPE_DEVICE_NAME": {},
85+
"SURGE_TOKEN": {},
86+
"SURGE_LOGIN": {},
87+
"TWILIO_ACCOUNT_SID": {},
88+
"CONSUMER_KEY": {},
89+
"CONSUMER_SECRET": {},
90+
"TRAVIS_SUDO": {},
91+
"TRAVIS_OS_NAME": {},
92+
"TRAVIS_SECURE_ENV_VARS": {},
93+
"VAULT_TOKEN": {},
94+
"VAULT_CLIENT_KEY": {},
95+
"TOKEN": {},
96+
"VULTR_ACCESS": {},
97+
"VULTR_SECRET": {},
98+
}
99+
}
100+
101+
// FilterEnvironmentArray expects an array of strings representing environment variables. Each element of the array is expected to be in the format of "KEY=VALUE".
102+
// blockList is the list of elements to filter from variables, and for each element of variables that does not appear in the blockList onAllowed will be called.
103+
func FilterEnvironmentArray(variables []string, blockList map[string]struct{}, onAllowed func(key, val, orig string)) {
104+
for _, v := range variables {
105+
key, val := splitVariable(v)
106+
if _, inBlockList := blockList[key]; inBlockList {
107+
continue
108+
}
109+
110+
onAllowed(key, val, v)
111+
}
112+
}

pkg/attestation/environment/environment.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,28 @@ type Attestor struct {
4040
Hostname string `json:"hostname"`
4141
Username string `json:"username"`
4242
Variables map[string]string `json:"variables,omitempty"`
43+
44+
blockList map[string]struct{}
45+
}
46+
47+
type Option func(*Attestor)
48+
49+
func WithBlockList(blockList map[string]struct{}) Option {
50+
return func(a *Attestor) {
51+
a.blockList = blockList
52+
}
4353
}
4454

45-
func New() *Attestor {
46-
return &Attestor{}
55+
func New(opts ...Option) *Attestor {
56+
attestor := &Attestor{
57+
blockList: DefaultBlockList(),
58+
}
59+
60+
for _, opt := range opts {
61+
opt(attestor)
62+
}
63+
64+
return attestor
4765
}
4866

4967
func (a *Attestor) Name() string {
@@ -70,17 +88,21 @@ func (a *Attestor) Attest(ctx *attestation.AttestationContext) error {
7088
a.Username = user.Username
7189
}
7290

73-
variables := os.Environ()
74-
for _, v := range variables {
75-
parts := strings.SplitN(v, "=", 2)
76-
key := parts[0]
77-
val := ""
78-
if len(parts) > 1 {
79-
val = parts[1]
80-
}
81-
91+
FilterEnvironmentArray(os.Environ(), a.blockList, func(key, val, _ string) {
8292
a.Variables[key] = val
83-
}
93+
})
8494

8595
return nil
8696
}
97+
98+
// splitVariable splits a string representing an environment variable in the format of
99+
// "KEY=VAL" and returns the key and val separately.
100+
func splitVariable(v string) (key, val string) {
101+
parts := strings.SplitN(v, "=", 2)
102+
key = parts[0]
103+
if len(parts) > 1 {
104+
val = parts[1]
105+
}
106+
107+
return
108+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2021 The Witness Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package environment
16+
17+
import (
18+
"os"
19+
"testing"
20+
21+
"github.com/stretchr/testify/require"
22+
"github.com/testifysec/witness/pkg/attestation"
23+
)
24+
25+
func TestEnvironment(t *testing.T) {
26+
attestor := New()
27+
ctx, err := attestation.NewContext([]attestation.Attestor{attestor})
28+
require.NoError(t, err)
29+
30+
t.Setenv("AWS_ACCESS_KEY_ID", "super secret")
31+
origVars := os.Environ()
32+
require.NoError(t, attestor.Attest(ctx))
33+
for _, env := range origVars {
34+
origKey, _ := splitVariable(env)
35+
if _, inBlockList := attestor.blockList[origKey]; inBlockList {
36+
require.NotContains(t, attestor.Variables, origKey)
37+
} else {
38+
require.Contains(t, attestor.Variables, origKey)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)