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 15package rules 16 17import ( 18 "go/ast" 19 "go/types" 20 21 "github.com/securego/gosec/v2" 22) 23 24type subprocess struct { 25 gosec.MetaData 26 gosec.CallList 27} 28 29func (r *subprocess) ID() string { 30 return r.MetaData.ID 31} 32 33// TODO(gm) The only real potential for command injection with a Go project 34// is something like this: 35// 36// syscall.Exec("/bin/sh", []string{"-c", tainted}) 37// 38// E.g. Input is correctly escaped but the execution context being used 39// is unsafe. For example: 40// 41// syscall.Exec("echo", "foobar" + tainted) 42func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { 43 if node := r.ContainsPkgCallExpr(n, c, false); node != nil { 44 args := node.Args 45 if r.isContext(n, c) { 46 args = args[1:] 47 } 48 for _, arg := range args { 49 if ident, ok := arg.(*ast.Ident); ok { 50 obj := c.Info.ObjectOf(ident) 51 if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) { 52 return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with variable", gosec.Medium, gosec.High), nil 53 } 54 } else if !gosec.TryResolve(arg, c) { 55 // the arg is not a constant or a variable but instead a function call or os.Args[i] 56 return gosec.NewIssue(c, n, r.ID(), "Subprocess launched with function call as argument or cmd arguments", gosec.Medium, gosec.High), nil 57 } 58 } 59 } 60 return nil, nil 61} 62 63// isContext checks whether or not the node is a CommandContext call or not 64// Thi is requried in order to skip the first argument from the check. 65func (r *subprocess) isContext(n ast.Node, ctx *gosec.Context) bool { 66 selector, indent, err := gosec.GetCallInfo(n, ctx) 67 if err != nil { 68 return false 69 } 70 if selector == "exec" && indent == "CommandContext" { 71 return true 72 } 73 return false 74} 75 76// NewSubproc detects cases where we are forking out to an external process 77func NewSubproc(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { 78 rule := &subprocess{gosec.MetaData{ID: id}, gosec.NewCallList()} 79 rule.Add("os/exec", "Command") 80 rule.Add("os/exec", "CommandContext") 81 rule.Add("syscall", "Exec") 82 rule.Add("syscall", "ForkExec") 83 rule.Add("syscall", "StartProcess") 84 return rule, []ast.Node{(*ast.CallExpr)(nil)} 85} 86