-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhardcoded.go
136 lines (123 loc) · 3.51 KB
/
hardcoded.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
// Copyright 2018 Terence Tarvis. All rights reserved.
//
package main
import (
"go/ast"
"go/token"
"encoding/hex"
"encoding/base64"
"regexp"
"strings"
)
func init() {
register("hardcoded",
"this is a test to look for suspected hardcoded credentials",
hardcodedCheck,
assignStmt, genDecl)
}
// isHighEntropy checks for string encoding to
// properly decode it and then measures its entropy
func isHighEntropy(s *string) bool {
// entropyN is normalised entropy
var entropy, entropyN float64;
// if there is an error decoding, it could
// be that the string matched the regular expression
// for that encoding type but does not properly decode.
// So, these reverse the normal situation.
// if there isn't an error, it won't fallthrough;
switch {
case isHex(*s):
buf, err := hex.DecodeString(*s);
if err == nil {
entropy, entropyN = H(buf);
break;
}
fallthrough;
case isBase64(*s):
buf, err := base64.StdEncoding.DecodeString(*s);
if err == nil {
entropy, entropyN = H(buf);
break;
}
fallthrough;
default:
buf := []byte(*s);
entropy, entropyN = H(buf);
}
// these values, 2.5 and .98 are set through experiment
// consider changing these or being more rigorous here
if((entropy > 2.5) && (entropyN > .98)) {
return true;
}
return false;
}
// isCommonCred checks for commonly used credentials
// This check doesn't try to be exhaustive.
func isCommonCred(s *string) bool {
credPatterns := []string{"password", "p4ssword", "123456", "letmein", "admin", "abc123", "passw0rd", "pwd"};
val := strings.ToLower(*s);
for _, str := range credPatterns {
re := regexp.MustCompile(str);
if matches := re.MatchString(val); matches {
return true;
}
}
return false;
}
// checkSuspectVal runs essentially the same checks on a suspect value
// to see if it may be a credential.
func checkSuspectVal(f *File, basicLit *ast.BasicLit) {
// strip quotes from input
suspectVal := basicLit.Value;
// todo: is this the best way to handle this?
// under certain conditions, the below slice assignment contains
// bounds that are out of range
if(string(suspectVal[0]) == `"`) {
suspectVal = suspectVal[1: len(suspectVal) -1];
}
// now check suspectVal
if isCommonCred(&suspectVal) {
f.Reportf(basicLit.Pos(), "Possible credential found: %s", suspectVal);
return;
}
if isHighEntropy(&suspectVal) {
f.Reportf(basicLit.Pos(), "Possible credential found: %s", suspectVal);
return;
}
return;
}
// checkAssignStmt starts with an AssignStmt, checks if
func checkAssignStmt(f *File, node *ast.AssignStmt) {
for _, expr := range node.Rhs {
if basicLit, ok := expr.(*ast.BasicLit); ok {
checkSuspectVal(f, basicLit);
}
}
}
// checkGenDecl starts with a GenDecl, checks if it is a const or a var
// then goes through its specs, then converts them to ValueSpecs
// then takes the Exprs from the ValueSpec and converts those to
// BasicLits and then finally takes the actual string values for testing
// todo: could add check on the variable names first but this is debatable
func checkGenDecl(f *File, node *ast.GenDecl) {
if (node.Tok == token.CONST || node.Tok == token.VAR) {
for _, spec := range node.Specs {
if valSpec, ok := spec.(*ast.ValueSpec); ok {
for _, expr := range valSpec.Values {
if basicLit, ok := expr.(*ast.BasicLit); ok {
checkSuspectVal(f, basicLit);
}
}
}
}
}
}
func hardcodedCheck(f *File, node ast.Node) {
switch t := node.(type) {
case *ast.AssignStmt:
checkAssignStmt(f, t);
case *ast.GenDecl:
checkGenDecl(f, t);
}
return;
}