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  }