mirror of
https://github.com/securego/gosec.git
synced 2026-01-15 09:53:40 +08:00
Compare commits
23 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4ba96adc3 | ||
|
|
ec0f8ec9d6 | ||
|
|
247828cfa5 | ||
|
|
b6891998ce | ||
|
|
a7cff91312 | ||
|
|
1c438e36af | ||
|
|
9577fd0b44 | ||
|
|
e543f4662c | ||
|
|
dbd0f8f511 | ||
|
|
f06a84ebaa | ||
|
|
8dfa8dc015 | ||
|
|
fb0dc73a96 | ||
|
|
90a1c1d625 | ||
|
|
0d2e16dfa3 | ||
|
|
639987a295 | ||
|
|
de10a7456f | ||
|
|
4702cc5da7 | ||
|
|
c0db486820 | ||
|
|
6919d97188 | ||
|
|
f5b44b0740 | ||
|
|
7d767b4b66 | ||
|
|
3c8707c6c4 | ||
|
|
2f61fad317 |
@@ -1,8 +1,10 @@
|
||||
FROM golang:1.9.4-alpine3.7
|
||||
FROM golang:1.10.3-alpine3.8
|
||||
|
||||
ENV BIN=gosec
|
||||
ENV GOROOT=/usr/local/go
|
||||
ENV GOPATH=/go
|
||||
|
||||
COPY dist/linux_amd64/$BIN /go/bin/$BIN
|
||||
COPY $BIN /go/bin/$BIN
|
||||
COPY docker-entrypoint.sh /usr/local/bin
|
||||
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
12
Makefile
12
Makefile
@@ -1,7 +1,9 @@
|
||||
GIT_TAG?= $(shell git describe --always --tags)
|
||||
BIN = gosec
|
||||
FMT_CMD = $(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr)
|
||||
IMAGE_REPO = docker.io
|
||||
IMAGE_REPO = securego
|
||||
BUILDFLAGS := ''
|
||||
CGO_ENABLED = 0
|
||||
|
||||
default:
|
||||
$(MAKE) bootstrap
|
||||
@@ -27,8 +29,11 @@ clean:
|
||||
release: bootstrap
|
||||
@echo "Releasing the gosec binary..."
|
||||
goreleaser release
|
||||
|
||||
build-linux:
|
||||
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -ldflags $(BUILDFLAGS) -o $(BIN) ./cmd/gosec/
|
||||
|
||||
image: release
|
||||
image: build-linux
|
||||
@echo "Building the Docker image..."
|
||||
docker build -t $(IMAGE_REPO)/$(BIN):$(GIT_TAG) .
|
||||
docker tag $(IMAGE_REPO)/$(BIN):$(GIT_TAG) $(IMAGE_REPO)/$(BIN):latest
|
||||
@@ -36,8 +41,7 @@ image: release
|
||||
|
||||
image-push: image
|
||||
@echo "Pushing the Docker image..."
|
||||
|
||||
docker push $(IMAGE_REPO)/$(BIN):$(GIT_TAG)
|
||||
docker push $(IMAGE_REPO)/$(BIN):$(GIT_TAG)
|
||||
docker push $(IMAGE_REPO)/$(BIN):latest
|
||||
|
||||
.PHONY: test build clean release image image-push
|
||||
|
||||
21
README.md
21
README.md
@@ -50,7 +50,7 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
|
||||
- G303: Creating tempfile using a predictable path
|
||||
- G304: File path provided as taint input
|
||||
- G305: File traversal when extracting zip archive
|
||||
- G401: Detect the usage of DES, RC4, or MD5
|
||||
- G401: Detect the usage of DES, RC4, MD5 or SHA1
|
||||
- G402: Look for bad TLS connection settings
|
||||
- G403: Ensure minimum RSA key length of 2048 bits
|
||||
- G404: Insecure random number source (rand)
|
||||
@@ -58,6 +58,7 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
|
||||
- G502: Import blacklist: crypto/des
|
||||
- G503: Import blacklist: crypto/rc4
|
||||
- G504: Import blacklist: net/http/cgi
|
||||
- G505: Import blacklist: crypto/sha1
|
||||
|
||||
|
||||
```
|
||||
@@ -77,8 +78,8 @@ that are not considered build artifacts by the compiler (so test files).
|
||||
|
||||
As with all automated detection tools there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe it is possible to annotate the code with a '#nosec' comment.
|
||||
|
||||
The annotation causes gosec to stop processing any further nodes within the
|
||||
AST so can apply to a whole block or more granularly to a single expression.
|
||||
The annotation causes gosec to stop processing any further nodes within the
|
||||
AST so can apply to a whole block or more granularly to a single expression.
|
||||
|
||||
```go
|
||||
|
||||
@@ -96,6 +97,8 @@ func main(){
|
||||
|
||||
```
|
||||
|
||||
When a specific false positive has been identified and verified as safe, you may wish to suppress only that single rule (or a specific set of rules) within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within the `#nosec` annotation, e.g: `/* #nosec G401 */` or `// #nosec G201 G202 G203 `
|
||||
|
||||
In some cases you may also want to revisit places where #nosec annotations
|
||||
have been used. To run the scanner and ignore any #nosec annotations you
|
||||
can do the following:
|
||||
@@ -144,9 +147,12 @@ make test
|
||||
#### Release Build
|
||||
|
||||
Make sure you have installed the [goreleaser](https://github.com/goreleaser/goreleaser) tool and then you can release gosec as follows:
|
||||
|
||||
```
|
||||
git tag 1.0.0
|
||||
export GITHUB_TOKEN=<YOUR GITHUB TOKEN>
|
||||
make release
|
||||
```
|
||||
|
||||
The released version of the tool is available in the `dist` folder. The build information should be displayed in the usage text.
|
||||
|
||||
@@ -166,18 +172,17 @@ Note that all released archives are also uploaded to GitHub.
|
||||
|
||||
#### Docker image
|
||||
|
||||
You can execute a release and build the docker image as follows:
|
||||
You can build the docker image as follows:
|
||||
|
||||
```
|
||||
git tag <VERSION>
|
||||
export GITHUB_TOKEN=<Your GitHub token>
|
||||
make image
|
||||
```
|
||||
|
||||
Now you can run the gosec tool in a container against your local workspace:
|
||||
You can run the `gosec` tool in a container against your local Go project. You just have to mount the project in the
|
||||
`GOPATH` of the container:
|
||||
|
||||
```
|
||||
docker run -it -v <YOUR LOCAL WORKSPACE>:/workspace gosec /workspace
|
||||
docker run -it -v $GOPATH/src/<YOUR PROJECT PATH>:/go/src/<YOUR PORJECT PATH> securego/gosec /go/src/<YOUR PROJECT PATH>
|
||||
```
|
||||
|
||||
#### Generate TLS rule
|
||||
|
||||
@@ -91,6 +91,12 @@ var (
|
||||
// go build tags
|
||||
flagBuildTags = flag.String("tags", "", "Comma separated list of build tags")
|
||||
|
||||
// scan the vendor folder
|
||||
flagScanVendor = flag.Bool("vendor", false, "Scan the vendor folder")
|
||||
|
||||
// fail by severity
|
||||
flagSeverity = flag.String("severity", "low", "Fail the scanning for issues with the given or higher severity. Valid options are: low, medium, high")
|
||||
|
||||
logger *log.Logger
|
||||
)
|
||||
|
||||
@@ -222,6 +228,20 @@ func resolvePackage(pkg string, searchPaths []string) string {
|
||||
return pkg
|
||||
}
|
||||
|
||||
func convertToScore(severity string) (gosec.Score, error) {
|
||||
severity = strings.ToLower(severity)
|
||||
switch severity {
|
||||
case "low":
|
||||
return gosec.Low, nil
|
||||
case "medium":
|
||||
return gosec.Medium, nil
|
||||
case "high":
|
||||
return gosec.High, nil
|
||||
default:
|
||||
return gosec.Low, fmt.Errorf("provided severity '%s' not valid. Valid options: low, medium, high", severity)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Setup usage description
|
||||
@@ -254,6 +274,11 @@ func main() {
|
||||
logger = log.New(logWriter, "[gosec] ", log.LstdFlags)
|
||||
}
|
||||
|
||||
failSeverity, err := convertToScore(*flagSeverity)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
|
||||
// Load config
|
||||
config, err := loadConfig(*flagConfig)
|
||||
if err != nil {
|
||||
@@ -262,7 +287,7 @@ func main() {
|
||||
|
||||
// Load enabled rule definitions
|
||||
ruleDefinitions := loadRules(*flagRulesInclude, *flagRulesExclude)
|
||||
if len(ruleDefinitions) <= 0 {
|
||||
if len(ruleDefinitions) == 0 {
|
||||
logger.Fatal("cannot continue: no rules are configured.")
|
||||
}
|
||||
|
||||
@@ -278,8 +303,10 @@ func main() {
|
||||
for _, pkg := range gotool.ImportPaths(cleanPaths(flag.Args())) {
|
||||
|
||||
// Skip vendor directory
|
||||
if vendor.MatchString(pkg) {
|
||||
continue
|
||||
if !*flagScanVendor {
|
||||
if vendor.MatchString(pkg) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
packages = append(packages, resolvePackage(pkg, gopaths))
|
||||
}
|
||||
@@ -295,17 +322,24 @@ func main() {
|
||||
// Collect the results
|
||||
issues, metrics := analyzer.Report()
|
||||
|
||||
issuesFound := len(issues) > 0
|
||||
// Exit quietly if nothing was found
|
||||
if !issuesFound && *flagQuiet {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Sort the issue by severity
|
||||
if *flagSortIssues {
|
||||
sortIssues(issues)
|
||||
}
|
||||
|
||||
issuesFound := false
|
||||
for _, issue := range issues {
|
||||
if issue.Severity >= failSeverity {
|
||||
issuesFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Exit quietly if nothing was found
|
||||
if !issuesFound && *flagQuiet {
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Create output report
|
||||
if err := saveOutput(*flagOutput, *flagFormat, issues, metrics); err != nil {
|
||||
logger.Fatal(err)
|
||||
|
||||
@@ -10,7 +10,7 @@ type sortBySeverity []*gosec.Issue
|
||||
|
||||
func (s sortBySeverity) Len() int { return len(s) }
|
||||
|
||||
func (s sortBySeverity) Less(i, j int) bool { return s[i].Severity > s[i].Severity }
|
||||
func (s sortBySeverity) Less(i, j int) bool { return s[i].Severity > s[j].Severity }
|
||||
|
||||
func (s sortBySeverity) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
|
||||
25
helpers.go
25
helpers.go
@@ -256,3 +256,28 @@ func GetPkgAbsPath(pkgPath string) (string, error) {
|
||||
}
|
||||
return absPath, nil
|
||||
}
|
||||
|
||||
// ConcatString recusively concatenates strings from a binary expression
|
||||
func ConcatString(n *ast.BinaryExpr) (string, bool) {
|
||||
var s string
|
||||
// sub expressions are found in X object, Y object is always last BasicLit
|
||||
if rightOperand, ok := n.Y.(*ast.BasicLit); ok {
|
||||
if str, err := GetString(rightOperand); err == nil {
|
||||
s = str + s
|
||||
}
|
||||
} else {
|
||||
return "", false
|
||||
}
|
||||
if leftOperand, ok := n.X.(*ast.BinaryExpr); ok {
|
||||
if recursion, ok := ConcatString(leftOperand); ok {
|
||||
s = recursion + s
|
||||
}
|
||||
} else if leftOperand, ok := n.X.(*ast.BasicLit); ok {
|
||||
if str, err := GetString(leftOperand); err == nil {
|
||||
s = str + s
|
||||
}
|
||||
} else {
|
||||
return "", false
|
||||
}
|
||||
return s, true
|
||||
}
|
||||
|
||||
@@ -85,3 +85,10 @@ func NewBlacklistedImportCGI(id string, conf gosec.Config) (gosec.Rule, []ast.No
|
||||
"net/http/cgi": "Blacklisted import net/http/cgi: Go versions < 1.6.3 are vulnerable to Httpoxy attack: (CVE-2016-5386)",
|
||||
})
|
||||
}
|
||||
|
||||
// NewBlacklistedImportSHA1 fails if SHA1 is imported
|
||||
func NewBlacklistedImportSHA1(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
return NewBlacklistedImports(id, conf, map[string]string{
|
||||
"crypto/sha1": "Blacklisted import crypto/sha1: weak cryptographic primitive",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ func Generate(filters ...RuleFilter) RuleList {
|
||||
{"G305", "File path traversal when extracting zip archive", NewArchive},
|
||||
|
||||
// crypto
|
||||
{"G401", "Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
|
||||
{"G401", "Detect the usage of DES, RC4, MD5 or SHA1", NewUsesWeakCryptography},
|
||||
{"G402", "Look for bad TLS connection settings", NewIntermediateTLSCheck},
|
||||
{"G403", "Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
|
||||
{"G404", "Insecure random number source (rand)", NewWeakRandCheck},
|
||||
@@ -90,6 +90,7 @@ func Generate(filters ...RuleFilter) RuleList {
|
||||
{"G502", "Import blacklist: crypto/des", NewBlacklistedImportDES},
|
||||
{"G503", "Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
|
||||
{"G504", "Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
|
||||
{"G505", "Import blacklist: crypto/sha1", NewBlacklistedImportSHA1},
|
||||
}
|
||||
|
||||
ruleMap := make(map[string]RuleDefinition)
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/securego/gosec"
|
||||
"github.com/securego/gosec/rules"
|
||||
"github.com/securego/gosec/testutils"
|
||||
@@ -110,6 +111,10 @@ var _ = Describe("gosec rules", func() {
|
||||
runner("G401", testutils.SampleCodeG401)
|
||||
})
|
||||
|
||||
It("should detect weak crypto algorithms", func() {
|
||||
runner("G401", testutils.SampleCodeG401b)
|
||||
})
|
||||
|
||||
It("should find insecure tls settings", func() {
|
||||
runner("G402", testutils.SampleCodeG402)
|
||||
})
|
||||
@@ -137,6 +142,9 @@ var _ = Describe("gosec rules", func() {
|
||||
It("should detect blacklisted imports - CGI (httpoxy)", func() {
|
||||
runner("G504", testutils.SampleCodeG504)
|
||||
})
|
||||
It("should detect blacklisted imports - SHA1", func() {
|
||||
runner("G505", testutils.SampleCodeG505)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
39
rules/sql.go
39
rules/sql.go
@@ -98,15 +98,44 @@ func NewSQLStrConcat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
|
||||
type sqlStrFormat struct {
|
||||
sqlStatement
|
||||
calls gosec.CallList
|
||||
calls gosec.CallList
|
||||
noIssue gosec.CallList
|
||||
}
|
||||
|
||||
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
|
||||
func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
|
||||
|
||||
// argIndex changes the function argument which gets matched to the regex
|
||||
argIndex := 0
|
||||
|
||||
// TODO(gm) improve confidence if database/sql is being used
|
||||
if node := s.calls.ContainsCallExpr(n, c); node != nil {
|
||||
if arg, e := gosec.GetString(node.Args[0]); s.MatchPatterns(arg) && e == nil {
|
||||
// if the function is fmt.Fprintf, search for SQL statement in Args[1] instead
|
||||
if sel, ok := node.Fun.(*ast.SelectorExpr); ok {
|
||||
if sel.Sel.Name == "Fprintf" {
|
||||
// if os.Stderr or os.Stdout is in Arg[0], mark as no issue
|
||||
if arg, ok := node.Args[0].(*ast.SelectorExpr); ok {
|
||||
if ident, ok := arg.X.(*ast.Ident); ok {
|
||||
if s.noIssue.Contains(ident.Name, arg.Sel.Name) {
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// the function is Fprintf so set argIndex = 1
|
||||
argIndex = 1
|
||||
}
|
||||
}
|
||||
// concats callexpr arg strings together if needed before regex evaluation
|
||||
if argExpr, ok := node.Args[argIndex].(*ast.BinaryExpr); ok {
|
||||
if fullStr, ok := gosec.ConcatString(argExpr); ok {
|
||||
if s.MatchPatterns(fullStr) {
|
||||
return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence),
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if arg, e := gosec.GetString(node.Args[argIndex]); s.MatchPatterns(arg) && e == nil {
|
||||
return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil
|
||||
}
|
||||
}
|
||||
@@ -116,7 +145,8 @@ func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error)
|
||||
// NewSQLStrFormat looks for cases where we're building SQL query strings using format strings
|
||||
func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
rule := &sqlStrFormat{
|
||||
calls: gosec.NewCallList(),
|
||||
calls: gosec.NewCallList(),
|
||||
noIssue: gosec.NewCallList(),
|
||||
sqlStatement: sqlStatement{
|
||||
patterns: []*regexp.Regexp{
|
||||
regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
|
||||
@@ -130,6 +160,7 @@ func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
|
||||
},
|
||||
},
|
||||
}
|
||||
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln")
|
||||
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln", "Fprintf")
|
||||
rule.noIssue.AddAll("os", "Stdout", "Stderr")
|
||||
return rule, []ast.Node{(*ast.CallExpr)(nil)}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ func NewUsesWeakCryptography(id string, conf gosec.Config) (gosec.Rule, []ast.No
|
||||
calls := make(map[string][]string)
|
||||
calls["crypto/des"] = []string{"NewCipher", "NewTripleDESCipher"}
|
||||
calls["crypto/md5"] = []string{"New", "Sum"}
|
||||
calls["crypto/sha1"] = []string{"New", "Sum"}
|
||||
calls["crypto/rc4"] = []string{"NewCipher"}
|
||||
rule := &usesWeakCryptography{
|
||||
blacklist: calls,
|
||||
|
||||
@@ -633,6 +633,31 @@ func main() {
|
||||
fmt.Printf("%x", h.Sum(nil))
|
||||
}`, 1}}
|
||||
|
||||
// SampleCodeG401b - Use of weak crypto SHA1
|
||||
SampleCodeG401b = []CodeSample{
|
||||
{`
|
||||
package main
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
func main() {
|
||||
f, err := os.Open("file.txt")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha1.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%x", h.Sum(nil))
|
||||
}`, 1}}
|
||||
|
||||
// SampleCodeG402 - TLS settings
|
||||
SampleCodeG402 = []CodeSample{{`
|
||||
// InsecureSkipVerify
|
||||
@@ -827,6 +852,20 @@ import (
|
||||
)
|
||||
func main() {
|
||||
cgi.Serve(http.FileServer(http.Dir("/usr/share/doc")))
|
||||
}`, 1}}
|
||||
// SampleCodeG505 - Blacklisted import SHA1
|
||||
SampleCodeG505 = []CodeSample{
|
||||
{`
|
||||
package main
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
func main() {
|
||||
for _, arg := range os.Args {
|
||||
fmt.Printf("%x - %s\n", sha1.Sum([]byte(arg)), arg)
|
||||
}
|
||||
}`, 1}}
|
||||
// SampleCode601 - Go build tags
|
||||
SampleCode601 = []CodeSample{{`
|
||||
|
||||
Reference in New Issue
Block a user