mirror of
https://github.com/securego/gosec.git
synced 2024-11-05 19:45:51 +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"
|
||||
)
|
||||
|
||||
// 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.
|
||||
// It is passed through to all rule functions as they are called. Rules may use
|
||||
// this data in conjunction withe the encoutered AST node.
|
||||
|
@ -37,6 +44,7 @@ type Context struct {
|
|||
Pkg *types.Package
|
||||
Root *ast.File
|
||||
Config map[string]interface{}
|
||||
Imports *ImportInfo
|
||||
}
|
||||
|
||||
// The Rule interface used by all rules supported by GAS.
|
||||
|
@ -77,7 +85,19 @@ func NewAnalyzer(conf map[string]interface{}, logger *log.Logger) Analyzer {
|
|||
ignoreNosec: conf["ignoreNosec"].(bool),
|
||||
ruleset: make(RuleSet),
|
||||
Issues: make([]Issue, 0),
|
||||
context: Context{token.NewFileSet(), nil, nil, nil, nil, nil},
|
||||
context: Context{
|
||||
token.NewFileSet(),
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
&ImportInfo{
|
||||
make(map[string]string),
|
||||
make(map[string]string),
|
||||
make(map[string]bool),
|
||||
},
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
|
@ -110,6 +130,10 @@ func (gas *Analyzer) process(filename string, source interface{}) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, pkg := range gas.context.Pkg.Imports() {
|
||||
gas.context.Imports.Imported[pkg.Path()] = pkg.Name()
|
||||
}
|
||||
|
||||
ast.Walk(gas, root)
|
||||
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.
|
||||
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
||||
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 {
|
||||
for _, rule := range val {
|
||||
ret, err := rule.Match(n, &gas.context)
|
||||
|
|
|
@ -47,19 +47,45 @@ func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
|||
return nil
|
||||
}
|
||||
|
||||
// MatchCallByObject ses the type checker to resolve the associated object with a
|
||||
// particular *ast.CallExpr. This object is used to determine if the
|
||||
// package and identifier name matches the passed in parameters.
|
||||
// MatchCallByPackage ensures that the specified package is imported,
|
||||
// adjusts the name for any aliases and ignores cases that are
|
||||
// initialization only imports.
|
||||
//
|
||||
// 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) {
|
||||
call, obj := GetCallObject(n, c)
|
||||
if obj != nil && obj.Pkg().Path() == pkg && obj.Name() == name {
|
||||
return call, obj
|
||||
func MatchCallByPackage(n ast.Node, c *Context, pkg string, names ...string) (*ast.CallExpr, bool) {
|
||||
|
||||
importName, imported := c.Imports.Imported[pkg]
|
||||
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.
|
||||
|
|
|
@ -27,7 +27,7 @@ type BlacklistImport struct {
|
|||
|
||||
func (r *BlacklistImport) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
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 (
|
||||
"go/ast"
|
||||
"regexp"
|
||||
|
||||
gas "github.com/GoASTScanner/gas/core"
|
||||
)
|
||||
|
||||
type UsesWeakCryptography struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
blacklist map[string][]string
|
||||
}
|
||||
|
||||
func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
if node := gas.MatchCall(n, r.pattern); node != 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
|
||||
}
|
||||
|
||||
// Uses des.* md5.* or rc4.*
|
||||
func NewUsesWeakCryptography(conf map[string]interface{}) (r gas.Rule, n ast.Node) {
|
||||
r = &UsesWeakCryptography{
|
||||
pattern: regexp.MustCompile(`des\.NewCipher|des\.NewTripleDESCipher|md5\.New|md5\.Sum|rc4\.NewCipher`),
|
||||
func NewUsesWeakCryptography(conf map[string]interface{}) (gas.Rule, ast.Node) {
|
||||
calls := make(map[string][]string)
|
||||
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
|
||||
calls["crypto/md5"] = []string{"New", "Sum"}
|
||||
calls["crypto/rc4"] = []string{"NewCipher"}
|
||||
rule := &UsesWeakCryptography{
|
||||
blacklist: calls,
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "Use of weak cryptographic primitive",
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
return rule, (*ast.CallExpr)(nil)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue