mirror of
https://github.com/securego/gosec.git
synced 2024-12-25 12:05:52 +00:00
Merge pull request #75 from GoASTScanner/experimental
Track package imports and aliases
This commit is contained in:
commit
9f54d257fe
5 changed files with 128 additions and 21 deletions
|
@ -27,6 +27,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ImportInfo is used to track aliased and initialization only imports.
|
||||||
|
type ImportInfo struct {
|
||||||
|
Imported map[string]string
|
||||||
|
Aliased map[string]string
|
||||||
|
InitOnly map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
// The Context is populated with data parsed from the source code as it is scanned.
|
// The Context is populated with data parsed from the source code as it is scanned.
|
||||||
// It is passed through to all rule functions as they are called. Rules may use
|
// It is passed through to all rule functions as they are called. Rules may use
|
||||||
// this data in conjunction withe the encoutered AST node.
|
// this data in conjunction withe the encoutered AST node.
|
||||||
|
@ -37,6 +44,7 @@ type Context struct {
|
||||||
Pkg *types.Package
|
Pkg *types.Package
|
||||||
Root *ast.File
|
Root *ast.File
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
|
Imports *ImportInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
// The Rule interface used by all rules supported by GAS.
|
// The Rule interface used by all rules supported by GAS.
|
||||||
|
@ -77,8 +85,20 @@ func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
|
||||||
ignoreNosec: conf["ignoreNosec"].(bool),
|
ignoreNosec: conf["ignoreNosec"].(bool),
|
||||||
ruleset: make(RuleSet),
|
ruleset: make(RuleSet),
|
||||||
Issues: make([]Issue, 0),
|
Issues: make([]Issue, 0),
|
||||||
context: Context{token.NewFileSet(), nil, nil, nil, nil, nil},
|
context: Context{
|
||||||
logger: logger,
|
token.NewFileSet(),
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
&ImportInfo{
|
||||||
|
make(map[string]string),
|
||||||
|
make(map[string]string),
|
||||||
|
make(map[string]bool),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(tkelsey): use the inc/exc lists
|
// TODO(tkelsey): use the inc/exc lists
|
||||||
|
@ -110,6 +130,10 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, pkg := range gas.context.Pkg.Imports() {
|
||||||
|
gas.context.Imports.Imported[pkg.Path()] = pkg.Name()
|
||||||
|
}
|
||||||
|
|
||||||
ast.Walk(gas, root)
|
ast.Walk(gas, root)
|
||||||
gas.Stats.NumFiles++
|
gas.Stats.NumFiles++
|
||||||
}
|
}
|
||||||
|
@ -169,6 +193,20 @@ func (gas *Analyzer) ignore(n ast.Node) bool {
|
||||||
// Rule methods added with AddRule will be invoked as necessary.
|
// Rule methods added with AddRule will be invoked as necessary.
|
||||||
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
||||||
if !gas.ignore(n) {
|
if !gas.ignore(n) {
|
||||||
|
|
||||||
|
// Track aliased and initialization imports
|
||||||
|
if imported, ok := n.(*ast.ImportSpec); ok {
|
||||||
|
if imported.Name != nil {
|
||||||
|
path := strings.Trim(imported.Path.Value, `"`)
|
||||||
|
if imported.Name.Name == "_" {
|
||||||
|
// Initialization import
|
||||||
|
gas.context.Imports.InitOnly[path] = true
|
||||||
|
} else {
|
||||||
|
// Aliased import
|
||||||
|
gas.context.Imports.Aliased[imported.Name.Name] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
|
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
|
||||||
for _, rule := range val {
|
for _, rule := range val {
|
||||||
ret, err := rule.Match(n, &gas.context)
|
ret, err := rule.Match(n, &gas.context)
|
||||||
|
|
|
@ -47,19 +47,45 @@ func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchCallByObject ses the type checker to resolve the associated object with a
|
// MatchCallByPackage ensures that the specified package is imported,
|
||||||
// particular *ast.CallExpr. This object is used to determine if the
|
// adjusts the name for any aliases and ignores cases that are
|
||||||
// package and identifier name matches the passed in parameters.
|
// initialization only imports.
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
// node, obj := MatchCall(n, ctx, "math/rand", "Read")
|
// node, matched := MatchCallByPackage(n, ctx, "math/rand", "Read")
|
||||||
//
|
//
|
||||||
func MatchCallByObject(n ast.Node, c *Context, pkg, name string) (*ast.CallExpr, types.Object) {
|
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
|
||||||
call, obj := GetCallObject(n, c)
|
|
||||||
if obj != nil && obj.Pkg().Path() == pkg && obj.Name() == name {
|
importName, imported := c.Imports.Imported[pkg]
|
||||||
return call, obj
|
if !imported {
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
return nil, nil
|
|
||||||
|
if _, initonly := c.Imports.InitOnly[pkg]; initonly {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
if alias, ok := c.Imports.Aliased[pkg]; ok {
|
||||||
|
importName = alias
|
||||||
|
}
|
||||||
|
|
||||||
|
switch node := n.(type) {
|
||||||
|
case *ast.CallExpr:
|
||||||
|
switch fn := node.Fun.(type) {
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
switch expr := fn.X.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
if expr.Name == importName {
|
||||||
|
for _, name := range names {
|
||||||
|
if fn.Sel.Name == name {
|
||||||
|
return node, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchCompLit will match an ast.CompositeLit if its string value obays the given regex.
|
// MatchCompLit will match an ast.CompositeLit if its string value obays the given regex.
|
||||||
|
|
|
@ -27,7 +27,7 @@ type BlacklistImport struct {
|
||||||
|
|
||||||
func (r *BlacklistImport) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (r *BlacklistImport) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||||
if node, ok := n.(*ast.ImportSpec); ok {
|
if node, ok := n.(*ast.ImportSpec); ok {
|
||||||
if r.Path == node.Path.Value {
|
if r.Path == node.Path.Value && node.Name.String() != "_" {
|
||||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
38
rules/blacklist_test.go
Normal file
38
rules/blacklist_test.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package rules
|
||||||
|
|
||||||
|
import (
|
||||||
|
gas "github.com/GoASTScanner/gas/core"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const initOnlyImportSrc = `
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
_ "crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
for _, arg := range os.Args {
|
||||||
|
fmt.Println(arg)
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
|
||||||
|
func TestInitOnlyImport(t *testing.T) {
|
||||||
|
config := map[string]interface{}{"ignoreNosec": false}
|
||||||
|
analyzer := gas.NewAnalyzer(config, nil)
|
||||||
|
analyzer.AddRule(NewBlacklist_crypto_md5(config))
|
||||||
|
issues := gasTestRunner(initOnlyImportSrc, analyzer)
|
||||||
|
checkTestResults(t, issues, 0, "")
|
||||||
|
}
|
|
@ -16,33 +16,38 @@ package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
gas "github.com/GoASTScanner/gas/core"
|
gas "github.com/GoASTScanner/gas/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsesWeakCryptography struct {
|
type UsesWeakCryptography struct {
|
||||||
gas.MetaData
|
gas.MetaData
|
||||||
pattern *regexp.Regexp
|
blacklist map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
|
||||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
for pkg, funcs := range r.blacklist {
|
||||||
|
if _, matched := gas.MatchCallByPackage(n, c, pkg, funcs...); matched {
|
||||||
|
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uses des.* md5.* or rc4.*
|
// Uses des.* md5.* or rc4.*
|
||||||
func NewUsesWeakCryptography(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
|
func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
|
||||||
r = &UsesWeakCryptography{
|
calls := make(map[string][]string)
|
||||||
pattern: regexp.MustCompile(`des\.NewCipher|des\.NewTripleDESCipher|md5\.New|md5\.Sum|rc4\.NewCipher`),
|
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
|
||||||
|
calls["crypto/md5"] = []string{"New", "Sum"}
|
||||||
|
calls["crypto/rc4"] = []string{"NewCipher"}
|
||||||
|
rule := &UsesWeakCryptography{
|
||||||
|
blacklist: calls,
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
Severity: gas.Medium,
|
Severity: gas.Medium,
|
||||||
Confidence: gas.High,
|
Confidence: gas.High,
|
||||||
What: "Use of weak cryptographic primitive",
|
What: "Use of weak cryptographic primitive",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
n = (*ast.CallExpr)(nil)
|
return rule, (*ast.CallExpr)(nil)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue