gopkg.in/alecthomas/gometalinter.v3@v3.0.0/_linters/src/github.com/securego/gosec/rules/sql.go (about) 1 // (c) Copyright 2016 Hewlett Packard Enterprise Development LP 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 rules 16 17 import ( 18 "go/ast" 19 "regexp" 20 21 "github.com/securego/gosec" 22 ) 23 24 type sqlStatement struct { 25 gosec.MetaData 26 27 // Contains a list of patterns which must all match for the rule to match. 28 patterns []*regexp.Regexp 29 } 30 31 func (s *sqlStatement) ID() string { 32 return s.MetaData.ID 33 } 34 35 // See if the string matches the patterns for the statement. 36 func (s *sqlStatement) MatchPatterns(str string) bool { 37 for _, pattern := range s.patterns { 38 if !pattern.MatchString(str) { 39 return false 40 } 41 } 42 return true 43 } 44 45 type sqlStrConcat struct { 46 sqlStatement 47 } 48 49 func (s *sqlStrConcat) ID() string { 50 return s.MetaData.ID 51 } 52 53 // see if we can figure out what it is 54 func (s *sqlStrConcat) checkObject(n *ast.Ident) bool { 55 if n.Obj != nil { 56 return n.Obj.Kind != ast.Var && n.Obj.Kind != ast.Fun 57 } 58 return false 59 } 60 61 // Look for "SELECT * FROM table WHERE " + " ' OR 1=1" 62 func (s *sqlStrConcat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { 63 if node, ok := n.(*ast.BinaryExpr); ok { 64 if start, ok := node.X.(*ast.BasicLit); ok { 65 if str, e := gosec.GetString(start); e == nil { 66 if !s.MatchPatterns(str) { 67 return nil, nil 68 } 69 if _, ok := node.Y.(*ast.BasicLit); ok { 70 return nil, nil // string cat OK 71 } 72 if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second) { 73 return nil, nil 74 } 75 return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil 76 } 77 } 78 } 79 return nil, nil 80 } 81 82 // NewSQLStrConcat looks for cases where we are building SQL strings via concatenation 83 func NewSQLStrConcat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { 84 return &sqlStrConcat{ 85 sqlStatement: sqlStatement{ 86 patterns: []*regexp.Regexp{ 87 regexp.MustCompile(`(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) `), 88 }, 89 MetaData: gosec.MetaData{ 90 ID: id, 91 Severity: gosec.Medium, 92 Confidence: gosec.High, 93 What: "SQL string concatenation", 94 }, 95 }, 96 }, []ast.Node{(*ast.BinaryExpr)(nil)} 97 } 98 99 type sqlStrFormat struct { 100 sqlStatement 101 calls gosec.CallList 102 noIssue gosec.CallList 103 } 104 105 // Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)" 106 func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { 107 108 // argIndex changes the function argument which gets matched to the regex 109 argIndex := 0 110 111 // TODO(gm) improve confidence if database/sql is being used 112 if node := s.calls.ContainsCallExpr(n, c); node != nil { 113 // if the function is fmt.Fprintf, search for SQL statement in Args[1] instead 114 if sel, ok := node.Fun.(*ast.SelectorExpr); ok { 115 if sel.Sel.Name == "Fprintf" { 116 // if os.Stderr or os.Stdout is in Arg[0], mark as no issue 117 if arg, ok := node.Args[0].(*ast.SelectorExpr); ok { 118 if ident, ok := arg.X.(*ast.Ident); ok { 119 if s.noIssue.Contains(ident.Name, arg.Sel.Name) { 120 return nil, nil 121 } 122 } 123 } 124 // the function is Fprintf so set argIndex = 1 125 argIndex = 1 126 } 127 } 128 // concats callexpr arg strings together if needed before regex evaluation 129 if argExpr, ok := node.Args[argIndex].(*ast.BinaryExpr); ok { 130 if fullStr, ok := gosec.ConcatString(argExpr); ok { 131 if s.MatchPatterns(fullStr) { 132 return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), 133 nil 134 } 135 } 136 } 137 138 if arg, e := gosec.GetString(node.Args[argIndex]); s.MatchPatterns(arg) && e == nil { 139 return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil 140 } 141 } 142 return nil, nil 143 } 144 145 // NewSQLStrFormat looks for cases where we're building SQL query strings using format strings 146 func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { 147 rule := &sqlStrFormat{ 148 calls: gosec.NewCallList(), 149 noIssue: gosec.NewCallList(), 150 sqlStatement: sqlStatement{ 151 patterns: []*regexp.Regexp{ 152 regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "), 153 regexp.MustCompile("%[^bdoxXfFp]"), 154 }, 155 MetaData: gosec.MetaData{ 156 ID: id, 157 Severity: gosec.Medium, 158 Confidence: gosec.High, 159 What: "SQL string formatting", 160 }, 161 }, 162 } 163 rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln", "Fprintf") 164 rule.noIssue.AddAll("os", "Stdout", "Stderr") 165 return rule, []ast.Node{(*ast.CallExpr)(nil)} 166 }