mirror of
https://github.com/securego/gosec.git
synced 2026-01-15 01:33:41 +08:00
feat(rules): enhance subprocess variable checks (#1453)
* feat(rules): enhance subprocess variable checks * ast.Object is deprecated
This commit is contained in:
@@ -16,6 +16,7 @@ package rules
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
|
||||||
"github.com/securego/gosec/v2"
|
"github.com/securego/gosec/v2"
|
||||||
@@ -31,6 +32,29 @@ func (r *subprocess) ID() string {
|
|||||||
return r.MetaData.ID
|
return r.MetaData.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getEnclosingBodyStart returns the position of the '{' for the innermost function body enclosing the given position.
|
||||||
|
// Returns token.NoPos if no enclosing body found.
|
||||||
|
func getEnclosingBodyStart(pos token.Pos, ctx *gosec.Context) token.Pos {
|
||||||
|
if ctx.Root == nil {
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
var bodyStart token.Pos
|
||||||
|
ast.Inspect(ctx.Root, func(n ast.Node) bool {
|
||||||
|
var body *ast.BlockStmt
|
||||||
|
switch f := n.(type) {
|
||||||
|
case *ast.FuncDecl:
|
||||||
|
body = f.Body
|
||||||
|
case *ast.FuncLit:
|
||||||
|
body = f.Body
|
||||||
|
}
|
||||||
|
if body != nil && body.Pos() <= pos && pos < body.End() && body.Lbrace.IsValid() {
|
||||||
|
bodyStart = body.Lbrace
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return bodyStart
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(gm) The only real potential for command injection with a Go project
|
// TODO(gm) The only real potential for command injection with a Go project
|
||||||
// is something like this:
|
// is something like this:
|
||||||
//
|
//
|
||||||
@@ -46,49 +70,27 @@ func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*issue.Issue, error) {
|
|||||||
if r.isContext(n, c) {
|
if r.isContext(n, c) {
|
||||||
args = args[1:]
|
args = args[1:]
|
||||||
}
|
}
|
||||||
for _, arg := range args {
|
for i, arg := range args {
|
||||||
if ident, ok := arg.(*ast.Ident); ok {
|
if ident, ok := arg.(*ast.Ident); ok {
|
||||||
obj := c.Info.ObjectOf(ident)
|
obj := c.Info.ObjectOf(ident)
|
||||||
|
if v, ok := obj.(*types.Var); ok {
|
||||||
// need to cast and check whether it is for a variable ?
|
// Special case: struct fields OR function parameters/receivers used as executable name (i==0) -> skip
|
||||||
_, variable := obj.(*types.Var)
|
if i == 0 {
|
||||||
|
if v.IsField() {
|
||||||
// .. indeed it is a variable then processing is different than a normal
|
continue
|
||||||
// field assignment
|
}
|
||||||
if variable {
|
bodyStart := getEnclosingBodyStart(ident.Pos(), c)
|
||||||
// skip the check when the declaration is not available
|
if bodyStart != token.NoPos && obj.Pos() < bodyStart {
|
||||||
if ident.Obj == nil {
|
continue // Parameter or receiver (declared before body brace)
|
||||||
continue
|
}
|
||||||
}
|
}
|
||||||
switch ident.Obj.Decl.(type) {
|
// For all variables: flag if not resolvable to a constant
|
||||||
case *ast.AssignStmt:
|
if !gosec.TryResolve(ident, c) {
|
||||||
_, assignment := ident.Obj.Decl.(*ast.AssignStmt)
|
return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil
|
||||||
if variable && assignment {
|
|
||||||
if !gosec.TryResolve(ident, c) {
|
|
||||||
return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.Field:
|
|
||||||
_, field := ident.Obj.Decl.(*ast.Field)
|
|
||||||
if variable && field {
|
|
||||||
// check if the variable exist in the scope
|
|
||||||
vv, vvok := obj.(*types.Var)
|
|
||||||
|
|
||||||
if vvok && vv.Parent().Lookup(ident.Name) == nil {
|
|
||||||
return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
_, valueSpec := ident.Obj.Decl.(*ast.ValueSpec)
|
|
||||||
if variable && valueSpec {
|
|
||||||
if !gosec.TryResolve(ident, c) {
|
|
||||||
return c.NewIssue(n, r.ID(), "Subprocess launched with variable", issue.Medium, issue.High), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !gosec.TryResolve(arg, c) {
|
} else if !gosec.TryResolve(arg, c) {
|
||||||
// the arg is not a constant or a variable but instead a function call or os.Args[i]
|
// Non-identifier arguments that cannot be resolved
|
||||||
return c.NewIssue(n, r.ID(), "Subprocess launched with a potential tainted input or cmd arguments", issue.Medium, issue.High), nil
|
return c.NewIssue(n, r.ID(), "Subprocess launched with a potential tainted input or cmd arguments", issue.Medium, issue.High), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -278,4 +278,41 @@ func main() {
|
|||||||
_ = exec.Command(exe, args...)
|
_ = exec.Command(exe, args...)
|
||||||
}
|
}
|
||||||
`}, 0, gosec.NewConfig()},
|
`}, 0, gosec.NewConfig()},
|
||||||
|
{[]string{`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Direct use of a function parameter in exec.Command.
|
||||||
|
// This is clearly tainted input (parameter from caller, potentially user-controlled).
|
||||||
|
func vulnerable(command string) {
|
||||||
|
// Dangerous pattern: passing unsanitized input to a shell
|
||||||
|
_ = exec.Command("bash", "-c", command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// In real scenarios, this could be user input (e.g., via flag, HTTP param, etc.)
|
||||||
|
vulnerable("echo safe")
|
||||||
|
}
|
||||||
|
`}, 1, gosec.NewConfig()},
|
||||||
|
{[]string{`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Indirect use: assign parameter to local variable before use.
|
||||||
|
// Included for comparison/regression testing.
|
||||||
|
func vulnerable(command string) {
|
||||||
|
cmdStr := command // local assignment
|
||||||
|
_ = exec.Command("bash", "-c", cmdStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
vulnerable("echo safe")
|
||||||
|
}
|
||||||
|
`}, 1, gosec.NewConfig()},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user