1package rules 2 3import ( 4 "go/ast" 5 "go/types" 6 7 "github.com/securego/gosec/v2" 8) 9 10type ssrf struct { 11 gosec.MetaData 12 gosec.CallList 13} 14 15// ID returns the identifier for this rule 16func (r *ssrf) ID() string { 17 return r.MetaData.ID 18} 19 20// ResolveVar tries to resolve the first argument of a call expression 21// The first argument is the url 22func (r *ssrf) ResolveVar(n *ast.CallExpr, c *gosec.Context) bool { 23 if len(n.Args) > 0 { 24 arg := n.Args[0] 25 if ident, ok := arg.(*ast.Ident); ok { 26 obj := c.Info.ObjectOf(ident) 27 if _, ok := obj.(*types.Var); ok { 28 scope := c.Pkg.Scope() 29 if scope != nil && scope.Lookup(ident.Name) != nil { 30 // a URL defined in a variable at package scope can be changed at any time 31 return true 32 } 33 if !gosec.TryResolve(ident, c) { 34 return true 35 } 36 } 37 } 38 } 39 return false 40} 41 42// Match inspects AST nodes to determine if certain net/http methods are called with variable input 43func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) { 44 // Call expression is using http package directly 45 if node := r.ContainsPkgCallExpr(n, c, false); node != nil { 46 if r.ResolveVar(node, c) { 47 return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil 48 } 49 } 50 return nil, nil 51} 52 53// NewSSRFCheck detects cases where HTTP requests are sent 54func NewSSRFCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) { 55 rule := &ssrf{ 56 CallList: gosec.NewCallList(), 57 MetaData: gosec.MetaData{ 58 ID: id, 59 What: "Potential HTTP request made with variable url", 60 Severity: gosec.Medium, 61 Confidence: gosec.Medium, 62 }, 63 } 64 rule.AddAll("net/http", "Do", "Get", "Head", "Post", "PostForm", "RoundTrip") 65 return rule, []ast.Node{(*ast.CallExpr)(nil)} 66} 67