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