mirror of
https://github.com/securego/gosec.git
synced 2024-11-05 19:45:51 +00:00
Major rework of codebase
- Get rid of 'core' and move CLI to cmd/gas directory - Migrate (most) tests to use Ginkgo and testutils framework - GAS now expects package to reside in $GOPATH - GAS now can resolve dependencies for better type checking (if package on GOPATH) - Simplified public API
This commit is contained in:
parent
f4b705a864
commit
6943f9e5e4
51 changed files with 1189 additions and 2695 deletions
20
analyzer.go
20
analyzer.go
|
@ -18,9 +18,11 @@ package gas
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -64,10 +66,11 @@ type Analyzer struct {
|
||||||
// NewAnalyzer builds a new anaylzer.
|
// NewAnalyzer builds a new anaylzer.
|
||||||
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
|
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
|
||||||
ignoreNoSec := false
|
ignoreNoSec := false
|
||||||
if val, err := conf.Get("ignoreNoSec"); err == nil {
|
if setting, err := conf.GetGlobal("nosec"); err == nil {
|
||||||
if override, ok := val.(bool); ok {
|
ignoreNoSec = setting == "true" || setting == "enabled"
|
||||||
ignoreNoSec = override
|
}
|
||||||
}
|
if logger == nil {
|
||||||
|
logger = log.New(os.Stderr, "[gas]", log.LstdFlags)
|
||||||
}
|
}
|
||||||
return &Analyzer{
|
return &Analyzer{
|
||||||
ignoreNosec: ignoreNoSec,
|
ignoreNosec: ignoreNoSec,
|
||||||
|
@ -94,7 +97,7 @@ func (gas *Analyzer) Process(packagePath string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
packageConfig := loader.Config{Build: &build.Default}
|
packageConfig := loader.Config{Build: &build.Default, ParserMode: parser.ParseComments}
|
||||||
packageFiles := make([]string, 0)
|
packageFiles := make([]string, 0)
|
||||||
for _, filename := range basePackage.GoFiles {
|
for _, filename := range basePackage.GoFiles {
|
||||||
packageFiles = append(packageFiles, path.Join(packagePath, filename))
|
packageFiles = append(packageFiles, path.Join(packagePath, filename))
|
||||||
|
@ -168,3 +171,10 @@ func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
||||||
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
|
func (gas *Analyzer) Report() ([]*Issue, *Metrics) {
|
||||||
return gas.issues, gas.stats
|
return gas.issues, gas.stats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset clears state such as context, issues and metrics from the configured analyzer
|
||||||
|
func (gas *Analyzer) Reset() {
|
||||||
|
gas.context = &Context{}
|
||||||
|
gas.issues = make([]*Issue, 0, 16)
|
||||||
|
gas.stats = &Metrics{}
|
||||||
|
}
|
||||||
|
|
113
analyzer_test.go
Normal file
113
analyzer_test.go
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
"github.com/GoASTScanner/gas/rules"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas/testutils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Analyzer", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
analyzer *gas.Analyzer
|
||||||
|
logger *log.Logger
|
||||||
|
output *bytes.Buffer
|
||||||
|
)
|
||||||
|
BeforeEach(func() {
|
||||||
|
logger, output = testutils.NewLogger()
|
||||||
|
analyzer = gas.NewAnalyzer(nil, logger)
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when processing a package", func() {
|
||||||
|
|
||||||
|
It("should return an error if the package contains no Go files", func() {
|
||||||
|
analyzer.LoadRules(rules.Generate().Builders()...)
|
||||||
|
dir, err := ioutil.TempDir("", "empty")
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
err = analyzer.Process(dir)
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
Expect(err.Error()).Should(MatchRegexp("no buildable Go source files"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return an error if the package fails to build", func() {
|
||||||
|
analyzer.LoadRules(rules.Generate().Builders()...)
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
defer pkg.Close()
|
||||||
|
pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`)
|
||||||
|
pkg.Build()
|
||||||
|
|
||||||
|
err := analyzer.Process(pkg.Path)
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
Expect(err.Error()).Should(MatchRegexp(`expected 'package'`))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should find errors when nosec is not in use", func() {
|
||||||
|
|
||||||
|
// Rule for MD5 weak crypto usage
|
||||||
|
sample := testutils.SampleCodeG401[0]
|
||||||
|
source := sample.Code
|
||||||
|
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
|
||||||
|
|
||||||
|
controlPackage := testutils.NewTestPackage()
|
||||||
|
defer controlPackage.Close()
|
||||||
|
controlPackage.AddFile("md5.go", source)
|
||||||
|
controlPackage.Build()
|
||||||
|
analyzer.Process(controlPackage.Path)
|
||||||
|
controlIssues, _ := analyzer.Report()
|
||||||
|
Expect(controlIssues).Should(HaveLen(sample.Errors))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not report errors when a nosec comment is present", func() {
|
||||||
|
// Rule for MD5 weak crypto usage
|
||||||
|
sample := testutils.SampleCodeG401[0]
|
||||||
|
source := sample.Code
|
||||||
|
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
|
||||||
|
|
||||||
|
nosecPackage := testutils.NewTestPackage()
|
||||||
|
defer nosecPackage.Close()
|
||||||
|
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec", 1)
|
||||||
|
nosecPackage.AddFile("md5.go", nosecSource)
|
||||||
|
nosecPackage.Build()
|
||||||
|
|
||||||
|
analyzer.Process(nosecPackage.Path)
|
||||||
|
nosecIssues, _ := analyzer.Report()
|
||||||
|
Expect(nosecIssues).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be possible to overwrite nosec comments, and report issues", func() {
|
||||||
|
|
||||||
|
// Rule for MD5 weak crypto usage
|
||||||
|
sample := testutils.SampleCodeG401[0]
|
||||||
|
source := sample.Code
|
||||||
|
|
||||||
|
// overwrite nosec option
|
||||||
|
nosecIgnoreConfig := gas.NewConfig()
|
||||||
|
nosecIgnoreConfig.SetGlobal("nosec", "true")
|
||||||
|
customAnalyzer := gas.NewAnalyzer(nosecIgnoreConfig, logger)
|
||||||
|
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()...)
|
||||||
|
|
||||||
|
nosecPackage := testutils.NewTestPackage()
|
||||||
|
defer nosecPackage.Close()
|
||||||
|
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec", 1)
|
||||||
|
nosecPackage.AddFile("md5.go", nosecSource)
|
||||||
|
nosecPackage.Build()
|
||||||
|
|
||||||
|
customAnalyzer.Process(nosecPackage.Path)
|
||||||
|
nosecIssues, _ := customAnalyzer.Report()
|
||||||
|
Expect(nosecIssues).Should(HaveLen(sample.Errors))
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
12
call_list.go
12
call_list.go
|
@ -55,19 +55,19 @@ func (c CallList) Contains(selector, ident string) bool {
|
||||||
|
|
||||||
/// ContainsCallExpr resolves the call expression name and type
|
/// ContainsCallExpr resolves the call expression name and type
|
||||||
/// or package and determines if it exists within the CallList
|
/// or package and determines if it exists within the CallList
|
||||||
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) bool {
|
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
|
||||||
selector, ident, err := GetCallInfo(n, ctx)
|
selector, ident, err := GetCallInfo(n, ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
// Try direct resolution
|
// Try direct resolution
|
||||||
if c.Contains(selector, ident) {
|
if c.Contains(selector, ident) {
|
||||||
return true
|
return n.(*ast.CallExpr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also support explicit path
|
// Also support explicit path
|
||||||
if path, ok := GetImportPath(selector, ctx); ok {
|
if path, ok := GetImportPath(selector, ctx); ok && c.Contains(path, ident) {
|
||||||
return c.Contains(path, ident)
|
return n.(*ast.CallExpr)
|
||||||
}
|
}
|
||||||
return false
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,60 +1,86 @@
|
||||||
package gas
|
package gas_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"testing"
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
"github.com/GoASTScanner/gas/testutils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
type callListRule struct {
|
var _ = Describe("call list", func() {
|
||||||
MetaData
|
var (
|
||||||
callList CallList
|
calls gas.CallList
|
||||||
matched int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *callListRule) Match(n ast.Node, c *Context) (gi *Issue, err error) {
|
|
||||||
if r.callList.ContainsCallExpr(n, c) {
|
|
||||||
r.matched += 1
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallListContainsCallExpr(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := NewAnalyzer(config, nil)
|
|
||||||
calls := NewCallList()
|
|
||||||
calls.AddAll("bytes.Buffer", "Write", "WriteTo")
|
|
||||||
rule := &callListRule{
|
|
||||||
MetaData: MetaData{
|
|
||||||
Severity: Low,
|
|
||||||
Confidence: Low,
|
|
||||||
What: "A dummy rule",
|
|
||||||
},
|
|
||||||
callList: calls,
|
|
||||||
matched: 0,
|
|
||||||
}
|
|
||||||
analyzer.AddRule(rule, []ast.Node{(*ast.CallExpr)(nil)})
|
|
||||||
source := `
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
)
|
||||||
func main() {
|
BeforeEach(func() {
|
||||||
var b bytes.Buffer
|
calls = gas.NewCallList()
|
||||||
b.Write([]byte("Hello "))
|
})
|
||||||
fmt.Fprintf(&b, "world!")
|
|
||||||
}`
|
|
||||||
|
|
||||||
analyzer.ProcessSource("dummy.go", source)
|
It("should not return any matches when empty", func() {
|
||||||
if rule.matched != 1 {
|
Expect(calls.Contains("foo", "bar")).Should(BeFalse())
|
||||||
t.Errorf("Expected to match a bytes.Buffer.Write call")
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCallListContains(t *testing.T) {
|
It("should be possible to add a single call", func() {
|
||||||
callList := NewCallList()
|
Expect(calls).Should(HaveLen(0))
|
||||||
callList.Add("fmt", "Printf")
|
calls.Add("foo", "bar")
|
||||||
if !callList.Contains("fmt", "Printf") {
|
Expect(calls).Should(HaveLen(1))
|
||||||
t.Errorf("Expected call list to contain fmt.Printf")
|
|
||||||
}
|
expected := make(map[string]bool)
|
||||||
}
|
expected["bar"] = true
|
||||||
|
actual := map[string]bool(calls["foo"])
|
||||||
|
Expect(actual).Should(Equal(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be possible to add multiple calls at once", func() {
|
||||||
|
Expect(calls).Should(HaveLen(0))
|
||||||
|
calls.AddAll("fmt", "Sprint", "Sprintf", "Printf", "Println")
|
||||||
|
|
||||||
|
expected := map[string]bool{
|
||||||
|
"Sprint": true,
|
||||||
|
"Sprintf": true,
|
||||||
|
"Printf": true,
|
||||||
|
"Println": true,
|
||||||
|
}
|
||||||
|
actual := map[string]bool(calls["fmt"])
|
||||||
|
Expect(actual).Should(Equal(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not return a match if none are present", func() {
|
||||||
|
calls.Add("ioutil", "Copy")
|
||||||
|
Expect(calls.Contains("fmt", "Println")).Should(BeFalse())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match a call based on selector and ident", func() {
|
||||||
|
calls.Add("ioutil", "Copy")
|
||||||
|
Expect(calls.Contains("ioutil", "Copy")).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should match a call expression", func() {
|
||||||
|
|
||||||
|
// Create file to be scanned
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
defer pkg.Close()
|
||||||
|
pkg.AddFile("md5.go", testutils.SampleCodeG401[0].Code)
|
||||||
|
|
||||||
|
ctx := pkg.CreateContext("md5.go")
|
||||||
|
|
||||||
|
// Search for md5.New()
|
||||||
|
calls.Add("md5", "New")
|
||||||
|
|
||||||
|
// Stub out visitor and count number of matched call expr
|
||||||
|
matched := 0
|
||||||
|
v := testutils.NewMockVisitor()
|
||||||
|
v.Context = ctx
|
||||||
|
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
|
||||||
|
if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx) != nil {
|
||||||
|
matched++
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
ast.Walk(v, ctx.Root)
|
||||||
|
Expect(matched).Should(Equal(1))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
|
@ -1,251 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_newFileList(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
paths []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *fileList
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil paths",
|
|
||||||
args: args{paths: nil},
|
|
||||||
want: &fileList{patterns: map[string]struct{}{}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty paths",
|
|
||||||
args: args{paths: []string{}},
|
|
||||||
want: &fileList{patterns: map[string]struct{}{}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "have paths",
|
|
||||||
args: args{paths: []string{"*_test.go"}},
|
|
||||||
want: &fileList{patterns: map[string]struct{}{
|
|
||||||
"*_test.go": struct{}{},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
if got := newFileList(tt.args.paths...); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("%q. newFileList() = %v, want %v", tt.name, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_fileList_String(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
patterns []string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil patterns",
|
|
||||||
fields: fields{patterns: nil},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty patterns",
|
|
||||||
fields: fields{patterns: []string{}},
|
|
||||||
want: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern",
|
|
||||||
fields: fields{patterns: []string{"foo"}},
|
|
||||||
want: "foo",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "two patterns",
|
|
||||||
fields: fields{patterns: []string{"bar", "foo"}},
|
|
||||||
want: "bar, foo",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
f := newFileList(tt.fields.patterns...)
|
|
||||||
if got := f.String(); got != tt.want {
|
|
||||||
t.Errorf("%q. fileList.String() = %v, want %v", tt.name, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_fileList_Set(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
patterns []string
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
want map[string]struct{}
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "add empty path",
|
|
||||||
fields: fields{patterns: nil},
|
|
||||||
args: args{path: ""},
|
|
||||||
want: map[string]struct{}{},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "add path to nil patterns",
|
|
||||||
fields: fields{patterns: nil},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: map[string]struct{}{
|
|
||||||
"foo": struct{}{},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "add path to empty patterns",
|
|
||||||
fields: fields{patterns: []string{}},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: map[string]struct{}{
|
|
||||||
"foo": struct{}{},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "add path to populated patterns",
|
|
||||||
fields: fields{patterns: []string{"foo"}},
|
|
||||||
args: args{path: "bar"},
|
|
||||||
want: map[string]struct{}{
|
|
||||||
"foo": struct{}{},
|
|
||||||
"bar": struct{}{},
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
f := newFileList(tt.fields.patterns...)
|
|
||||||
if err := f.Set(tt.args.path); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("%q. fileList.Set() error = %v, wantErr %v", tt.name, err, tt.wantErr)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(f.patterns, tt.want) {
|
|
||||||
t.Errorf("%q. got state fileList.patterns = %v, want state %v", tt.name, f.patterns, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_fileList_Contains(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
patterns []string
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
path string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "nil patterns",
|
|
||||||
fields: fields{patterns: nil},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty patterns",
|
|
||||||
fields: fields{patterns: nil},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern, no wildcard, no match",
|
|
||||||
fields: fields{patterns: []string{"foo"}},
|
|
||||||
args: args{path: "bar"},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern, no wildcard, match",
|
|
||||||
fields: fields{patterns: []string{"foo"}},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern, wildcard prefix, match",
|
|
||||||
fields: fields{patterns: []string{"*foo"}},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern, wildcard suffix, match",
|
|
||||||
fields: fields{patterns: []string{"foo*"}},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "one pattern, wildcard both ends, match",
|
|
||||||
fields: fields{patterns: []string{"*foo*"}},
|
|
||||||
args: args{path: "foo"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default test match 1",
|
|
||||||
fields: fields{patterns: []string{"*_test.go"}},
|
|
||||||
args: args{path: "foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default test match 2",
|
|
||||||
fields: fields{patterns: []string{"*_test.go"}},
|
|
||||||
args: args{path: "bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default test match 3",
|
|
||||||
fields: fields{patterns: []string{"*_test.go"}},
|
|
||||||
args: args{path: "/bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default test match 4",
|
|
||||||
fields: fields{patterns: []string{"*_test.go"}},
|
|
||||||
args: args{path: "baz/bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "default test match 5",
|
|
||||||
fields: fields{patterns: []string{"*_test.go"}},
|
|
||||||
args: args{path: "/baz/bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "many patterns, no match",
|
|
||||||
fields: fields{patterns: []string{"*_one.go", "*_two.go"}},
|
|
||||||
args: args{path: "/baz/bar/foo_test.go"},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "many patterns, match",
|
|
||||||
fields: fields{patterns: []string{"*_one.go", "*_two.go", "*_test.go"}},
|
|
||||||
args: args{path: "/baz/bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "sub-folder, match",
|
|
||||||
fields: fields{patterns: []string{"vendor"}},
|
|
||||||
args: args{path: "/baz/vendor/bar/foo_test.go"},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
f := newFileList(tt.fields.patterns...)
|
|
||||||
if got := f.Contains(tt.args.path); got != tt.want {
|
|
||||||
t.Errorf("%q. fileList.Contains() = %v, want %v", tt.name, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -117,6 +117,9 @@ func loadConfig(configFile string) (gas.Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if *flagIgnoreNoSec {
|
||||||
|
config.SetGlobal("nosec", "true")
|
||||||
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func Test_shouldInclude(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
path string
|
|
||||||
excluded *fileList
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "non .go file",
|
|
||||||
args: args{
|
|
||||||
path: "thing.txt",
|
|
||||||
excluded: newFileList(),
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ".go file, not excluded",
|
|
||||||
args: args{
|
|
||||||
path: "thing.go",
|
|
||||||
excluded: newFileList(),
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: ".go file, excluded",
|
|
||||||
args: args{
|
|
||||||
path: "thing.go",
|
|
||||||
excluded: newFileList("thing.go"),
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
if got := shouldInclude(tt.args.path, tt.args.excluded); got != tt.want {
|
|
||||||
t.Errorf("%q. shouldInclude() = %v, want %v", tt.name, got, tt.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
68
config.go
68
config.go
|
@ -15,7 +15,9 @@ type Config map[string]interface{}
|
||||||
// needs to be loaded via c.ReadFrom(strings.NewReader("config data"))
|
// needs to be loaded via c.ReadFrom(strings.NewReader("config data"))
|
||||||
// or from a *os.File.
|
// or from a *os.File.
|
||||||
func NewConfig() Config {
|
func NewConfig() Config {
|
||||||
return make(Config)
|
cfg := make(Config)
|
||||||
|
cfg["global"] = make(map[string]string)
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadFrom implements the io.ReaderFrom interface. This
|
// ReadFrom implements the io.ReaderFrom interface. This
|
||||||
|
@ -26,7 +28,7 @@ func (c Config) ReadFrom(r io.Reader) (int64, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return int64(len(data)), err
|
return int64(len(data)), err
|
||||||
}
|
}
|
||||||
if err = json.Unmarshal(data, c); err != nil {
|
if err = json.Unmarshal(data, &c); err != nil {
|
||||||
return int64(len(data)), err
|
return int64(len(data)), err
|
||||||
}
|
}
|
||||||
return int64(len(data)), nil
|
return int64(len(data)), nil
|
||||||
|
@ -42,39 +44,39 @@ func (c Config) WriteTo(w io.Writer) (int64, error) {
|
||||||
return io.Copy(w, bytes.NewReader(data))
|
return io.Copy(w, bytes.NewReader(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnableRule will change the rule to the specified enabled state
|
// Get returns the configuration section for the supplied key
|
||||||
func (c Config) EnableRule(ruleID string, enabled bool) {
|
func (c Config) Get(section string) (interface{}, error) {
|
||||||
if data, found := c["rules"]; found {
|
settings, found := c[section]
|
||||||
if rules, ok := data.(map[string]bool); ok {
|
|
||||||
rules[ruleID] = enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enabled returns a list of rules that are enabled
|
|
||||||
func (c Config) Enabled() []string {
|
|
||||||
if data, found := c["rules"]; found {
|
|
||||||
if rules, ok := data.(map[string]bool); ok {
|
|
||||||
enabled := make([]string, len(rules))
|
|
||||||
for ruleID := range rules {
|
|
||||||
enabled = append(enabled, ruleID)
|
|
||||||
}
|
|
||||||
return enabled
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns the configuration section for a given rule
|
|
||||||
func (c Config) Get(ruleID string) (interface{}, error) {
|
|
||||||
section, found := c[ruleID]
|
|
||||||
if !found {
|
if !found {
|
||||||
return nil, fmt.Errorf("Rule %s not in configuration", ruleID)
|
return nil, fmt.Errorf("Section %s not in configuration", section)
|
||||||
}
|
}
|
||||||
return section, nil
|
return settings, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set section for a given rule
|
// Set section in the configuration to specified value
|
||||||
func (c Config) Set(ruleID string, val interface{}) {
|
func (c Config) Set(section string, value interface{}) {
|
||||||
c[ruleID] = val
|
c[section] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobal returns value associated with global configuration option
|
||||||
|
func (c Config) GetGlobal(option string) (string, error) {
|
||||||
|
if globals, ok := c["global"]; ok {
|
||||||
|
if settings, ok := globals.(map[string]string); ok {
|
||||||
|
if value, ok := settings[option]; ok {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("global setting for %s not found", option)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no global config options found")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetGlobal associates a value with a global configuration ooption
|
||||||
|
func (c Config) SetGlobal(option, value string) {
|
||||||
|
if globals, ok := c["global"]; ok {
|
||||||
|
if settings, ok := globals.(map[string]string); ok {
|
||||||
|
settings[option] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
103
config_test.go
Normal file
103
config_test.go
Normal file
|
@ -0,0 +1,103 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Configuration", func() {
|
||||||
|
var configuration gas.Config
|
||||||
|
BeforeEach(func() {
|
||||||
|
configuration = gas.NewConfig()
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when loading from disk", func() {
|
||||||
|
|
||||||
|
It("should be possible to load configuration from a file", func() {
|
||||||
|
json := `{"G101": {}}`
|
||||||
|
buffer := bytes.NewBufferString(json)
|
||||||
|
nread, err := configuration.ReadFrom(buffer)
|
||||||
|
Expect(nread).Should(Equal(int64(len(json))))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return an error if configuration file is invalid", func() {
|
||||||
|
var err error
|
||||||
|
invalidBuffer := bytes.NewBuffer([]byte{0xc0, 0xff, 0xee})
|
||||||
|
_, err = configuration.ReadFrom(invalidBuffer)
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
|
||||||
|
emptyBuffer := bytes.NewBuffer([]byte{})
|
||||||
|
_, err = configuration.ReadFrom(emptyBuffer)
|
||||||
|
Expect(err).Should(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when saving to disk", func() {
|
||||||
|
It("should be possible to save an empty configuration to file", func() {
|
||||||
|
expected := `{"global":{}}`
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
nbytes, err := configuration.WriteTo(buffer)
|
||||||
|
Expect(int(nbytes)).Should(Equal(len(expected)))
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(buffer.String()).Should(Equal(expected))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be possible to save configuration to file", func() {
|
||||||
|
|
||||||
|
configuration.Set("G101", map[string]string{
|
||||||
|
"mode": "strict",
|
||||||
|
})
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer([]byte{})
|
||||||
|
nbytes, err := configuration.WriteTo(buffer)
|
||||||
|
Expect(int(nbytes)).ShouldNot(BeZero())
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(buffer.String()).Should(Equal(`{"G101":{"mode":"strict"},"global":{}}`))
|
||||||
|
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when configuring rules", func() {
|
||||||
|
|
||||||
|
It("should be possible to get configuration for a rule", func() {
|
||||||
|
settings := map[string]string{
|
||||||
|
"ciphers": "AES256-GCM",
|
||||||
|
}
|
||||||
|
configuration.Set("G101", settings)
|
||||||
|
|
||||||
|
retrieved, err := configuration.Get("G101")
|
||||||
|
Expect(err).ShouldNot(HaveOccurred())
|
||||||
|
Expect(retrieved).Should(HaveKeyWithValue("ciphers", "AES256-GCM"))
|
||||||
|
Expect(retrieved).ShouldNot(HaveKey("foobar"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when using global configuration options", func() {
|
||||||
|
It("should have a default global section", func() {
|
||||||
|
settings, err := configuration.Get("global")
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
expectedType := make(map[string]string)
|
||||||
|
Expect(settings).Should(BeAssignableToTypeOf(expectedType))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should save global settings to correct section", func() {
|
||||||
|
configuration.SetGlobal("nosec", "enabled")
|
||||||
|
settings, err := configuration.Get("global")
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
if globals, ok := settings.(map[string]string); ok {
|
||||||
|
Expect(globals["nosec"]).Should(MatchRegexp("enabled"))
|
||||||
|
} else {
|
||||||
|
Fail("globals are not defined as map")
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue, err := configuration.GetGlobal("nosec")
|
||||||
|
Expect(err).Should(BeNil())
|
||||||
|
Expect(setValue).Should(MatchRegexp("enabled"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
13
gas_suite_test.go
Normal file
13
gas_suite_test.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGas(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Gas Suite")
|
||||||
|
}
|
51
helpers.go
51
helpers.go
|
@ -19,34 +19,9 @@ import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// helpfull "canned" matching routines ----------------------------------------
|
|
||||||
|
|
||||||
func selectName(n ast.Node, s reflect.Type) (string, bool) {
|
|
||||||
t := reflect.TypeOf(&ast.SelectorExpr{})
|
|
||||||
if node, ok := SimpleSelect(n, s, t).(*ast.SelectorExpr); ok {
|
|
||||||
t = reflect.TypeOf(&ast.Ident{})
|
|
||||||
if ident, ok := SimpleSelect(node.X, t).(*ast.Ident); ok {
|
|
||||||
return strings.Join([]string{ident.Name, node.Sel.Name}, "."), ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchCall will match an ast.CallNode if its method name obays the given regex.
|
|
||||||
func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
|
||||||
t := reflect.TypeOf(&ast.CallExpr{})
|
|
||||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
|
||||||
return n.(*ast.CallExpr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchCallByPackage ensures that the specified package is imported,
|
// MatchCallByPackage ensures that the specified package is imported,
|
||||||
// adjusts the name for any aliases and ignores cases that are
|
// adjusts the name for any aliases and ignores cases that are
|
||||||
// initialization only imports.
|
// initialization only imports.
|
||||||
|
@ -100,11 +75,13 @@ func MatchCallByType(n ast.Node, ctx *Context, requiredType string, calls ...str
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchCompLit will match an ast.CompositeLit if its string value obays the given regex.
|
// MatchCompLit will match an ast.CompositeLit based on the supplied type
|
||||||
func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
|
func MatchCompLit(n ast.Node, ctx *Context, required string) *ast.CompositeLit {
|
||||||
t := reflect.TypeOf(&ast.CompositeLit{})
|
if complit, ok := n.(*ast.CompositeLit); ok {
|
||||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
typeOf := ctx.Info.TypeOf(complit)
|
||||||
return n.(*ast.CompositeLit)
|
if typeOf.String() == required {
|
||||||
|
return complit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -117,7 +94,7 @@ func GetInt(n ast.Node) (int64, error) {
|
||||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt will read and return a float value from an ast.BasicLit
|
// GetFloat will read and return a float value from an ast.BasicLit
|
||||||
func GetFloat(n ast.Node) (float64, error) {
|
func GetFloat(n ast.Node) (float64, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
||||||
return strconv.ParseFloat(node.Value, 64)
|
return strconv.ParseFloat(node.Value, 64)
|
||||||
|
@ -125,7 +102,7 @@ func GetFloat(n ast.Node) (float64, error) {
|
||||||
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt will read and return a char value from an ast.BasicLit
|
// GetChar will read and return a char value from an ast.BasicLit
|
||||||
func GetChar(n ast.Node) (byte, error) {
|
func GetChar(n ast.Node) (byte, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
||||||
return node.Value[0], nil
|
return node.Value[0], nil
|
||||||
|
@ -133,7 +110,7 @@ func GetChar(n ast.Node) (byte, error) {
|
||||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt will read and return a string value from an ast.BasicLit
|
// GetString will read and return a string value from an ast.BasicLit
|
||||||
func GetString(n ast.Node) (string, error) {
|
func GetString(n ast.Node) (string, error) {
|
||||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
||||||
return strconv.Unquote(node.Value)
|
return strconv.Unquote(node.Value)
|
||||||
|
@ -170,12 +147,10 @@ func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
|
||||||
t := ctx.Info.TypeOf(expr)
|
t := ctx.Info.TypeOf(expr)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
return t.String(), fn.Sel.Name, nil
|
return t.String(), fn.Sel.Name, nil
|
||||||
} else {
|
|
||||||
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
|
||||||
}
|
}
|
||||||
} else {
|
return "undefined", fn.Sel.Name, fmt.Errorf("missing type info")
|
||||||
return expr.Name, fn.Sel.Name, nil
|
|
||||||
}
|
}
|
||||||
|
return expr.Name, fn.Sel.Name, nil
|
||||||
}
|
}
|
||||||
case *ast.Ident:
|
case *ast.Ident:
|
||||||
return ctx.Pkg.Name(), fn.Name, nil
|
return ctx.Pkg.Name(), fn.Name, nil
|
||||||
|
@ -205,7 +180,7 @@ func GetImportedName(path string, ctx *Context) (string, bool) {
|
||||||
// GetImportPath resolves the full import path of an identifer based on
|
// GetImportPath resolves the full import path of an identifer based on
|
||||||
// the imports in the current context.
|
// the imports in the current context.
|
||||||
func GetImportPath(name string, ctx *Context) (string, bool) {
|
func GetImportPath(name string, ctx *Context) (string, bool) {
|
||||||
for path, _ := range ctx.Imports.Imported {
|
for path := range ctx.Imports.Imported {
|
||||||
if imported, ok := GetImportedName(path, ctx); ok && imported == name {
|
if imported, ok := GetImportedName(path, ctx); ok && imported == name {
|
||||||
return path, true
|
return path, true
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,71 +1,14 @@
|
||||||
package gas
|
package gas_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
. "github.com/onsi/ginkgo"
|
||||||
"testing"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
type dummyCallback func(ast.Node, *Context, string, ...string) (*ast.CallExpr, bool)
|
var _ = Describe("Helpers", func() {
|
||||||
|
Context("todo", func() {
|
||||||
type dummyRule struct {
|
It("should fail", func() {
|
||||||
MetaData
|
Expect(1).Should(Equal(2))
|
||||||
pkgOrType string
|
})
|
||||||
funcsOrMethods []string
|
})
|
||||||
callback dummyCallback
|
})
|
||||||
callExpr []ast.Node
|
|
||||||
matched int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *dummyRule) Match(n ast.Node, c *Context) (gi *Issue, err error) {
|
|
||||||
if callexpr, matched := r.callback(n, c, r.pkgOrType, r.funcsOrMethods...); matched {
|
|
||||||
r.matched += 1
|
|
||||||
r.callExpr = append(r.callExpr, callexpr)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMatchCallByType(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := NewAnalyzer(config, nil)
|
|
||||||
rule := &dummyRule{
|
|
||||||
MetaData: MetaData{
|
|
||||||
Severity: Low,
|
|
||||||
Confidence: Low,
|
|
||||||
What: "A dummy rule",
|
|
||||||
},
|
|
||||||
pkgOrType: "bytes.Buffer",
|
|
||||||
funcsOrMethods: []string{"Write"},
|
|
||||||
callback: MatchCallByType,
|
|
||||||
callExpr: []ast.Node{},
|
|
||||||
matched: 0,
|
|
||||||
}
|
|
||||||
analyzer.AddRule(rule, []ast.Node{(*ast.CallExpr)(nil)})
|
|
||||||
source := `
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
var b bytes.Buffer
|
|
||||||
b.Write([]byte("Hello "))
|
|
||||||
fmt.Fprintf(&b, "world!")
|
|
||||||
}`
|
|
||||||
|
|
||||||
analyzer.ProcessSource("dummy.go", source)
|
|
||||||
if rule.matched != 1 || len(rule.callExpr) != 1 {
|
|
||||||
t.Errorf("Expected to match a bytes.Buffer.Write call")
|
|
||||||
}
|
|
||||||
|
|
||||||
typeName, callName, err := GetCallInfo(rule.callExpr[0], analyzer.context)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to resolve call info: %v\n", err)
|
|
||||||
}
|
|
||||||
if typeName != "bytes.Buffer" {
|
|
||||||
t.Errorf("Expected: %s, Got: %s\n", "bytes.Buffer", typeName)
|
|
||||||
}
|
|
||||||
if callName != "Write" {
|
|
||||||
t.Errorf("Expected: %s, Got: %s\n", "Write", callName)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -34,9 +34,11 @@ func NewImportTracker() *ImportTracker {
|
||||||
|
|
||||||
func (t *ImportTracker) TrackPackages(pkgs ...*types.Package) {
|
func (t *ImportTracker) TrackPackages(pkgs ...*types.Package) {
|
||||||
for _, pkg := range pkgs {
|
for _, pkg := range pkgs {
|
||||||
for _, imp := range pkg.Imports() {
|
t.Imported[pkg.Path()] = pkg.Name()
|
||||||
t.Imported[imp.Path()] = imp.Name()
|
// Transient imports
|
||||||
}
|
//for _, imp := range pkg.Imports() {
|
||||||
|
// t.Imported[imp.Path()] = imp.Name()
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,8 +54,6 @@ func (t *ImportTracker) TrackImport(n ast.Node) {
|
||||||
t.Aliased[path] = imported.Name.Name
|
t.Aliased[path] = imported.Name.Name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// unsafe is not included in Package.Imports()
|
|
||||||
if path == "unsafe" {
|
if path == "unsafe" {
|
||||||
t.Imported[path] = path
|
t.Imported[path] = path
|
||||||
}
|
}
|
||||||
|
|
30
import_tracker_test.go
Normal file
30
import_tracker_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("ImportTracker", func() {
|
||||||
|
var (
|
||||||
|
source string
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
source = `// TODO(gm)`
|
||||||
|
})
|
||||||
|
Context("when I have a valid go package", func() {
|
||||||
|
It("should record all import specs", func() {
|
||||||
|
Expect(1).Should(Equal(1))
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should correctly track aliased package imports", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should correctly track init only packages", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
37
issue_test.go
Normal file
37
issue_test.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Issue", func() {
|
||||||
|
|
||||||
|
Context("when creating a new issue", func() {
|
||||||
|
It("should provide a code snippet for the specified ast.Node", func() {
|
||||||
|
Expect(1).Should(Equal(2))
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should return an error if specific context is not able to be obtained", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should provide accurate line and file information", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should maintain the provided severity score", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should maintain the provided confidence score", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should correctly record `unsafe` import as not considered a package", func() {
|
||||||
|
Fail("Not implemented")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -17,6 +17,7 @@ package gas
|
||||||
import "go/ast"
|
import "go/ast"
|
||||||
|
|
||||||
func resolveIdent(n *ast.Ident, c *Context) bool {
|
func resolveIdent(n *ast.Ident, c *Context) bool {
|
||||||
|
|
||||||
if n.Obj == nil || n.Obj.Kind != ast.Var {
|
if n.Obj == nil || n.Obj.Kind != ast.Var {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
95
resolve_test.go
Normal file
95
resolve_test.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
"github.com/GoASTScanner/gas/testutils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Resolve ast node to concrete value", func() {
|
||||||
|
Context("when attempting to resolve an ast node", func() {
|
||||||
|
It("should successfully resolve basic literal", func() {
|
||||||
|
var basicLiteral *ast.BasicLit
|
||||||
|
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
pkg.AddFile("foo.go", `package main; const foo = "bar"; func main(){}`)
|
||||||
|
ctx := pkg.CreateContext("foo.go")
|
||||||
|
v := testutils.NewMockVisitor()
|
||||||
|
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
|
||||||
|
if node, ok := n.(*ast.BasicLit); ok {
|
||||||
|
basicLiteral = node
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v.Context = ctx
|
||||||
|
ast.Walk(v, ctx.Root)
|
||||||
|
Expect(basicLiteral).ShouldNot(BeNil())
|
||||||
|
Expect(gas.TryResolve(basicLiteral, ctx)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should successfully resolve identifier", func() {
|
||||||
|
var ident *ast.Ident
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
pkg.AddFile("foo.go", `package main; var foo string = "bar"; func main(){}`)
|
||||||
|
ctx := pkg.CreateContext("foo.go")
|
||||||
|
v := testutils.NewMockVisitor()
|
||||||
|
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
|
||||||
|
if node, ok := n.(*ast.Ident); ok {
|
||||||
|
ident = node
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v.Context = ctx
|
||||||
|
ast.Walk(v, ctx.Root)
|
||||||
|
Expect(ident).ShouldNot(BeNil())
|
||||||
|
Expect(gas.TryResolve(ident, ctx)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should successfully resolve assign statement", func() {
|
||||||
|
var assign *ast.AssignStmt
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
pkg.AddFile("foo.go", `package main; const x = "bar"; func main(){ y := x; println(y) }`)
|
||||||
|
ctx := pkg.CreateContext("foo.go")
|
||||||
|
v := testutils.NewMockVisitor()
|
||||||
|
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
|
||||||
|
if node, ok := n.(*ast.AssignStmt); ok {
|
||||||
|
if id, ok := node.Lhs[0].(*ast.Ident); ok && id.Name == "y" {
|
||||||
|
assign = node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v.Context = ctx
|
||||||
|
ast.Walk(v, ctx.Root)
|
||||||
|
Expect(assign).ShouldNot(BeNil())
|
||||||
|
Expect(gas.TryResolve(assign, ctx)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should successfully resolve a binary statement", func() {
|
||||||
|
var target *ast.BinaryExpr
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
pkg.AddFile("foo.go", `package main; const (x = "bar"; y = "baz"); func main(){ z := x + y; println(z) }`)
|
||||||
|
ctx := pkg.CreateContext("foo.go")
|
||||||
|
v := testutils.NewMockVisitor()
|
||||||
|
v.Callback = func(n ast.Node, ctx *gas.Context) bool {
|
||||||
|
if node, ok := n.(*ast.BinaryExpr); ok {
|
||||||
|
target = node
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v.Context = ctx
|
||||||
|
ast.Walk(v, ctx.Root)
|
||||||
|
Expect(target).ShouldNot(BeNil())
|
||||||
|
Expect(gas.TryResolve(target, ctx)).Should(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: It should resolve call expressions
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
85
rule_test.go
Normal file
85
rule_test.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package gas_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockrule struct {
|
||||||
|
issue *gas.Issue
|
||||||
|
err error
|
||||||
|
callback func(n ast.Node, ctx *gas.Context) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockrule) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
|
||||||
|
if m.callback(n, ctx) {
|
||||||
|
return m.issue, nil
|
||||||
|
}
|
||||||
|
return nil, m.err
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Rule", func() {
|
||||||
|
|
||||||
|
Context("when using a ruleset", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
ruleset gas.RuleSet
|
||||||
|
dummyErrorRule gas.Rule
|
||||||
|
dummyIssueRule gas.Rule
|
||||||
|
)
|
||||||
|
|
||||||
|
JustBeforeEach(func() {
|
||||||
|
ruleset = gas.NewRuleSet()
|
||||||
|
dummyErrorRule = &mockrule{
|
||||||
|
issue: nil,
|
||||||
|
err: fmt.Errorf("An unexpected error occurred"),
|
||||||
|
callback: func(n ast.Node, ctx *gas.Context) bool { return false },
|
||||||
|
}
|
||||||
|
dummyIssueRule = &mockrule{
|
||||||
|
issue: &gas.Issue{
|
||||||
|
Severity: gas.High,
|
||||||
|
Confidence: gas.High,
|
||||||
|
What: `Some explanation of the thing`,
|
||||||
|
File: "main.go",
|
||||||
|
Code: `#include <stdio.h> int main(){ puts("hello world"); }`,
|
||||||
|
Line: 42,
|
||||||
|
},
|
||||||
|
err: nil,
|
||||||
|
callback: func(n ast.Node, ctx *gas.Context) bool { return true },
|
||||||
|
}
|
||||||
|
})
|
||||||
|
It("should be possible to register a rule for multiple ast.Node", func() {
|
||||||
|
registeredNodeA := (*ast.CallExpr)(nil)
|
||||||
|
registeredNodeB := (*ast.AssignStmt)(nil)
|
||||||
|
unregisteredNode := (*ast.BinaryExpr)(nil)
|
||||||
|
|
||||||
|
ruleset.Register(dummyIssueRule, registeredNodeA, registeredNodeB)
|
||||||
|
Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty())
|
||||||
|
Expect(ruleset.RegisteredFor(registeredNodeA)).Should(ContainElement(dummyIssueRule))
|
||||||
|
Expect(ruleset.RegisteredFor(registeredNodeB)).Should(ContainElement(dummyIssueRule))
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should not register a rule when no ast.Nodes are specified", func() {
|
||||||
|
ruleset.Register(dummyErrorRule)
|
||||||
|
Expect(ruleset).Should(BeEmpty())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be possible to retrieve a list of rules for a given node type", func() {
|
||||||
|
registeredNode := (*ast.CallExpr)(nil)
|
||||||
|
unregisteredNode := (*ast.AssignStmt)(nil)
|
||||||
|
ruleset.Register(dummyErrorRule, registeredNode)
|
||||||
|
ruleset.Register(dummyIssueRule, registeredNode)
|
||||||
|
Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty())
|
||||||
|
Expect(ruleset.RegisteredFor(registeredNode)).Should(HaveLen(2))
|
||||||
|
Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyErrorRule))
|
||||||
|
Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyIssueRule))
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
|
@ -1,49 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBigExp(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewUsingBigExp(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
z := new(big.Int)
|
|
||||||
x := new(big.Int)
|
|
||||||
x = x.SetUint64(2)
|
|
||||||
y := new(big.Int)
|
|
||||||
y = y.SetUint64(4)
|
|
||||||
m := new(big.Int)
|
|
||||||
m = m.SetUint64(0)
|
|
||||||
|
|
||||||
z = z.Exp(x, y, m)
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Use of math/big.Int.Exp function")
|
|
||||||
}
|
|
|
@ -24,24 +24,29 @@ import (
|
||||||
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
|
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
|
||||||
type BindsToAllNetworkInterfaces struct {
|
type BindsToAllNetworkInterfaces struct {
|
||||||
gas.MetaData
|
gas.MetaData
|
||||||
call *regexp.Regexp
|
calls gas.CallList
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *BindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (r *BindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, r.call); node != nil {
|
callExpr := r.calls.ContainsCallExpr(n, c)
|
||||||
if arg, err := gas.GetString(node.Args[1]); err == nil {
|
if callExpr == nil {
|
||||||
if r.pattern.MatchString(arg) {
|
return nil, nil
|
||||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
}
|
||||||
}
|
if arg, err := gas.GetString(callExpr.Args[1]); err == nil {
|
||||||
|
if r.pattern.MatchString(arg) {
|
||||||
|
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBindsToAllNetworkInterfaces(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewBindsToAllNetworkInterfaces(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
|
calls := gas.NewCallList()
|
||||||
|
calls.Add("net", "Listen")
|
||||||
|
calls.Add("tls", "Listen")
|
||||||
return &BindsToAllNetworkInterfaces{
|
return &BindsToAllNetworkInterfaces{
|
||||||
call: regexp.MustCompile(`^(net|tls)\.Listen$`),
|
calls: calls,
|
||||||
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
|
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
Severity: gas.Medium,
|
Severity: gas.Medium,
|
||||||
|
|
|
@ -1,65 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBind0000(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBindsToAllNetworkInterfaces(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
l, err := net.Listen("tcp", "0.0.0.0:2000")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Binds to all network interfaces")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBindEmptyHost(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBindsToAllNetworkInterfaces(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
l, err := net.Listen("tcp", ":2000")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer l.Close()
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Binds to all network interfaces")
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
const initOnlyImportSrc = `
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
_ "crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
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, "")
|
|
||||||
}
|
|
|
@ -49,7 +49,7 @@ func (r *NoErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
|
||||||
switch stmt := n.(type) {
|
switch stmt := n.(type) {
|
||||||
case *ast.AssignStmt:
|
case *ast.AssignStmt:
|
||||||
for _, expr := range stmt.Rhs {
|
for _, expr := range stmt.Rhs {
|
||||||
if callExpr, ok := expr.(*ast.CallExpr); ok && !r.whitelist.ContainsCallExpr(callExpr, ctx) {
|
if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx) == nil {
|
||||||
pos := returnsError(callExpr, ctx)
|
pos := returnsError(callExpr, ctx)
|
||||||
if pos < 0 || pos >= len(stmt.Lhs) {
|
if pos < 0 || pos >= len(stmt.Lhs) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -60,7 +60,7 @@ func (r *NoErrorCheck) Match(n ast.Node, ctx *gas.Context) (*gas.Issue, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case *ast.ExprStmt:
|
case *ast.ExprStmt:
|
||||||
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && !r.whitelist.ContainsCallExpr(callExpr, ctx) {
|
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil {
|
||||||
pos := returnsError(callExpr, ctx)
|
pos := returnsError(callExpr, ctx)
|
||||||
if pos >= 0 {
|
if pos >= 0 {
|
||||||
return gas.NewIssue(ctx, n, r.What, r.Severity, r.Confidence), nil
|
return gas.NewIssue(ctx, n, r.What, r.Severity, r.Confidence), nil
|
||||||
|
|
|
@ -1,144 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrorsMulti(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewNoErrorCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func test() (int,error) {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
v, _ := test()
|
|
||||||
fmt.Println(v)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Errors unhandled")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorsSingle(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewNoErrorCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func a() error {
|
|
||||||
return fmt.Errorf("This is an error")
|
|
||||||
}
|
|
||||||
|
|
||||||
func b() {
|
|
||||||
fmt.Println("b")
|
|
||||||
}
|
|
||||||
|
|
||||||
func c() string {
|
|
||||||
return fmt.Sprintf("This isn't anything")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
_ = a()
|
|
||||||
a()
|
|
||||||
b()
|
|
||||||
_ = c()
|
|
||||||
c()
|
|
||||||
}`, analyzer)
|
|
||||||
checkTestResults(t, issues, 2, "Errors unhandled")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorsGood(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewNoErrorCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func test() err error {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
e := test()
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestErrorsWhitelisted(t *testing.T) {
|
|
||||||
config := map[string]interface{}{
|
|
||||||
"ignoreNosec": false,
|
|
||||||
"G104": map[string][]string{
|
|
||||||
"compress/zlib": []string{"NewReader"},
|
|
||||||
"io": []string{"Copy"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewNoErrorCheck(config))
|
|
||||||
source := `package main
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"fmt"
|
|
||||||
"bytes"
|
|
||||||
"compress/zlib"
|
|
||||||
)
|
|
||||||
|
|
||||||
func a() error {
|
|
||||||
return fmt.Errorf("This is an error ok")
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Expect at least one failure
|
|
||||||
_ = a()
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
// Default whitelist
|
|
||||||
nbytes, _ := b.Write([]byte("Hello "))
|
|
||||||
if nbytes <= 0 {
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Whitelisted via configuration
|
|
||||||
r, _ := zlib.NewReader(&b)
|
|
||||||
io.Copy(os.Stdout, r)
|
|
||||||
}`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "Errors unhandled")
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestChmod(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewFilePerms(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import "os"
|
|
||||||
func main() {
|
|
||||||
os.Chmod("/tmp/somefile", 0777)
|
|
||||||
os.Chmod("/tmp/someotherfile", 0600)
|
|
||||||
os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0666)
|
|
||||||
os.OpenFile("/tmp/thing", os.O_CREATE|os.O_WRONLY, 0600)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 2, "Expect file permissions")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMkdir(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewMkdirPerms(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import "os"
|
|
||||||
func main() {
|
|
||||||
os.Mkdir("/tmp/mydir", 0777)
|
|
||||||
os.Mkdir("/tmp/mydir", 0600)
|
|
||||||
os.MkdirAll("/tmp/mydir/mysubidr", 0775)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 2, "Expect directory permissions")
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHardcoded(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
username := "admin"
|
|
||||||
password := "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedWithEntropy(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
username := "admin"
|
|
||||||
password := "secret"
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedIgnoreEntropy(t *testing.T) {
|
|
||||||
config := map[string]interface{}{
|
|
||||||
"ignoreNosec": false,
|
|
||||||
"G101": map[string]string{
|
|
||||||
"ignore_entropy": "true",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
username := "admin"
|
|
||||||
password := "admin"
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedGlobalVar(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
var password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
username := "admin"
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedConstant(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
username := "admin"
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedConstantMulti(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
username = "user"
|
|
||||||
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Doing something with: ", username, password)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardecodedVarsNotAssigned(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
var password string
|
|
||||||
func init() {
|
|
||||||
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
}`, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedConstInteger(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
const (
|
|
||||||
ATNStateSomethingElse = 1
|
|
||||||
ATNStateTokenStart = 42
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
println(ATNStateTokenStart)
|
|
||||||
}`, analyzer)
|
|
||||||
checkTestResults(t, issues, 0, "Potential hardcoded credentials")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHardcodedConstString(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewHardcodedCredentials(config))
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
const (
|
|
||||||
ATNStateTokenStart = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
println(ATNStateTokenStart)
|
|
||||||
}`, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHttpoxy(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBlacklist_net_http_cgi(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"net/http/cgi"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
cgi.Serve(http.FileServer(http.Dir("/usr/share/doc")))
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Go versions < 1.6.3 are vulnerable to Httpoxy")
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNosec(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd := exec.Command("sh", "-c", os.Getenv("BLAH")) // #nosec
|
|
||||||
cmd.Run()
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "None")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNosecBlock(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// #nosec
|
|
||||||
if true {
|
|
||||||
cmd := exec.Command("sh", "-c", os.Getenv("BLAH"))
|
|
||||||
cmd.Run()
|
|
||||||
}
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "None")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNosecIgnore(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": true}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd := exec.Command("sh", "-c", os.Args[1]) // #nosec
|
|
||||||
cmd.Run()
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Subprocess launching with variable.")
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRandOk(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewWeakRandCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "crypto/rand"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
good, _ := rand.Read(nil)
|
|
||||||
println(good)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRandBad(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewWeakRandCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "math/rand"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
bad := rand.Int()
|
|
||||||
println(bad)
|
|
||||||
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Use of weak random number generator (math/rand instead of crypto/rand)")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRandRenamed(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewWeakRandCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
mrand "math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
good, _ := rand.Read(nil)
|
|
||||||
println(good)
|
|
||||||
i := mrand.Int31()
|
|
||||||
println(i)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
15
rules/rsa.go
15
rules/rsa.go
|
@ -17,20 +17,19 @@ package rules
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
"github.com/GoASTScanner/gas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WeakKeyStrength struct {
|
type WeakKeyStrength struct {
|
||||||
gas.MetaData
|
gas.MetaData
|
||||||
pattern *regexp.Regexp
|
calls gas.CallList
|
||||||
bits int
|
bits int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WeakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
func (w *WeakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, w.pattern); node != nil {
|
if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil {
|
||||||
if bits, err := gas.GetInt(node.Args[1]); err == nil && bits < (int64)(w.bits) {
|
if bits, err := gas.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) {
|
||||||
return gas.NewIssue(c, n, w.What, w.Severity, w.Confidence), nil
|
return gas.NewIssue(c, n, w.What, w.Severity, w.Confidence), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,10 +37,12 @@ func (w *WeakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWeakKeyStrength(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewWeakKeyStrength(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
|
calls := gas.NewCallList()
|
||||||
|
calls.Add("rsa", "GenerateKey")
|
||||||
bits := 2048
|
bits := 2048
|
||||||
return &WeakKeyStrength{
|
return &WeakKeyStrength{
|
||||||
pattern: regexp.MustCompile(`^rsa\.GenerateKey$`),
|
calls: calls,
|
||||||
bits: bits,
|
bits: bits,
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
Severity: gas.Medium,
|
Severity: gas.Medium,
|
||||||
Confidence: gas.High,
|
Confidence: gas.High,
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRSAKeys(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewWeakKeyStrength(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(
|
|
||||||
`package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"crypto/rsa"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
//Generate Private Key
|
|
||||||
pvk, err := rsa.GenerateKey(rand.Reader, 1024)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
fmt.Println(pvk)
|
|
||||||
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "RSA keys should")
|
|
||||||
}
|
|
13
rules/rules_suite_test.go
Normal file
13
rules/rules_suite_test.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package rules_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRules(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Rules Suite")
|
||||||
|
}
|
67
rules/rules_test.go
Normal file
67
rules/rules_test.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package rules_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas/rules"
|
||||||
|
"github.com/GoASTScanner/gas/testutils"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("gas rules", func() {
|
||||||
|
|
||||||
|
var (
|
||||||
|
logger *log.Logger
|
||||||
|
output *bytes.Buffer
|
||||||
|
config gas.Config
|
||||||
|
analyzer *gas.Analyzer
|
||||||
|
runner func(string, []testutils.CodeSample)
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
logger, output = testutils.NewLogger()
|
||||||
|
config = gas.NewConfig()
|
||||||
|
analyzer = gas.NewAnalyzer(config, logger)
|
||||||
|
runner = func(rule string, samples []testutils.CodeSample) {
|
||||||
|
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, rule)).Builders()...)
|
||||||
|
for n, sample := range samples {
|
||||||
|
analyzer.Reset()
|
||||||
|
pkg := testutils.NewTestPackage()
|
||||||
|
pkg.AddFile(fmt.Sprintf("sample_%d.go", n), sample.Code)
|
||||||
|
pkg.Build()
|
||||||
|
e := analyzer.Process(pkg.Path)
|
||||||
|
Expect(e).ShouldNot(HaveOccurred())
|
||||||
|
issues, _ := analyzer.Report()
|
||||||
|
if len(issues) != sample.Errors {
|
||||||
|
fmt.Println(sample.Code)
|
||||||
|
}
|
||||||
|
Expect(issues).Should(HaveLen(sample.Errors))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("report correct errors for all samples", func() {
|
||||||
|
It("should work for G101 samples", func() {
|
||||||
|
runner("G101", testutils.SampleCodeG101)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should work for G102 samples", func() {
|
||||||
|
runner("G102", testutils.SampleCodeG102)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should work for G103 samples", func() {
|
||||||
|
runner("G103", testutils.SampleCodeG103)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should work for G104 samples", func() {
|
||||||
|
runner("G104", testutils.SampleCodeG104)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
16
rules/sql.go
16
rules/sql.go
|
@ -71,12 +71,14 @@ func NewSqlStrConcat(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
|
|
||||||
type SqlStrFormat struct {
|
type SqlStrFormat struct {
|
||||||
SqlStatement
|
SqlStatement
|
||||||
call *regexp.Regexp
|
calls gas.CallList
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
|
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
|
||||||
func (s *SqlStrFormat) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (s *SqlStrFormat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, s.call); node != nil {
|
|
||||||
|
// TODO(gm) improve confidence if database/sql is being used
|
||||||
|
if node := s.calls.ContainsCallExpr(n, c); node != nil {
|
||||||
if arg, e := gas.GetString(node.Args[0]); s.pattern.MatchString(arg) && e == nil {
|
if arg, e := gas.GetString(node.Args[0]); s.pattern.MatchString(arg) && e == nil {
|
||||||
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
|
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
|
||||||
}
|
}
|
||||||
|
@ -85,8 +87,8 @@ func (s *SqlStrFormat) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err err
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSqlStrFormat(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewSqlStrFormat(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
return &SqlStrFormat{
|
rule := &SqlStrFormat{
|
||||||
call: regexp.MustCompile(`^fmt\.Sprintf$`),
|
calls: gas.NewCallList(),
|
||||||
SqlStatement: SqlStatement{
|
SqlStatement: SqlStatement{
|
||||||
pattern: regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
|
pattern: regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
|
@ -95,5 +97,7 @@ func NewSqlStrFormat(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
What: "SQL string formatting",
|
What: "SQL string formatting",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}, []ast.Node{(*ast.CallExpr)(nil)}
|
}
|
||||||
|
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln")
|
||||||
|
return rule, []ast.Node{(*ast.CallExpr)(nil)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,216 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSQLInjectionViaConcatenation(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrConcat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rows, err := db.Query("SELECT * FROM foo WHERE name = " + os.Args[1])
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "SQL string concatenation")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSQLInjectionViaIntepolation(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrFormat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
q := fmt.Sprintf("SELECT * FROM foo where name = '%s'", os.Args[1])
|
|
||||||
rows, err := db.Query(q)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "SQL string formatting")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSQLInjectionFalsePositiveA(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrConcat(config))
|
|
||||||
analyzer.AddRule(NewSqlStrFormat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var staticQuery = "SELECT * FROM foo WHERE age < 32"
|
|
||||||
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rows, err := db.Query(staticQuery)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSQLInjectionFalsePositiveB(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrConcat(config))
|
|
||||||
analyzer.AddRule(NewSqlStrFormat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var staticQuery = "SELECT * FROM foo WHERE age < 32"
|
|
||||||
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rows, err := db.Query(staticQuery)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSQLInjectionFalsePositiveC(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrConcat(config))
|
|
||||||
analyzer.AddRule(NewSqlStrFormat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
var staticQuery = "SELECT * FROM foo WHERE age < "
|
|
||||||
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rows, err := db.Query(staticQuery + "32")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSQLInjectionFalsePositiveD(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSqlStrConcat(config))
|
|
||||||
analyzer.AddRule(NewSqlStrFormat(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
//_ "github.com/mattn/go-sqlite3"
|
|
||||||
)
|
|
||||||
|
|
||||||
const age = "32"
|
|
||||||
var staticQuery = "SELECT * FROM foo WHERE age < "
|
|
||||||
|
|
||||||
func main(){
|
|
||||||
db, err := sql.Open("sqlite3", ":memory:")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
rows, err := db.Query(staticQuery + age)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
`
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 0, "Not expected to match")
|
|
||||||
}
|
|
|
@ -16,41 +16,42 @@ package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"regexp"
|
"go/types"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
"github.com/GoASTScanner/gas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Subprocess struct {
|
type Subprocess struct {
|
||||||
pattern *regexp.Regexp
|
gas.CallList
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(gm) The only real potential for command injection with a Go project
|
||||||
|
// is something like this:
|
||||||
|
//
|
||||||
|
// syscall.Exec("/bin/sh", []string{"-c", tainted})
|
||||||
|
//
|
||||||
|
// E.g. Input is correctly escaped but the execution context being used
|
||||||
|
// is unsafe. For example:
|
||||||
|
//
|
||||||
|
// syscall.Exec("echo", "foobar" + tainted)
|
||||||
func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
if node := r.ContainsCallExpr(n, c); node != nil {
|
||||||
for _, arg := range node.Args {
|
for _, arg := range node.Args {
|
||||||
if !gas.TryResolve(arg, c) {
|
if ident, ok := arg.(*ast.Ident); ok {
|
||||||
what := "Subprocess launching with variable."
|
obj := c.Info.ObjectOf(ident)
|
||||||
return gas.NewIssue(c, n, what, gas.High, gas.High), nil
|
if _, ok := obj.(*types.Var); ok && !gas.TryResolve(ident, c) {
|
||||||
|
return gas.NewIssue(c, n, "Subprocess launched with variable", gas.Medium, gas.High), nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return gas.NewIssue(c, n, "Subprocess launching should be audited", gas.Low, gas.High), nil
|
||||||
// call with partially qualified command
|
|
||||||
if str, err := gas.GetString(node.Args[0]); err == nil {
|
|
||||||
if !strings.HasPrefix(str, "/") {
|
|
||||||
what := "Subprocess launching with partial path."
|
|
||||||
return gas.NewIssue(c, n, what, gas.Medium, gas.High), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
what := "Subprocess launching should be audited."
|
|
||||||
return gas.NewIssue(c, n, what, gas.Low, gas.High), nil
|
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubproc(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewSubproc(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
return &Subprocess{
|
rule := &Subprocess{gas.NewCallList()}
|
||||||
pattern: regexp.MustCompile(`^exec\.Command|syscall\.Exec$`),
|
rule.Add("exec", "Command")
|
||||||
}, []ast.Node{(*ast.CallExpr)(nil)}
|
rule.Add("syscall", "Exec")
|
||||||
|
return rule, []ast.Node{(*ast.CallExpr)(nil)}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,124 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSubprocess(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
val := "/bin/" + "sleep"
|
|
||||||
cmd := exec.Command(val, "5")
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Printf("Waiting for command to finish...")
|
|
||||||
err = cmd.Wait()
|
|
||||||
log.Printf("Command finished with error: %v", err)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Subprocess launching should be audited.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubprocessVar(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
run := "sleep" + os.Getenv("SOMETHING")
|
|
||||||
cmd := exec.Command(run, "5")
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Printf("Waiting for command to finish...")
|
|
||||||
err = cmd.Wait()
|
|
||||||
log.Printf("Command finished with error: %v", err)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Subprocess launching with variable.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubprocessPath(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os/exec"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cmd := exec.Command("sleep", "5")
|
|
||||||
err := cmd.Start()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
log.Printf("Waiting for command to finish...")
|
|
||||||
err = cmd.Wait()
|
|
||||||
log.Printf("Command finished with error: %v", err)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Subprocess launching with partial path.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSubprocessSyscall(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewSubproc(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
syscall.Exec("/bin/cat", []string{ "/etc/passwd" }, nil)
|
|
||||||
}`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "Subprocess launching should be audited.")
|
|
||||||
}
|
|
|
@ -23,12 +23,12 @@ import (
|
||||||
|
|
||||||
type BadTempFile struct {
|
type BadTempFile struct {
|
||||||
gas.MetaData
|
gas.MetaData
|
||||||
args *regexp.Regexp
|
calls gas.CallList
|
||||||
call *regexp.Regexp
|
args *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BadTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (t *BadTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||||
if node := gas.MatchCall(n, t.call); node != nil {
|
if node := t.calls.ContainsCallExpr(n, c); node != nil {
|
||||||
if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
|
if arg, e := gas.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
|
||||||
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,12 @@ func (t *BadTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBadTempFile(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewBadTempFile(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
|
calls := gas.NewCallList()
|
||||||
|
calls.Add("ioutil", "WriteFile")
|
||||||
|
calls.Add("os", "Create")
|
||||||
return &BadTempFile{
|
return &BadTempFile{
|
||||||
call: regexp.MustCompile(`ioutil\.WriteFile|os\.Create`),
|
calls: calls,
|
||||||
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
|
args: regexp.MustCompile(`^/tmp/.*$|^/var/tmp/.*$`),
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
Severity: gas.Medium,
|
Severity: gas.Medium,
|
||||||
Confidence: gas.High,
|
Confidence: gas.High,
|
||||||
|
|
|
@ -1,47 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTempfiles(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBadTempFile(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
|
|
||||||
file1, _ := os.Create("/tmp/demo1")
|
|
||||||
defer file1.Close()
|
|
||||||
|
|
||||||
ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0644)
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 2, "shared tmp directory")
|
|
||||||
}
|
|
|
@ -16,18 +16,17 @@ package rules
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
"github.com/GoASTScanner/gas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TemplateCheck struct {
|
type TemplateCheck struct {
|
||||||
gas.MetaData
|
gas.MetaData
|
||||||
call *regexp.Regexp
|
calls gas.CallList
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TemplateCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (t *TemplateCheck) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||||
if node := gas.MatchCall(n, t.call); node != nil {
|
if node := t.calls.ContainsCallExpr(n, c); node != nil {
|
||||||
for _, arg := range node.Args {
|
for _, arg := range node.Args {
|
||||||
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
|
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
|
||||||
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
||||||
|
@ -38,8 +37,9 @@ func (t *TemplateCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err er
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTemplateCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewTemplateCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
|
|
||||||
return &TemplateCheck{
|
return &TemplateCheck{
|
||||||
call: regexp.MustCompile(`^template\.(HTML|JS|URL)$`),
|
calls: gas.NewCallList(),
|
||||||
MetaData: gas.MetaData{
|
MetaData: gas.MetaData{
|
||||||
Severity: gas.Medium,
|
Severity: gas.Medium,
|
||||||
Confidence: gas.Low,
|
Confidence: gas.Low,
|
||||||
|
|
|
@ -1,136 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestTemplateCheckSafe(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewTemplateCheck(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tmpl = ""
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
t := template.Must(template.New("ex").Parse(tmpl))
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"Title": "Test <b>World</b>",
|
|
||||||
"Body": template.HTML("<script>alert(1)</script>"),
|
|
||||||
}
|
|
||||||
t.Execute(os.Stdout, v)
|
|
||||||
}`
|
|
||||||
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 0, "this method will not auto-escape HTML. Verify data is well formed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateCheckBadHTML(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewTemplateCheck(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tmpl = ""
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := "something from another place"
|
|
||||||
t := template.Must(template.New("ex").Parse(tmpl))
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"Title": "Test <b>World</b>",
|
|
||||||
"Body": template.HTML(a),
|
|
||||||
}
|
|
||||||
t.Execute(os.Stdout, v)
|
|
||||||
}`
|
|
||||||
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateCheckBadJS(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewTemplateCheck(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tmpl = ""
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := "something from another place"
|
|
||||||
t := template.Must(template.New("ex").Parse(tmpl))
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"Title": "Test <b>World</b>",
|
|
||||||
"Body": template.JS(a),
|
|
||||||
}
|
|
||||||
t.Execute(os.Stdout, v)
|
|
||||||
}`
|
|
||||||
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTemplateCheckBadURL(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewTemplateCheck(config))
|
|
||||||
|
|
||||||
source := `
|
|
||||||
package samples
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
const tmpl = ""
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
a := "something from another place"
|
|
||||||
t := template.Must(template.New("ex").Parse(tmpl))
|
|
||||||
v := map[string]interface{}{
|
|
||||||
"Title": "Test <b>World</b>",
|
|
||||||
"Body": template.URL(a),
|
|
||||||
}
|
|
||||||
t.Execute(os.Stdout, v)
|
|
||||||
}`
|
|
||||||
|
|
||||||
issues := gasTestRunner(source, analyzer)
|
|
||||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
|
||||||
}
|
|
59
rules/tls.go
59
rules/tls.go
|
@ -17,17 +17,15 @@ package rules
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"reflect"
|
|
||||||
"regexp"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
"github.com/GoASTScanner/gas"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InsecureConfigTLS struct {
|
type InsecureConfigTLS struct {
|
||||||
MinVersion int16
|
MinVersion int16
|
||||||
MaxVersion int16
|
MaxVersion int16
|
||||||
pattern *regexp.Regexp
|
requiredType string
|
||||||
goodCiphers []string
|
goodCiphers []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringInSlice(a string, list []string) bool {
|
func stringInSlice(a string, list []string) bool {
|
||||||
|
@ -39,15 +37,24 @@ func stringInSlice(a string, list []string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *InsecureConfigTLS) processTlsCipherSuites(n ast.Node, c *gas.Context) *gas.Issue {
|
func (t *InsecureConfigTLS) processTLSCipherSuites(n ast.Node, c *gas.Context) *gas.Issue {
|
||||||
a := reflect.TypeOf(&ast.KeyValueExpr{})
|
tlsConfig := gas.MatchCompLit(n, c, t.requiredType)
|
||||||
b := reflect.TypeOf(&ast.CompositeLit{})
|
if tlsConfig == nil {
|
||||||
if node, ok := gas.SimpleSelect(n, a, b).(*ast.CompositeLit); ok {
|
return nil
|
||||||
for _, elt := range node.Elts {
|
}
|
||||||
if ident, ok := elt.(*ast.SelectorExpr); ok {
|
|
||||||
if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
|
for _, expr := range tlsConfig.Elts {
|
||||||
str := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
|
if keyvalExpr, ok := expr.(*ast.KeyValueExpr); ok {
|
||||||
return gas.NewIssue(c, n, str, gas.High, gas.High)
|
if keyname, ok := keyvalExpr.Key.(*ast.Ident); ok && keyname.Name == "CipherSuites" {
|
||||||
|
if ciphers, ok := keyvalExpr.Value.(*ast.CompositeLit); ok {
|
||||||
|
for _, cipher := range ciphers.Elts {
|
||||||
|
if ident, ok := cipher.(*ast.SelectorExpr); ok {
|
||||||
|
if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
|
||||||
|
str := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
|
||||||
|
return gas.NewIssue(c, n, str, gas.High, gas.High)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +104,7 @@ func (t *InsecureConfigTLS) processTlsConfVal(n *ast.KeyValueExpr, c *gas.Contex
|
||||||
}
|
}
|
||||||
|
|
||||||
case "CipherSuites":
|
case "CipherSuites":
|
||||||
if ret := t.processTlsCipherSuites(n, c); ret != nil {
|
if ret := t.processTLSCipherSuites(n, c); ret != nil {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,7 +115,7 @@ func (t *InsecureConfigTLS) processTlsConfVal(n *ast.KeyValueExpr, c *gas.Contex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *InsecureConfigTLS) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
func (t *InsecureConfigTLS) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||||
if node := gas.MatchCompLit(n, t.pattern); node != nil {
|
if node := gas.MatchCompLit(n, c, t.requiredType); node != nil {
|
||||||
for _, elt := range node.Elts {
|
for _, elt := range node.Elts {
|
||||||
if kve, ok := elt.(*ast.KeyValueExpr); ok {
|
if kve, ok := elt.(*ast.KeyValueExpr); ok {
|
||||||
gi = t.processTlsConfVal(kve, c)
|
gi = t.processTlsConfVal(kve, c)
|
||||||
|
@ -124,9 +131,9 @@ func (t *InsecureConfigTLS) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, er
|
||||||
func NewModernTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewModernTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
|
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
|
||||||
return &InsecureConfigTLS{
|
return &InsecureConfigTLS{
|
||||||
pattern: regexp.MustCompile(`^tls\.Config$`),
|
requiredType: "tls.Config",
|
||||||
MinVersion: 0x0303, // TLS 1.2 only
|
MinVersion: 0x0303, // TLS 1.2 only
|
||||||
MaxVersion: 0x0303,
|
MaxVersion: 0x0303,
|
||||||
goodCiphers: []string{
|
goodCiphers: []string{
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
|
||||||
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
|
||||||
|
@ -139,9 +146,9 @@ func NewModernTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
func NewIntermediateTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewIntermediateTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
|
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
|
||||||
return &InsecureConfigTLS{
|
return &InsecureConfigTLS{
|
||||||
pattern: regexp.MustCompile(`^tls\.Config$`),
|
requiredType: "tls.Config",
|
||||||
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
|
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
|
||||||
MaxVersion: 0x0303,
|
MaxVersion: 0x0303,
|
||||||
goodCiphers: []string{
|
goodCiphers: []string{
|
||||||
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
"TLS_RSA_WITH_AES_128_CBC_SHA",
|
||||||
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
"TLS_RSA_WITH_AES_256_CBC_SHA",
|
||||||
|
@ -165,9 +172,9 @@ func NewIntermediateTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
func NewCompatTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
func NewCompatTlsCheck(conf gas.Config) (gas.Rule, []ast.Node) {
|
||||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29
|
// https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29
|
||||||
return &InsecureConfigTLS{
|
return &InsecureConfigTLS{
|
||||||
pattern: regexp.MustCompile(`^tls\.Config$`),
|
requiredType: "tls.Config",
|
||||||
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
|
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
|
||||||
MaxVersion: 0x0303,
|
MaxVersion: 0x0303,
|
||||||
goodCiphers: []string{
|
goodCiphers: []string{
|
||||||
"TLS_RSA_WITH_RC4_128_SHA",
|
"TLS_RSA_WITH_RC4_128_SHA",
|
||||||
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
|
||||||
|
|
|
@ -1,169 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestInsecureSkipVerify(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewModernTlsCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
_, err := client.Get("https://golang.org/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "TLS InsecureSkipVerify set true")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsecureMinVersion(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewModernTlsCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{MinVersion: 0},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
_, err := client.Get("https://golang.org/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "TLS MinVersion too low")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsecureMaxVersion(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewModernTlsCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{MaxVersion: 0},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
_, err := client.Get("https://golang.org/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "TLS MaxVersion too low")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInsecureCipherSuite(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewModernTlsCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{CipherSuites: []uint16{
|
|
||||||
tls.TLS_RSA_WITH_RC4_128_SHA,
|
|
||||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
|
||||||
},},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
_, err := client.Get("https://golang.org/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "TLS Bad Cipher Suite: TLS_RSA_WITH_RC4_128_SHA")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPreferServerCipherSuites(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewModernTlsCheck(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
tr := &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{PreferServerCipherSuites: false},
|
|
||||||
}
|
|
||||||
client := &http.Client{Transport: tr}
|
|
||||||
_, err := client.Get("https://golang.org/")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 1, "TLS PreferServerCipherSuites set false")
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUnsafe(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewUsingUnsafe(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Fake struct{}
|
|
||||||
|
|
||||||
func (Fake) Good() {}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
unsafeM := Fake{}
|
|
||||||
unsafeM.Good()
|
|
||||||
intArray := [...]int{1, 2}
|
|
||||||
fmt.Printf("\nintArray: %v\n", intArray)
|
|
||||||
intPtr := &intArray[0]
|
|
||||||
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr)
|
|
||||||
addressHolder := uintptr(unsafe.Pointer(intPtr)) + unsafe.Sizeof(intArray[0])
|
|
||||||
intPtr = (*int)(unsafe.Pointer(addressHolder))
|
|
||||||
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr)
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 3, "Use of unsafe calls")
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func gasTestRunner(source string, analyzer gas.Analyzer) []*gas.Issue {
|
|
||||||
analyzer.ProcessSource("dummy.go", source)
|
|
||||||
return analyzer.Issues
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkTestResults(t *testing.T, issues []*gas.Issue, expected int, msg string) {
|
|
||||||
found := len(issues)
|
|
||||||
if found != expected {
|
|
||||||
t.Errorf("Found %d issues, expected %d", found, expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, issue := range issues {
|
|
||||||
if !strings.Contains(issue.What, msg) {
|
|
||||||
t.Errorf("Unexpected issue identified: %s", issue.What)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/GoASTScanner/gas"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMD5(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBlacklist_crypto_md5(config))
|
|
||||||
analyzer.AddRule(NewUsesWeakCryptography(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
import (
|
|
||||||
"crypto/md5"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
func main() {
|
|
||||||
for _, arg := range os.Args {
|
|
||||||
fmt.Printf("%x - %s\n", md5.Sum([]byte(arg)), arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestDES(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBlacklist_crypto_des(config))
|
|
||||||
analyzer.AddRule(NewUsesWeakCryptography(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/des"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
block, err := des.NewCipher([]byte("sekritz"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
|
|
||||||
ciphertext := make([]byte, des.BlockSize+len(plaintext))
|
|
||||||
iv := ciphertext[:des.BlockSize]
|
|
||||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stream := cipher.NewCFBEncrypter(block, iv)
|
|
||||||
stream.XORKeyStream(ciphertext[des.BlockSize:], plaintext)
|
|
||||||
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRC4(t *testing.T) {
|
|
||||||
config := map[string]interface{}{"ignoreNosec": false}
|
|
||||||
analyzer := gas.NewAnalyzer(config, nil)
|
|
||||||
analyzer.AddRule(NewBlacklist_crypto_rc4(config))
|
|
||||||
analyzer.AddRule(NewUsesWeakCryptography(config))
|
|
||||||
|
|
||||||
issues := gasTestRunner(`
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rc4"
|
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cipher, err := rc4.NewCipher([]byte("sekritz"))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
|
|
||||||
ciphertext := make([]byte, len(plaintext))
|
|
||||||
cipher.XORKeyStream(ciphertext, plaintext)
|
|
||||||
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
|
|
||||||
}
|
|
||||||
`, analyzer)
|
|
||||||
|
|
||||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
|
||||||
}
|
|
404
select.go
404
select.go
|
@ -1,404 +0,0 @@
|
||||||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
|
||||||
//
|
|
||||||
// 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 gas
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"go/ast"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SelectFunc is like an AST visitor, but has a richer interface. It
|
|
||||||
// is called with the current ast.Node being visitied and that nodes depth in
|
|
||||||
// the tree. The function can return true to continue traversing the tree, or
|
|
||||||
// false to end traversal here.
|
|
||||||
type SelectFunc func(ast.Node, int) bool
|
|
||||||
|
|
||||||
func walkIdentList(list []*ast.Ident, depth int, fun SelectFunc) {
|
|
||||||
for _, x := range list {
|
|
||||||
depthWalk(x, depth, fun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkExprList(list []ast.Expr, depth int, fun SelectFunc) {
|
|
||||||
for _, x := range list {
|
|
||||||
depthWalk(x, depth, fun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkStmtList(list []ast.Stmt, depth int, fun SelectFunc) {
|
|
||||||
for _, x := range list {
|
|
||||||
depthWalk(x, depth, fun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkDeclList(list []ast.Decl, depth int, fun SelectFunc) {
|
|
||||||
for _, x := range list {
|
|
||||||
depthWalk(x, depth, fun)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func depthWalk(node ast.Node, depth int, fun SelectFunc) {
|
|
||||||
if !fun(node, depth) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch n := node.(type) {
|
|
||||||
// Comments and fields
|
|
||||||
case *ast.Comment:
|
|
||||||
|
|
||||||
case *ast.CommentGroup:
|
|
||||||
for _, c := range n.List {
|
|
||||||
depthWalk(c, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.Field:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
walkIdentList(n.Names, depth+1, fun)
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
if n.Tag != nil {
|
|
||||||
depthWalk(n.Tag, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Comment != nil {
|
|
||||||
depthWalk(n.Comment, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.FieldList:
|
|
||||||
for _, f := range n.List {
|
|
||||||
depthWalk(f, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expressions
|
|
||||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
|
||||||
|
|
||||||
case *ast.Ellipsis:
|
|
||||||
if n.Elt != nil {
|
|
||||||
depthWalk(n.Elt, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.FuncLit:
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.CompositeLit:
|
|
||||||
if n.Type != nil {
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
}
|
|
||||||
walkExprList(n.Elts, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.ParenExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.SelectorExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
depthWalk(n.Sel, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.IndexExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
depthWalk(n.Index, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.SliceExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
if n.Low != nil {
|
|
||||||
depthWalk(n.Low, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.High != nil {
|
|
||||||
depthWalk(n.High, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Max != nil {
|
|
||||||
depthWalk(n.Max, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.TypeAssertExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
if n.Type != nil {
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.CallExpr:
|
|
||||||
depthWalk(n.Fun, depth+1, fun)
|
|
||||||
walkExprList(n.Args, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.StarExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.UnaryExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.BinaryExpr:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
depthWalk(n.Y, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.KeyValueExpr:
|
|
||||||
depthWalk(n.Key, depth+1, fun)
|
|
||||||
depthWalk(n.Value, depth+1, fun)
|
|
||||||
|
|
||||||
// Types
|
|
||||||
case *ast.ArrayType:
|
|
||||||
if n.Len != nil {
|
|
||||||
depthWalk(n.Len, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Elt, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.StructType:
|
|
||||||
depthWalk(n.Fields, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.FuncType:
|
|
||||||
if n.Params != nil {
|
|
||||||
depthWalk(n.Params, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Results != nil {
|
|
||||||
depthWalk(n.Results, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.InterfaceType:
|
|
||||||
depthWalk(n.Methods, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.MapType:
|
|
||||||
depthWalk(n.Key, depth+1, fun)
|
|
||||||
depthWalk(n.Value, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.ChanType:
|
|
||||||
depthWalk(n.Value, depth+1, fun)
|
|
||||||
|
|
||||||
// Statements
|
|
||||||
case *ast.BadStmt:
|
|
||||||
|
|
||||||
case *ast.DeclStmt:
|
|
||||||
depthWalk(n.Decl, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.EmptyStmt:
|
|
||||||
|
|
||||||
case *ast.LabeledStmt:
|
|
||||||
depthWalk(n.Label, depth+1, fun)
|
|
||||||
depthWalk(n.Stmt, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.ExprStmt:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.SendStmt:
|
|
||||||
depthWalk(n.Chan, depth+1, fun)
|
|
||||||
depthWalk(n.Value, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.IncDecStmt:
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.AssignStmt:
|
|
||||||
walkExprList(n.Lhs, depth+1, fun)
|
|
||||||
walkExprList(n.Rhs, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.GoStmt:
|
|
||||||
depthWalk(n.Call, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.DeferStmt:
|
|
||||||
depthWalk(n.Call, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.ReturnStmt:
|
|
||||||
walkExprList(n.Results, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.BranchStmt:
|
|
||||||
if n.Label != nil {
|
|
||||||
depthWalk(n.Label, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BlockStmt:
|
|
||||||
walkStmtList(n.List, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.IfStmt:
|
|
||||||
if n.Init != nil {
|
|
||||||
depthWalk(n.Init, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Cond, depth+1, fun)
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
if n.Else != nil {
|
|
||||||
depthWalk(n.Else, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.CaseClause:
|
|
||||||
walkExprList(n.List, depth+1, fun)
|
|
||||||
walkStmtList(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.SwitchStmt:
|
|
||||||
if n.Init != nil {
|
|
||||||
depthWalk(n.Init, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Tag != nil {
|
|
||||||
depthWalk(n.Tag, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.TypeSwitchStmt:
|
|
||||||
if n.Init != nil {
|
|
||||||
depthWalk(n.Init, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Assign, depth+1, fun)
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.CommClause:
|
|
||||||
if n.Comm != nil {
|
|
||||||
depthWalk(n.Comm, depth+1, fun)
|
|
||||||
}
|
|
||||||
walkStmtList(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.SelectStmt:
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.ForStmt:
|
|
||||||
if n.Init != nil {
|
|
||||||
depthWalk(n.Init, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Cond != nil {
|
|
||||||
depthWalk(n.Cond, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Post != nil {
|
|
||||||
depthWalk(n.Post, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
case *ast.RangeStmt:
|
|
||||||
if n.Key != nil {
|
|
||||||
depthWalk(n.Key, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Value != nil {
|
|
||||||
depthWalk(n.Value, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.X, depth+1, fun)
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
|
|
||||||
// Declarations
|
|
||||||
case *ast.ImportSpec:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Name != nil {
|
|
||||||
depthWalk(n.Name, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Path, depth+1, fun)
|
|
||||||
if n.Comment != nil {
|
|
||||||
depthWalk(n.Comment, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.ValueSpec:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
walkIdentList(n.Names, depth+1, fun)
|
|
||||||
if n.Type != nil {
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
}
|
|
||||||
walkExprList(n.Values, depth+1, fun)
|
|
||||||
if n.Comment != nil {
|
|
||||||
depthWalk(n.Comment, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.TypeSpec:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Name, depth+1, fun)
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
if n.Comment != nil {
|
|
||||||
depthWalk(n.Comment, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.BadDecl:
|
|
||||||
|
|
||||||
case *ast.GenDecl:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
for _, s := range n.Specs {
|
|
||||||
depthWalk(s, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
case *ast.FuncDecl:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
if n.Recv != nil {
|
|
||||||
depthWalk(n.Recv, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Name, depth+1, fun)
|
|
||||||
depthWalk(n.Type, depth+1, fun)
|
|
||||||
if n.Body != nil {
|
|
||||||
depthWalk(n.Body, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Files and packages
|
|
||||||
case *ast.File:
|
|
||||||
if n.Doc != nil {
|
|
||||||
depthWalk(n.Doc, depth+1, fun)
|
|
||||||
}
|
|
||||||
depthWalk(n.Name, depth+1, fun)
|
|
||||||
walkDeclList(n.Decls, depth+1, fun)
|
|
||||||
// don't walk n.Comments - they have been
|
|
||||||
// visited already through the individual
|
|
||||||
// nodes
|
|
||||||
|
|
||||||
case *ast.Package:
|
|
||||||
for _, f := range n.Files {
|
|
||||||
depthWalk(f, depth+1, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
panic(fmt.Sprintf("gas.depthWalk: unexpected node type %T", n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Selector interface {
|
|
||||||
Final(ast.Node)
|
|
||||||
Partial(ast.Node) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func Select(s Selector, n ast.Node, bits ...reflect.Type) {
|
|
||||||
fun := func(n ast.Node, d int) bool {
|
|
||||||
if d < len(bits) && reflect.TypeOf(n) == bits[d] {
|
|
||||||
if d == len(bits)-1 {
|
|
||||||
s.Final(n)
|
|
||||||
return false
|
|
||||||
} else if s.Partial(n) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
depthWalk(n, 0, fun)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SimpleSelect will try to match a path through a sub-tree starting at a given AST node.
|
|
||||||
// The type of each node in the path at a given depth must match its entry in list of
|
|
||||||
// node types given.
|
|
||||||
func SimpleSelect(n ast.Node, bits ...reflect.Type) ast.Node {
|
|
||||||
var found ast.Node
|
|
||||||
fun := func(n ast.Node, d int) bool {
|
|
||||||
if found != nil {
|
|
||||||
return false // short cut logic if we have found a match
|
|
||||||
}
|
|
||||||
|
|
||||||
if d < len(bits) && reflect.TypeOf(n) == bits[d] {
|
|
||||||
if d == len(bits)-1 {
|
|
||||||
found = n
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
depthWalk(n, 0, fun)
|
|
||||||
return found
|
|
||||||
}
|
|
12
testutils/log.go
Normal file
12
testutils/log.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewLogger returns a logger and the buffer that it will be written to
|
||||||
|
func NewLogger() (*log.Logger, *bytes.Buffer) {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
return log.New(&buf, "", log.Lshortfile), &buf
|
||||||
|
}
|
132
testutils/pkg.go
Normal file
132
testutils/pkg.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/build"
|
||||||
|
"go/parser"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
"golang.org/x/tools/go/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
type buildObj struct {
|
||||||
|
pkg *build.Package
|
||||||
|
config loader.Config
|
||||||
|
program *loader.Program
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestPackage struct {
|
||||||
|
Path string
|
||||||
|
Files map[string]string
|
||||||
|
ondisk bool
|
||||||
|
build *buildObj
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPackage will create a new and empty package. Must call Close() to cleanup
|
||||||
|
// auxilary files
|
||||||
|
func NewTestPackage() *TestPackage {
|
||||||
|
// Files must exist in $GOPATH
|
||||||
|
sourceDir := path.Join(os.Getenv("GOPATH"), "src")
|
||||||
|
workingDir, err := ioutil.TempDir(sourceDir, "gas_test")
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TestPackage{
|
||||||
|
Path: workingDir,
|
||||||
|
Files: make(map[string]string),
|
||||||
|
ondisk: false,
|
||||||
|
build: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddFile inserts the filename and contents into the package contents
|
||||||
|
func (p *TestPackage) AddFile(filename, content string) {
|
||||||
|
p.Files[path.Join(p.Path, filename)] = content
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *TestPackage) write() error {
|
||||||
|
if p.ondisk {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for filename, content := range p.Files {
|
||||||
|
if e := ioutil.WriteFile(filename, []byte(content), 0644); e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.ondisk = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build ensures all files are persisted to disk and built
|
||||||
|
func (p *TestPackage) Build() error {
|
||||||
|
if p.build != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := p.write(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
basePackage, err := build.Default.ImportDir(p.Path, build.ImportComment)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
packageConfig := loader.Config{Build: &build.Default, ParserMode: parser.ParseComments}
|
||||||
|
packageFiles := make([]string, 0)
|
||||||
|
for _, filename := range basePackage.GoFiles {
|
||||||
|
packageFiles = append(packageFiles, path.Join(p.Path, filename))
|
||||||
|
}
|
||||||
|
|
||||||
|
packageConfig.CreateFromFilenames(basePackage.Name, packageFiles...)
|
||||||
|
program, err := packageConfig.Load()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.build = &buildObj{
|
||||||
|
pkg: basePackage,
|
||||||
|
config: packageConfig,
|
||||||
|
program: program,
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateContext builds a context out of supplied package context
|
||||||
|
func (p *TestPackage) CreateContext(filename string) *gas.Context {
|
||||||
|
if err := p.Build(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pkg := range p.build.program.Created {
|
||||||
|
for _, file := range pkg.Files {
|
||||||
|
pkgFile := p.build.program.Fset.File(file.Pos()).Name()
|
||||||
|
strip := fmt.Sprintf("%s%c", p.Path, os.PathSeparator)
|
||||||
|
pkgFile = strings.TrimPrefix(pkgFile, strip)
|
||||||
|
if pkgFile == filename {
|
||||||
|
ctx := &gas.Context{
|
||||||
|
FileSet: p.build.program.Fset,
|
||||||
|
Root: file,
|
||||||
|
Config: gas.NewConfig(),
|
||||||
|
Info: &pkg.Info,
|
||||||
|
Pkg: pkg.Pkg,
|
||||||
|
Imports: gas.NewImportTracker(),
|
||||||
|
}
|
||||||
|
ctx.Imports.TrackPackages(ctx.Pkg.Imports()...)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close will delete the package and all files in that directory
|
||||||
|
func (p *TestPackage) Close() {
|
||||||
|
if p.ondisk {
|
||||||
|
os.RemoveAll(p.Path)
|
||||||
|
}
|
||||||
|
}
|
193
testutils/source.go
Normal file
193
testutils/source.go
Normal file
|
@ -0,0 +1,193 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
// CodeSample encapsulates a snippet of source code that compiles, and how many errors should be detected
|
||||||
|
type CodeSample struct {
|
||||||
|
Code string
|
||||||
|
Errors int
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// SampleCodeG101 code snippets for hardcoded credentials
|
||||||
|
SampleCodeG101 = []CodeSample{{`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
func main() {
|
||||||
|
username := "admin"
|
||||||
|
password := "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
fmt.Println("Doing something with: ", username, password)
|
||||||
|
}`, 1}, {`
|
||||||
|
// Entropy check should not report this error by default
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
func main() {
|
||||||
|
username := "admin"
|
||||||
|
password := "secret"
|
||||||
|
fmt.Println("Doing something with: ", username, password)
|
||||||
|
}`, 0}, {`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
var password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
func main() {
|
||||||
|
username := "admin"
|
||||||
|
fmt.Println("Doing something with: ", username, password)
|
||||||
|
}`, 1}, {`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
const password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
func main() {
|
||||||
|
username := "admin"
|
||||||
|
fmt.Println("Doing something with: ", username, password)
|
||||||
|
}`, 1}, {`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
const (
|
||||||
|
username = "user"
|
||||||
|
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
fmt.Println("Doing something with: ", username, password)
|
||||||
|
}`, 1}, {`
|
||||||
|
package main
|
||||||
|
var password string
|
||||||
|
func init() {
|
||||||
|
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
}`, 1}, {`
|
||||||
|
package main
|
||||||
|
const (
|
||||||
|
ATNStateSomethingElse = 1
|
||||||
|
ATNStateTokenStart = 42
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
println(ATNStateTokenStart)
|
||||||
|
}`, 0}, {`
|
||||||
|
package main
|
||||||
|
const (
|
||||||
|
ATNStateTokenStart = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
println(ATNStateTokenStart)
|
||||||
|
}`, 1}}
|
||||||
|
|
||||||
|
// SampleCodeG102 code snippets for network binding
|
||||||
|
SampleCodeG102 = []CodeSample{
|
||||||
|
// Bind to all networks explicitly
|
||||||
|
{`
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
l, err := net.Listen("tcp", "0.0.0.0:2000")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
}`, 1},
|
||||||
|
|
||||||
|
// Bind to all networks implicitly (default if host omitted)
|
||||||
|
{`
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
l, err := net.Listen("tcp", ":2000")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer l.Close()
|
||||||
|
}`, 1},
|
||||||
|
}
|
||||||
|
// SampleCodeG103 find instances of unsafe blocks for auditing purposes
|
||||||
|
SampleCodeG103 = []CodeSample{
|
||||||
|
{`
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
type Fake struct{}
|
||||||
|
func (Fake) Good() {}
|
||||||
|
func main() {
|
||||||
|
unsafeM := Fake{}
|
||||||
|
unsafeM.Good()
|
||||||
|
intArray := [...]int{1, 2}
|
||||||
|
fmt.Printf("\nintArray: %v\n", intArray)
|
||||||
|
intPtr := &intArray[0]
|
||||||
|
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr)
|
||||||
|
addressHolder := uintptr(unsafe.Pointer(intPtr)) + unsafe.Sizeof(intArray[0])
|
||||||
|
intPtr = (*int)(unsafe.Pointer(addressHolder))
|
||||||
|
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr)
|
||||||
|
}`, 3}}
|
||||||
|
|
||||||
|
// SampleCodeG104 finds errors that aren't being handled
|
||||||
|
SampleCodeG104 = []CodeSample{
|
||||||
|
{`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
func test() (int,error) {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
v, _ := test()
|
||||||
|
fmt.Println(v)
|
||||||
|
}`, 1}, {`
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
func a() error {
|
||||||
|
return fmt.Errorf("This is an error")
|
||||||
|
}
|
||||||
|
func b() {
|
||||||
|
fmt.Println("b")
|
||||||
|
ioutil.WriteFile("foo.txt", []byte("bar"), os.ModeExclusive)
|
||||||
|
}
|
||||||
|
func c() string {
|
||||||
|
return fmt.Sprintf("This isn't anything")
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
_ = a()
|
||||||
|
a()
|
||||||
|
b()
|
||||||
|
c()
|
||||||
|
}`, 3}, {`
|
||||||
|
package main
|
||||||
|
import "fmt"
|
||||||
|
func test() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func main() {
|
||||||
|
e := test()
|
||||||
|
fmt.Println(e)
|
||||||
|
}`, 0}}
|
||||||
|
|
||||||
|
// SampleCodeG401 - Use of weak crypto MD5
|
||||||
|
SampleCodeG401 = []CodeSample{
|
||||||
|
{`
|
||||||
|
package main
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
func main() {
|
||||||
|
f, err := os.Open("file.txt")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
h := md5.New()
|
||||||
|
if _, err := io.Copy(h, f); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%x", h.Sum(nil))
|
||||||
|
}`, 1}}
|
||||||
|
)
|
28
testutils/visitor.go
Normal file
28
testutils/visitor.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package testutils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
|
||||||
|
"github.com/GoASTScanner/gas"
|
||||||
|
)
|
||||||
|
|
||||||
|
// MockVisitor is useful for stubbing out ast.Visitor with callback
|
||||||
|
// and looking for specific conditions to exist.
|
||||||
|
type MockVisitor struct {
|
||||||
|
Context *gas.Context
|
||||||
|
Callback func(n ast.Node, ctx *gas.Context) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockVisitor creates a new empty struct, the Context and
|
||||||
|
// Callback must be set manually. See call_list_test.go for an example.
|
||||||
|
func NewMockVisitor() *MockVisitor {
|
||||||
|
return &MockVisitor{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visit satisfies the ast.Visitor interface
|
||||||
|
func (v *MockVisitor) Visit(n ast.Node) ast.Visitor {
|
||||||
|
if v.Callback(n, v.Context) {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue