Skip to content

Commit

Permalink
feat: block known sensitive environment variables
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
mikhailswift committed May 23, 2022
1 parent b55fb8b commit 5d7bc95
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 32 deletions.
19 changes: 15 additions & 4 deletions pkg/attestation/commandrun/commandrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"os/exec"

"github.com/testifysec/witness/pkg/attestation"
"github.com/testifysec/witness/pkg/attestation/environment"
"github.com/testifysec/witness/pkg/cryptoutil"
)

Expand Down Expand Up @@ -62,8 +63,17 @@ func WithSilent(silent bool) Option {
}
}

func WithEnvironmentBlockList(blockList map[string]struct{}) Option {
return func(cr *CommandRun) {
cr.environmentBlockList = blockList
}
}

func New(opts ...Option) *CommandRun {
cr := &CommandRun{}
cr := &CommandRun{
environmentBlockList: environment.DefaultBlockList(),
}

for _, opt := range opts {
opt(cr)
}
Expand Down Expand Up @@ -91,9 +101,10 @@ type CommandRun struct {
ExitCode int `json:"exitcode"`
Processes []ProcessInfo `json:"processes,omitempty"`

silent bool
materials map[string]cryptoutil.DigestSet
enableTracing bool
silent bool
materials map[string]cryptoutil.DigestSet
enableTracing bool
environmentBlockList map[string]struct{}
}

func (rc *CommandRun) Attest(ctx *attestation.AttestationContext) error {
Expand Down
38 changes: 22 additions & 16 deletions pkg/attestation/commandrun/tracing_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"strings"

"github.com/testifysec/witness/pkg/attestation"
"github.com/testifysec/witness/pkg/attestation/environment"
"github.com/testifysec/witness/pkg/cryptoutil"
"github.com/testifysec/witness/pkg/log"
"golang.org/x/sys/unix"
Expand All @@ -37,11 +38,12 @@ const (
)

type ptraceContext struct {
parentPid int
mainProgram string
processes map[int]*ProcessInfo
exitCode int
hash []crypto.Hash
parentPid int
mainProgram string
processes map[int]*ProcessInfo
exitCode int
hash []crypto.Hash
environmentBlockList map[string]struct{}
}

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

func (r *CommandRun) trace(c *exec.Cmd, actx *attestation.AttestationContext) ([]ProcessInfo, error) {
pctx := &ptraceContext{
parentPid: c.Process.Pid,
mainProgram: c.Path,
processes: make(map[int]*ProcessInfo),
hash: actx.Hashes(),
parentPid: c.Process.Pid,
mainProgram: c.Path,
processes: make(map[int]*ProcessInfo),
hash: actx.Hashes(),
environmentBlockList: r.environmentBlockList,
}

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

environ, err := os.ReadFile(envinLocation)
if err == nil {
procInfo.Environ = cleanString(string(environ))
allVars := strings.Split(string(environ), "\x00")
filteredEnviron := make([]string, 0)
environment.FilterEnvironmentArray(allVars, p.environmentBlockList, func(_, _, varStr string) {
filteredEnviron = append(filteredEnviron, varStr)
})

procInfo.Environ = strings.Join(filteredEnviron, " ")
}

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

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

func getPPIDFromStatus(status []byte) (int, error) {
statusStr := string(status)

lines := strings.Split(statusStr, "\n")

for _, line := range lines {
if strings.Contains(line, "PPid:") {
parts := strings.Split(line, ":")
ppid := strings.TrimSpace(parts[1])
return strconv.Atoi(ppid)
}
}

return 0, nil
}

func getSpecBypassIsVulnFromStatus(status []byte) bool {
statusStr := string(status)

lines := strings.Split(statusStr, "\n")

for _, line := range lines {
if strings.Contains(line, "Speculation_Store_Bypass:") {
parts := strings.Split(line, ":")
isVuln := strings.TrimSpace(parts[1])
if strings.Contains(isVuln, "vulnerable") {
return true
}

}
}

return false
}
112 changes: 112 additions & 0 deletions pkg/attestation/environment/blocklist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2021 The Witness Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package environment

// sourced from https://github.com/Puliczek/awesome-list-of-secrets-in-environment-variables/blob/main/raw_list.txt
func DefaultBlockList() map[string]struct{} {
return map[string]struct{}{
"AWS_ACCESS_KEY_ID": {},
"AWS_SECRET_ACCESS_KEY": {},
"AMAZON_AWS_ACCESS_KEY_ID": {},
"AMAZON_AWS_SECRET_ACCESS_KEY": {},
"ALGOLIA_API_KEY": {},
"AZURE_CLIENT_ID": {},
"AZURE_CLIENT_SECRET": {},
"AZURE_USERNAME": {},
"AZURE_PASSWORD": {},
"MSI_ENDPOINT": {},
"MSI_SECRET": {},
"binance_api": {},
"binance_secret": {},
"BITTREX_API_KEY": {},
"BITTREX_API_SECRET": {},
"CF_PASSWORD": {},
"CF_USERNAME": {},
"CODECLIMATE_REPO_TOKEN": {},
"COVERALLS_REPO_TOKEN": {},
"CIRCLE_TOKEN": {},
"DIGITALOCEAN_ACCESS_TOKEN": {},
"DOCKER_EMAIL": {},
"DOCKER_PASSWORD": {},
"DOCKER_USERNAME": {},
"DOCKERHUB_PASSWORD": {},
"FACEBOOK_APP_ID": {},
"FACEBOOK_APP_SECRET": {},
"FACEBOOK_ACCESS_TOKEN": {},
"FIREBASE_TOKEN": {},
"FOSSA_API_KEY": {},
"GH_TOKEN": {},
"GH_ENTERPRISE_TOKEN": {},
"GOOGLE_APPLICATION_CREDENTIALS": {},
"GOOGLE_API_KEY": {},
"CI_DEPLOY_USER": {},
"CI_DEPLOY_PASSWORD": {},
"GITLAB_USER_LOGIN": {},
"CI_JOB_JWT": {},
"CI_JOB_JWT_V2": {},
"CI_JOB_TOKEN": {},
"HEROKU_API_KEY": {},
"HEROKU_API_USER": {},
"MAILGUN_API_KEY": {},
"MCLI_PRIVATE_API_KEY": {},
"MCLI_PUBLIC_API_KEY": {},
"NGROK_TOKEN": {},
"NGROK_AUTH_TOKEN": {},
"NPM_AUTH_TOKEN": {},
"OKTA_CLIENT_ORGURL": {},
"OKTA_CLIENT_TOKEN": {},
"OKTA_OAUTH2_CLIENTSECRET": {},
"OKTA_OAUTH2_CLIENTID": {},
"OKTA_AUTHN_GROUPID": {},
"OS_USERNAME": {},
"OS_PASSWORD": {},
"PERCY_TOKEN": {},
"SAUCE_ACCESS_KEY": {},
"SAUCE_USERNAME": {},
"SENTRY_AUTH_TOKEN": {},
"SLACK_TOKEN": {},
"SNYK_TOKEN": {},
"square_access_token": {},
"square_oauth_secret": {},
"STRIPE_API_KEY": {},
"STRIPE_DEVICE_NAME": {},
"SURGE_TOKEN": {},
"SURGE_LOGIN": {},
"TWILIO_ACCOUNT_SID": {},
"CONSUMER_KEY": {},
"CONSUMER_SECRET": {},
"TRAVIS_SUDO": {},
"TRAVIS_OS_NAME": {},
"TRAVIS_SECURE_ENV_VARS": {},
"VAULT_TOKEN": {},
"VAULT_CLIENT_KEY": {},
"TOKEN": {},
"VULTR_ACCESS": {},
"VULTR_SECRET": {},
}
}

// FilterEnvironmentArray expects an array of strings representing environment variables. Each element of the array is expected to be in the format of "KEY=VALUE".
// 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.
func FilterEnvironmentArray(variables []string, blockList map[string]struct{}, onAllowed func(key, val, orig string)) {
for _, v := range variables {
key, val := splitVariable(v)
if _, inBlockList := blockList[key]; inBlockList {
continue
}

onAllowed(key, val, v)
}
}
46 changes: 34 additions & 12 deletions pkg/attestation/environment/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,28 @@ type Attestor struct {
Hostname string `json:"hostname"`
Username string `json:"username"`
Variables map[string]string `json:"variables,omitempty"`

blockList map[string]struct{}
}

type Option func(*Attestor)

func WithBlockList(blockList map[string]struct{}) Option {
return func(a *Attestor) {
a.blockList = blockList
}
}

func New() *Attestor {
return &Attestor{}
func New(opts ...Option) *Attestor {
attestor := &Attestor{
blockList: DefaultBlockList(),
}

for _, opt := range opts {
opt(attestor)
}

return attestor
}

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

variables := os.Environ()
for _, v := range variables {
parts := strings.SplitN(v, "=", 2)
key := parts[0]
val := ""
if len(parts) > 1 {
val = parts[1]
}

FilterEnvironmentArray(os.Environ(), a.blockList, func(key, val, _ string) {
a.Variables[key] = val
}
})

return nil
}

// splitVariable splits a string representing an environment variable in the format of
// "KEY=VAL" and returns the key and val separately.
func splitVariable(v string) (key, val string) {
parts := strings.SplitN(v, "=", 2)
key = parts[0]
if len(parts) > 1 {
val = parts[1]
}

return
}
41 changes: 41 additions & 0 deletions pkg/attestation/environment/environment_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2021 The Witness Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package environment

import (
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/testifysec/witness/pkg/attestation"
)

func TestEnvironment(t *testing.T) {
attestor := New()
ctx, err := attestation.NewContext([]attestation.Attestor{attestor})
require.NoError(t, err)

t.Setenv("AWS_ACCESS_KEY_ID", "super secret")
origVars := os.Environ()
require.NoError(t, attestor.Attest(ctx))
for _, env := range origVars {
origKey, _ := splitVariable(env)
if _, inBlockList := attestor.blockList[origKey]; inBlockList {
require.NotContains(t, attestor.Variables, origKey)
} else {
require.Contains(t, attestor.Variables, origKey)
}
}
}

0 comments on commit 5d7bc95

Please sign in to comment.