Compare commits

...

25 Commits
1.1.0 ... 1.2.0

Author SHA1 Message Date
Cosmin Cojocar
2695567487 Build the code sample for string builder only fron Go 1.10 onwards 2018-11-11 09:57:28 +01:00
Cosmin Cojocar
ae82798b9c Fix the WriteSring test by handling the error 2018-11-11 09:57:28 +01:00
Edoardo Tenani
adb42220da whitelist strings.Builder method in rule G104 2018-11-11 09:57:28 +01:00
Edoardo Tenani
9b966a447e add test case for strings.Builder G104 whitelist inclusion 2018-11-11 09:57:28 +01:00
Yuki Ito
41809946d4 Make G201 ignore CallExpr with no args (#262) 2018-11-05 09:28:47 +01:00
Yuki Ito
443f84fd4d Fix golint link (#263) 2018-11-05 09:13:26 +01:00
Oleksandr Redko
3116b07de4 Fix typos in comments and rulelist (#256) 2018-10-11 14:45:31 +02:00
Cosmin Cojocar
e0a150bfa3 Merge pull request #254 from kishaningithub/253
Add install.sh script and update readme
2018-10-05 13:12:28 +02:00
Kishan B
97bc137c5b Add CI Installation steps and correct markdown lint errors 2018-10-05 15:27:14 +05:30
Kishan B
8c09a83248 Add install.sh script 2018-10-05 15:26:13 +05:30
Cosmin Cojocar
d032909e3f Merge pull request #251 from NeverOddOrEven/fix-html-template
Fix the html template
2018-10-04 09:39:56 +02:00
NeverOddOrEven
027dc2b8a7 This fixes the html template when using '-fmt=html'
- resolves HTML escaping issues within the template
 - resolves reference issues to reportInfo struct i.e. issues -> Issues, metrics -> Stats
2018-10-03 13:31:59 -05:00
Cosmin Cojocar
f9b41874b1 Merge pull request #249 from andrewhsu/go
bump Dockerfile golang from 1.10 to 1.11
2018-10-03 08:35:47 +02:00
Andrew Hsu
1ecd47e007 bump Dockerfile golang from 1.10 to 1.11
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-10-03 00:28:39 +00:00
Cosmin Cojocar
2cc6838ca3 Merge pull request #248 from ccojocar/code-samples-multiple-files
Refactor the test code sample to support multiple files per sample
2018-09-28 11:52:04 +03:00
Cosmin Cojocar
64d58c2e51 Refactor the test code sample to support multiple files per sample 2018-09-28 11:42:25 +03:00
Delon Wong Her Laang
d3f1980e7a Fix false positives for SQL string concatenation with constants from another file (#247)
* Allow for SQL concatenation of nodes that resolve to literals

If node.Y resolves to a literal, it will not be considered as an issue.

* Fix typo in comment.

* Go through all files in package to resolve that identifier

* Refactor code and added comments.

* Changed checking to not var or func.

* Allow for supporting code for test cases.

* Resolve merge conflict changes.
2018-09-28 10:46:59 +03:00
Andrew Hsu
5f98926a7b Refactor Dockerfile (#245)
* ignore the temporary image file used for builds

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* no need for GOPATH in the Dockerfile

It is already set in the golang:1.10.3-alpine3.8 image.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* no need for GOROOT in Dockerfile

The correct value is embedded in the go tool.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* bump Dockerfile golang to 1.10.4

The latest golang version thus far.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* replace docker-entrypoint.sh with the gosec binary

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* git ignore gosec binary

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* refactor Dockerfile into multi-stage

First stage does the build in a pristine alpine environment. Second
stage is a minimal image with just the necessary stuff to run the
compiled binary. Also added packages for gcc and musl-dev so cgo can do
its thang.

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>

* fix the image execution example in README.md

Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2018-09-26 08:09:20 +03:00
Grant Murphy
7f6509a916 Update README.md (#246)
Add logo to README.md
2018-09-25 19:44:53 +10:00
Dale Hui
762ff3a709 Allow quoted strings to be used to format SQL queries (#240)
* Support stripping vendor paths when matching calls

* Factor out matching of formatter string

* Quoted strings are safe to use with SQL str formatted strings

* Add test for allowing quoted strings with string formatters

* Install the pq package for tests to pass
2018-09-25 10:40:05 +03:00
Dale Hui
ec32ce68d8 Support Go 1.11 (#239)
* Test with the latest minor version of each major Go version

* Support Go 1.11 and modules
2018-09-10 09:09:12 +02:00
cschoenduve-splunk
145f1a0bf4 Removed wrapping feature (#238) 2018-09-04 18:08:37 +02:00
cschoenduve-splunk
419c9292c8 G107 - SSRF (#236)
* Initial SSRF Rule

* Added Selector evaluation

* Added source code tests

* Fixed spacing issues

* Fixed Spacingv2

* Removed resty test
2018-09-04 08:55:03 +02:00
Dom Udall 改善
63b25c147f Fix typo in README (#235)
`PORJECT` -> `PROJECT`
2018-09-03 09:39:31 +02:00
cschoenduve-splunk
7fd94463ed update to G304 which adds binary expressions and file joining (#233)
* Added features to G304

* Linted

* Added path selectors

* Used better solution

* removed debugging lines

* fixed comments

* Added test code

* fixed a spacing change
2018-08-28 14:34:07 +10:00
37 changed files with 1073 additions and 261 deletions

4
.gitignore vendored
View File

@@ -1,8 +1,12 @@
# transient files
/image
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
*.swp
/gosec
# Folders
_obj

View File

@@ -1,3 +1,11 @@
---
project_name: gosec
release:
github:
owner: securego
name: gosec
builds:
- main : ./cmd/gosec/
binary: gosec

View File

@@ -1,16 +1,18 @@
language: go
go:
- 1.9
- "1.10"
- "1.9.x"
- "1.10.x"
- "1.11.x"
- tip
install:
- go get -u github.com/golang/dep/cmd/dep
- go get -u github.com/golang/lint/golint
- go get -u golang.org/x/lint/golint
- go get -u github.com/onsi/ginkgo/ginkgo
- go get -u github.com/onsi/gomega
- go get -u golang.org/x/crypto/ssh
- go get -u github.com/lib/pq
- go get -u github.com/securego/gosec/cmd/gosec/...
- go get -v -t ./...
- export PATH=$PATH:$HOME/gopath/bin

View File

@@ -1,10 +1,11 @@
FROM golang:1.10.3-alpine3.8
FROM golang:1.11.1-alpine3.8 as build
WORKDIR /go/src/github.com/securego/gosec
COPY . .
RUN apk add -U git make
RUN go get -u github.com/golang/dep/cmd/dep
RUN make
ENV BIN=gosec
ENV GOROOT=/usr/local/go
ENV GOPATH=/go
COPY $BIN /go/bin/$BIN
COPY docker-entrypoint.sh /usr/local/bin
ENTRYPOINT ["docker-entrypoint.sh"]
FROM golang:1.11.1-alpine3.8
RUN apk add -U gcc musl-dev
COPY --from=build /go/src/github.com/securego/gosec/gosec /usr/local/bin/gosec
ENTRYPOINT ["gosec"]

View File

@@ -33,7 +33,7 @@ release: bootstrap
build-linux:
CGO_ENABLED=$(CGO_ENABLED) GOOS=linux GOARCH=amd64 go build -ldflags $(BUILDFLAGS) -o $(BIN) ./cmd/gosec/
image: build-linux
image:
@echo "Building the Docker image..."
docker build -t $(IMAGE_REPO)/$(BIN):$(GIT_TAG) .
docker tag $(IMAGE_REPO)/$(BIN):$(GIT_TAG) $(IMAGE_REPO)/$(BIN):latest

146
README.md
View File

@@ -1,67 +1,84 @@
## gosec -Golang Security Checker
# gosec - Golang Security Checker
Inspects source code for security problems by scanning the Go AST.
### License
<img src="https://securego.io/img/gosec.png" width="320">
## License
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 [here](http://www.apache.org/licenses/LICENSE-2.0).
### Project status
## Project status
[![Build Status](https://travis-ci.org/securego/gosec.svg?branch=master)](https://travis-ci.org/securego/gosec)
[![GoDoc](https://godoc.org/github.com/securego/gosec?status.svg)](https://godoc.org/github.com/securego/gosec)
[![Slack](http://securego.herokuapp.com/badge.svg)](http://securego.herokuapp.com)
## Install
### Install
### CI Installation
```bash
# binary will be $GOPATH/bin/gosec
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s -- -b $GOPATH/bin vX.Y.Z
# or install it into ./bin/
curl -sfL https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s vX.Y.Z
# In alpine linux (as it does not come with curl by default)
wget -O - -q https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s vX.Y.Z
gosec --help
```
### Local Installation
`$ go get github.com/securego/gosec/cmd/gosec/...`
### Usage
## Usage
Gosec can be configured to only run a subset of rules, to exclude certain file
paths, and produce reports in different formats. By default all rules will be
run against the supplied input files. To recursively scan from the current
directory you can supply './...' as the input argument.
#### Selecting rules
### Selecting rules
By default gosec will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-include=' flag,
or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
##### Available rules
### Available rules
- G101: Look for hardcoded credentials
- G102: Bind to all interfaces
- G103: Audit the use of unsafe block
- G104: Audit errors not checked
- G105: Audit the use of math/big.Int.Exp
- G106: Audit the use of ssh.InsecureIgnoreHostKey
- G201: SQL query construction using format string
- G202: SQL query construction using string concatenation
- G203: Use of unescaped data in HTML templates
- G204: Audit use of command execution
- G301: Poor file permissions used when creating a directory
- G302: Poor file permisions used with chmod
- 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, 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)
- G501: Import blacklist: crypto/md5
- G502: Import blacklist: crypto/des
- G503: Import blacklist: crypto/rc4
- G504: Import blacklist: net/http/cgi
- G505: Import blacklist: crypto/sha1
- G101: Look for hard coded credentials
- G102: Bind to all interfaces
- G103: Audit the use of unsafe block
- G104: Audit errors not checked
- G105: Audit the use of math/big.Int.Exp
- G106: Audit the use of ssh.InsecureIgnoreHostKey
- G107: Url provided to HTTP request as taint input
- G201: SQL query construction using format string
- G202: SQL query construction using string concatenation
- G203: Use of unescaped data in HTML templates
- G204: Audit use of command execution
- G301: Poor file permissions used when creating a directory
- G302: Poor file permissions used with chmod
- 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, 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)
- G501: Import blacklist: crypto/md5
- G502: Import blacklist: crypto/des
- G503: Import blacklist: crypto/rc4
- G504: Import blacklist: net/http/cgi
- G505: Import blacklist: crypto/sha1
```
```bash
# Run a specific set of rules
$ gosec -include=G101,G203,G401 ./...
@@ -69,17 +86,17 @@ $ gosec -include=G101,G203,G401 ./...
$ gosec -exclude=G303 ./...
```
#### Excluding files:
### Excluding files
gosec will ignore dependencies in your vendor directory any files
that are not considered build artifacts by the compiler (so test files).
#### Annotating code
### Annotating code
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.
AST so can apply to a whole block or more granularly to a single expression.
```go
@@ -103,16 +120,17 @@ 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:
```bash
gosec -nosec=true ./...
```
$ gosec -nosec=true ./...
```
#### Build tags
### Build tags
gosec is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer.
They can be provided as a comma separated list as follows:
```
$ gosec -tag debug,ignore ./...
```bash
gosec -tag debug,ignore ./...
```
### Output formats
@@ -121,34 +139,39 @@ gosec currently supports text, json, yaml, csv and JUnit XML output formats. By
results will be reported to stdout, but can also be written to an output
file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows:
```
```bash
# Write output in json format to results.json
$ gosec -fmt=json -out=results.json *.go
```
### Development
#### Prerequisites
## Development
### Prerequisites
Install dep according to the instructions here: https://github.com/golang/dep
Install the latest version of golint: https://github.com/golang/lint
#### Build
Install the latest version of golint:
```bash
go get -u golang.org/x/lint/golint
```
### Build
```bash
make
```
#### Tests
### Tests
```
```bash
make test
```
#### Release Build
### Release Build
Make sure you have installed the [goreleaser](https://github.com/goreleaser/goreleaser) tool and then you can release gosec as follows:
```
```bash
git tag 1.0.0
export GITHUB_TOKEN=<YOUR GITHUB TOKEN>
make release
@@ -156,7 +179,7 @@ make release
The released version of the tool is available in the `dist` folder. The build information should be displayed in the usage text.
```
```bash
./dist/darwin_amd64/gosec -h
gosec - Golang security checker
@@ -170,35 +193,34 @@ BUILD DATE: 2018-04-27T12:41:38Z
Note that all released archives are also uploaded to GitHub.
#### Docker image
### Docker image
You can build the docker image as follows:
```
```bash
make image
```
You can run the `gosec` tool in a container against your local Go project. You just have to mount the project in the
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 $GOPATH/src/<YOUR PROJECT PATH>:/go/src/<YOUR PORJECT PATH> securego/gosec /go/src/<YOUR PROJECT PATH>
```bash
docker run -it -v $GOPATH/src/<YOUR PROJECT PATH>:/go/src/<YOUR PROJECT PATH> securego/gosec ./...
```
#### Generate TLS rule
### Generate TLS rule
The configuration of TLS rule can be generated from [Mozilla's TLS ciphers recommendation](https://statics.tls.security.mozilla.org/server-side-tls-conf.json).
First you need to install the generator tool:
```
```bash
go get github.com/securego/gosec/cmd/tlsconfig/...
```
You can invoke now the `go generate` in the root of the project:
```
```bash
go generate ./...
```

View File

@@ -33,12 +33,13 @@ import (
// The Context is populated with data parsed from the source code as it is scanned.
// It is passed through to all rule functions as they are called. Rules may use
// this data in conjunction withe the encoutered AST node.
// this data in conjunction withe the encountered AST node.
type Context struct {
FileSet *token.FileSet
Comments ast.CommentMap
Info *types.Info
Pkg *types.Package
PkgFiles []*ast.File
Root *ast.File
Config map[string]interface{}
Imports *ImportTracker
@@ -65,7 +66,7 @@ type Analyzer struct {
stats *Metrics
}
// NewAnalyzer builds a new anaylzer.
// NewAnalyzer builds a new analyzer.
func NewAnalyzer(conf Config, logger *log.Logger) *Analyzer {
ignoreNoSec := false
if setting, err := conf.GetGlobal("nosec"); err == nil {
@@ -139,6 +140,7 @@ func (gosec *Analyzer) Process(buildTags []string, packagePaths ...string) error
gosec.context.Root = file
gosec.context.Info = &pkg.Info
gosec.context.Pkg = pkg.Pkg
gosec.context.PkgFiles = pkg.Files
gosec.context.Imports = NewImportTracker()
gosec.context.Imports.TrackPackages(gosec.context.Pkg.Imports()...)
ast.Walk(gosec, file)

View File

@@ -51,7 +51,7 @@ var _ = Describe("Analyzer", func() {
})
It("should be able to analyze mulitple Go files", func() {
It("should be able to analyze multiple Go files", func() {
analyzer.LoadRules(rules.Generate().Builders())
pkg := testutils.NewTestPackage()
defer pkg.Close()
@@ -72,7 +72,7 @@ var _ = Describe("Analyzer", func() {
Expect(metrics.NumFiles).To(Equal(2))
})
It("should be able to analyze mulitple Go packages", func() {
It("should be able to analyze multiple Go packages", func() {
analyzer.LoadRules(rules.Generate().Builders())
pkg1 := testutils.NewTestPackage()
pkg2 := testutils.NewTestPackage()
@@ -98,7 +98,7 @@ var _ = Describe("Analyzer", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
controlPackage := testutils.NewTestPackage()
@@ -114,7 +114,7 @@ var _ = Describe("Analyzer", func() {
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
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
@@ -131,7 +131,7 @@ var _ = Describe("Analyzer", func() {
It("should not report errors when an exclude comment is present for the correct rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
@@ -148,7 +148,7 @@ var _ = Describe("Analyzer", func() {
It("should report errors when an exclude comment is present for a different rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
@@ -165,7 +165,7 @@ var _ = Describe("Analyzer", func() {
It("should not report errors when an exclude comment is present for multiple rules, including the correct rule", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders())
nosecPackage := testutils.NewTestPackage()
@@ -181,7 +181,7 @@ var _ = Describe("Analyzer", func() {
It("should pass the build tags", func() {
sample := testutils.SampleCode601[0]
source := sample.Code
source := sample.Code[0]
analyzer.LoadRules(rules.Generate().Builders())
pkg := testutils.NewTestPackage()
defer pkg.Close()
@@ -197,7 +197,7 @@ var _ = Describe("Analyzer", func() {
// Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0]
source := sample.Code
source := sample.Code[0]
// overwrite nosec option
nosecIgnoreConfig := gosec.NewConfig()

View File

@@ -15,8 +15,11 @@ package gosec
import (
"go/ast"
"strings"
)
const vendorPath = "vendor/"
type set map[string]bool
// CallList is used to check for usage of specific packages
@@ -55,17 +58,27 @@ func (c CallList) Contains(selector, ident string) bool {
// ContainsCallExpr resolves the call expression name and type
/// or package and determines if it exists within the CallList
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context, stripVendor bool) *ast.CallExpr {
selector, ident, err := GetCallInfo(n, ctx)
if err != nil {
return nil
}
// Use only explicit path to reduce conflicts
if path, ok := GetImportPath(selector, ctx); ok && c.Contains(path, ident) {
return n.(*ast.CallExpr)
// Use only explicit path (optionally strip vendor path prefix) to reduce conflicts
path, ok := GetImportPath(selector, ctx)
if !ok {
return nil
}
if stripVendor {
if vendorIdx := strings.Index(path, vendorPath); vendorIdx >= 0 {
path = path[vendorIdx+len(vendorPath):]
}
}
if !c.Contains(path, ident) {
return nil
}
return n.(*ast.CallExpr)
/*
// Try direct resolution
if c.Contains(selector, ident) {
@@ -74,5 +87,4 @@ func (c CallList) ContainsCallExpr(n ast.Node, ctx *Context) *ast.CallExpr {
}
*/
return nil
}

View File

@@ -61,7 +61,7 @@ var _ = Describe("call list", func() {
// Create file to be scanned
pkg := testutils.NewTestPackage()
defer pkg.Close()
pkg.AddFile("md5.go", testutils.SampleCodeG401[0].Code)
pkg.AddFile("md5.go", testutils.SampleCodeG401[0].Code[0])
ctx := pkg.CreateContext("md5.go")
@@ -73,7 +73,7 @@ var _ = Describe("call list", func() {
v := testutils.NewMockVisitor()
v.Context = ctx
v.Callback = func(n ast.Node, ctx *gosec.Context) bool {
if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx) != nil {
if _, ok := n.(*ast.CallExpr); ok && calls.ContainsCallExpr(n, ctx, false) != nil {
matched++
}
return true

View File

@@ -345,7 +345,7 @@ func main() {
logger.Fatal(err)
}
// Finialize logging
// Finalize logging
logWriter.Close() // #nosec
// Do we have an issue? If so exit 1

View File

@@ -62,7 +62,7 @@ type goTLSConfiguration struct {
// getTLSConfFromURL retrieves the json containing the TLS configurations from the specified URL.
func getTLSConfFromURL(url string) (*ServerSideTLSJson, error) {
r, err := http.Get(url)
r, err := http.Get(url) // #nosec G107
if err != nil {
return nil, err
}

View File

@@ -78,7 +78,7 @@ func (c Config) GetGlobal(option string) (string, error) {
}
// SetGlobal associates a value with a global configuration ooption
// SetGlobal associates a value with a global configuration option
func (c Config) SetGlobal(option, value string) {
if globals, ok := c[Globals]; ok {
if settings, ok := globals.(map[string]string); ok {

View File

@@ -1,2 +0,0 @@
#!/usr/bin/env sh
${BIN} "$@"

22
go.mod Normal file
View File

@@ -0,0 +1,22 @@
module github.com/securego/gosec
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.2.0 // indirect
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3
github.com/kr/pretty v0.1.0 // indirect
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735
github.com/stretchr/testify v1.2.2 // indirect
golang.org/x/net v0.0.0-20170915142106-8351a756f30f // indirect
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce // indirect
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80 // indirect
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7
)

39
go.sum Normal file
View File

@@ -0,0 +1,39 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3 h1:s/sV9geKJwXXzcrFiQdiiIFgfesbREplXWR9ZFgnGSQ=
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40 h1:Q0XH6Ql1+Z6YbUKyWyI0sD8/9yH0U8x86yA8LuWMJwY=
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd h1:hEzcdYzgmGA1zDrSYdh+OE4H43RrglXdZQ5ip/+93GU=
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c h1:Hww8mOyEKTeON4bZn7FrlLismspbPc1teNRUVH7wLQ8=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c h1:eSfnfIuwhxZyULg1NNuZycJcYkjYVGYe7FczwQReM6U=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735 h1:7YvPJVmEeFHR1Tj9sZEYsmarJEQfMVYpd/Vyy/A8dqE=
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f h1:gBDN4vcizo3zTVoOZWdw1W3KB3Yh9lxB8I1uOgf/7n0=
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce h1:BDMHZhZQhI6KuA6MzarSMksZq8ZegBJ3mSbFKLEYG/w=
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80 h1:LMxnNSL1jel8frQKy+gjCcwcgLsd3UEDVGg9DD8ryxw=
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f h1:2bTOCVQtYN868SqJlTyB1SOrvrmeurDB7H5ylUynHsY=
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=

View File

@@ -166,7 +166,7 @@ func GetCallInfo(n ast.Node, ctx *Context) (string, string, error) {
}
// GetImportedName returns the name used for the package within the
// code. It will resolve aliases and ignores initalization only imports.
// code. It will resolve aliases and ignores initialization only imports.
func GetImportedName(path string, ctx *Context) (string, bool) {
importName, imported := ctx.Imports.Imported[path]
if !imported {
@@ -183,7 +183,7 @@ func GetImportedName(path string, ctx *Context) (string, bool) {
return importName, true
}
// GetImportPath resolves the full import path of an identifer based on
// GetImportPath resolves the full import path of an identifier based on
// the imports in the current context.
func GetImportPath(name string, ctx *Context) (string, bool) {
for path := range ctx.Imports.Imported {
@@ -257,7 +257,7 @@ func GetPkgAbsPath(pkgPath string) (string, error) {
return absPath, nil
}
// ConcatString recusively concatenates strings from a binary expression
// ConcatString recursively 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
@@ -281,3 +281,33 @@ func ConcatString(n *ast.BinaryExpr) (string, bool) {
}
return s, true
}
// FindVarIdentities returns array of all variable identities in a given binary expression
func FindVarIdentities(n *ast.BinaryExpr, c *Context) ([]*ast.Ident, bool) {
identities := []*ast.Ident{}
// sub expressions are found in X object, Y object is always the last term
if rightOperand, ok := n.Y.(*ast.Ident); ok {
obj := c.Info.ObjectOf(rightOperand)
if _, ok := obj.(*types.Var); ok && !TryResolve(rightOperand, c) {
identities = append(identities, rightOperand)
}
}
if leftOperand, ok := n.X.(*ast.BinaryExpr); ok {
if leftIdentities, ok := FindVarIdentities(leftOperand, c); ok {
identities = append(identities, leftIdentities...)
}
} else {
if leftOperand, ok := n.X.(*ast.Ident); ok {
obj := c.Info.ObjectOf(leftOperand)
if _, ok := obj.(*types.Var); ok && !TryResolve(leftOperand, c) {
identities = append(identities, leftOperand)
}
}
}
if len(identities) > 0 {
return identities, true
}
// if nil or error, return false
return nil, false
}

381
install.sh Normal file
View File

@@ -0,0 +1,381 @@
#!/bin/sh
set -e
# Code generated by godownloader on 2018-10-05T09:52:28Z. DO NOT EDIT.
#
usage() {
this=$1
cat <<EOF
$this: download go binaries for securego/gosec
Usage: $this [-b] bindir [-d] [tag]
-b sets bindir or installation directory, Defaults to ./bin
-d turns on debug logging
[tag] is a tag from
https://github.com/securego/gosec/releases
If tag is missing, then the latest will be used.
Generated by godownloader
https://github.com/goreleaser/godownloader
EOF
exit 2
}
parse_args() {
#BINDIR is ./bin unless set be ENV
# over-ridden by flag below
BINDIR=${BINDIR:-./bin}
while getopts "b:dh?" arg; do
case "$arg" in
b) BINDIR="$OPTARG" ;;
d) log_set_priority 10 ;;
h | \?) usage "$0" ;;
esac
done
shift $((OPTIND - 1))
TAG=$1
}
# this function wraps all the destructive operations
# if a curl|bash cuts off the end of the script due to
# network, either nothing will happen or will syntax error
# out preventing half-done work
execute() {
tmpdir=$(mktmpdir)
log_debug "downloading files into ${tmpdir}"
http_download "${tmpdir}/${TARBALL}" "${TARBALL_URL}"
http_download "${tmpdir}/${CHECKSUM}" "${CHECKSUM_URL}"
hash_sha256_verify "${tmpdir}/${TARBALL}" "${tmpdir}/${CHECKSUM}"
srcdir="${tmpdir}"
(cd "${tmpdir}" && untar "${TARBALL}")
install -d "${BINDIR}"
for binexe in "gosec" ; do
if [ "$OS" = "windows" ]; then
binexe="${binexe}.exe"
fi
install "${srcdir}/${binexe}" "${BINDIR}/"
log_info "installed ${BINDIR}/${binexe}"
done
}
is_supported_platform() {
platform=$1
found=1
case "$platform" in
darwin/amd64) found=0 ;;
linux/amd64) found=0 ;;
windows/amd64) found=0 ;;
esac
return $found
}
check_platform() {
if is_supported_platform "$PLATFORM"; then
# optional logging goes here
true
else
log_crit "platform $PLATFORM is not supported. Make sure this script is up-to-date and file request at https://github.com/${PREFIX}/issues/new"
exit 1
fi
}
tag_to_version() {
if [ -z "${TAG}" ]; then
log_info "checking GitHub for latest tag"
else
log_info "checking GitHub for tag '${TAG}'"
fi
REALTAG=$(github_release "$OWNER/$REPO" "${TAG}") && true
if test -z "$REALTAG"; then
log_crit "unable to find '${TAG}' - use 'latest' or see https://github.com/${PREFIX}/releases for details"
exit 1
fi
# if version starts with 'v', remove it
TAG="$REALTAG"
VERSION=${TAG#v}
}
adjust_format() {
# change format (tar.gz or zip) based on ARCH
true
}
adjust_os() {
# adjust archive name based on OS
true
}
adjust_arch() {
# adjust archive name based on ARCH
true
}
cat /dev/null <<EOF
------------------------------------------------------------------------
https://github.com/client9/shlib - portable posix shell functions
Public domain - http://unlicense.org
https://github.com/client9/shlib/blob/master/LICENSE.md
but credit (and pull requests) appreciated.
------------------------------------------------------------------------
EOF
is_command() {
command -v "$1" >/dev/null
}
echoerr() {
echo "$@" 1>&2
}
log_prefix() {
echo "$0"
}
_logp=6
log_set_priority() {
_logp="$1"
}
log_priority() {
if test -z "$1"; then
echo "$_logp"
return
fi
[ "$1" -le "$_logp" ]
}
log_tag() {
case $1 in
0) echo "emerg" ;;
1) echo "alert" ;;
2) echo "crit" ;;
3) echo "err" ;;
4) echo "warning" ;;
5) echo "notice" ;;
6) echo "info" ;;
7) echo "debug" ;;
*) echo "$1" ;;
esac
}
log_debug() {
log_priority 7 || return 0
echoerr "$(log_prefix)" "$(log_tag 7)" "$@"
}
log_info() {
log_priority 6 || return 0
echoerr "$(log_prefix)" "$(log_tag 6)" "$@"
}
log_err() {
log_priority 3 || return 0
echoerr "$(log_prefix)" "$(log_tag 3)" "$@"
}
log_crit() {
log_priority 2 || return 0
echoerr "$(log_prefix)" "$(log_tag 2)" "$@"
}
uname_os() {
os=$(uname -s | tr '[:upper:]' '[:lower:]')
case "$os" in
msys_nt) os="windows" ;;
esac
echo "$os"
}
uname_arch() {
arch=$(uname -m)
case $arch in
x86_64) arch="amd64" ;;
x86) arch="386" ;;
i686) arch="386" ;;
i386) arch="386" ;;
aarch64) arch="arm64" ;;
armv5*) arch="armv5" ;;
armv6*) arch="armv6" ;;
armv7*) arch="armv7" ;;
esac
echo ${arch}
}
uname_os_check() {
os=$(uname_os)
case "$os" in
darwin) return 0 ;;
dragonfly) return 0 ;;
freebsd) return 0 ;;
linux) return 0 ;;
android) return 0 ;;
nacl) return 0 ;;
netbsd) return 0 ;;
openbsd) return 0 ;;
plan9) return 0 ;;
solaris) return 0 ;;
windows) return 0 ;;
esac
log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib"
return 1
}
uname_arch_check() {
arch=$(uname_arch)
case "$arch" in
386) return 0 ;;
amd64) return 0 ;;
arm64) return 0 ;;
armv5) return 0 ;;
armv6) return 0 ;;
armv7) return 0 ;;
ppc64) return 0 ;;
ppc64le) return 0 ;;
mips) return 0 ;;
mipsle) return 0 ;;
mips64) return 0 ;;
mips64le) return 0 ;;
s390x) return 0 ;;
amd64p32) return 0 ;;
esac
log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib"
return 1
}
untar() {
tarball=$1
case "${tarball}" in
*.tar.gz | *.tgz) tar -xzf "${tarball}" ;;
*.tar) tar -xf "${tarball}" ;;
*.zip) unzip "${tarball}" ;;
*)
log_err "untar unknown archive format for ${tarball}"
return 1
;;
esac
}
mktmpdir() {
test -z "$TMPDIR" && TMPDIR="$(mktemp -d)"
mkdir -p "${TMPDIR}"
echo "${TMPDIR}"
}
http_download_curl() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url")
else
code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url")
fi
if [ "$code" != "200" ]; then
log_debug "http_download_curl received HTTP status $code"
return 1
fi
return 0
}
http_download_wget() {
local_file=$1
source_url=$2
header=$3
if [ -z "$header" ]; then
wget -q -O "$local_file" "$source_url"
else
wget -q --header "$header" -O "$local_file" "$source_url"
fi
}
http_download() {
log_debug "http_download $2"
if is_command curl; then
http_download_curl "$@"
return
elif is_command wget; then
http_download_wget "$@"
return
fi
log_crit "http_download unable to find wget or curl"
return 1
}
http_copy() {
tmp=$(mktemp)
http_download "${tmp}" "$1" "$2" || return 1
body=$(cat "$tmp")
rm -f "${tmp}"
echo "$body"
}
github_release() {
owner_repo=$1
version=$2
test -z "$version" && version="latest"
giturl="https://github.com/${owner_repo}/releases/${version}"
json=$(http_copy "$giturl" "Accept:application/json")
test -z "$json" && return 1
version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//')
test -z "$version" && return 1
echo "$version"
}
hash_sha256() {
TARGET=${1:-/dev/stdin}
if is_command gsha256sum; then
hash=$(gsha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command sha256sum; then
hash=$(sha256sum "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command shasum; then
hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1
echo "$hash" | cut -d ' ' -f 1
elif is_command openssl; then
hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1
echo "$hash" | cut -d ' ' -f a
else
log_crit "hash_sha256 unable to find command to compute sha-256 hash"
return 1
fi
}
hash_sha256_verify() {
TARGET=$1
checksums=$2
if [ -z "$checksums" ]; then
log_err "hash_sha256_verify checksum file not specified in arg2"
return 1
fi
BASENAME=${TARGET##*/}
want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1)
if [ -z "$want" ]; then
log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'"
return 1
fi
got=$(hash_sha256 "$TARGET")
if [ "$want" != "$got" ]; then
log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got"
return 1
fi
}
cat /dev/null <<EOF
------------------------------------------------------------------------
End of functions from https://github.com/client9/shlib
------------------------------------------------------------------------
EOF
PROJECT_NAME="gosec"
OWNER=securego
REPO="gosec"
BINARY=gosec
FORMAT=tar.gz
OS=$(uname_os)
ARCH=$(uname_arch)
PREFIX="$OWNER/$REPO"
# use in logging routines
log_prefix() {
echo "$PREFIX"
}
PLATFORM="${OS}/${ARCH}"
GITHUB_DOWNLOAD=https://github.com/${OWNER}/${REPO}/releases/download
uname_os_check "$OS"
uname_arch_check "$ARCH"
parse_args "$@"
check_platform
tag_to_version
adjust_format
adjust_os
adjust_arch
log_info "found version: ${VERSION} for ${TAG}/${OS}/${ARCH}"
NAME=${PROJECT_NAME}_${VERSION}_${OS}_${ARCH}
TARBALL=${NAME}.${FORMAT}
TARBALL_URL=${GITHUB_DOWNLOAD}/${TAG}/${TARBALL}
CHECKSUM=${PROJECT_NAME}_${VERSION}_checksums.txt
CHECKSUM_URL=${GITHUB_DOWNLOAD}/${TAG}/${CHECKSUM}
execute

View File

@@ -34,7 +34,7 @@ const (
High
)
// Issue is returnd by a gosec rule if it discovers an issue with the scanned code.
// Issue is returned by a gosec rule if it discovers an issue with the scanned code.
type Issue struct {
Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
@@ -46,7 +46,7 @@ type Issue struct {
}
// MetaData is embedded in all gosec rules. The Severity, Confidence and What message
// will be passed tbhrough to reported issues.
// will be passed through to reported issues.
type MetaData struct {
ID string
Severity Score

View File

@@ -26,7 +26,7 @@ import (
"gopkg.in/yaml.v2"
)
// ReportFormat enumrates the output format for reported issues
// ReportFormat enumerates the output format for reported issues
type ReportFormat int
const (

View File

@@ -62,7 +62,7 @@ const html = `
level = "is-warning";
}
return (
<div className={ "tag " + level }>
<div className="tag { level }">
{ this.props.label }: { this.props.level }
</div>
);
@@ -100,8 +100,8 @@ const html = `
render: function() {
return (
<p className="help">
Scanned { this.props.data.metrics.files.toLocaleString() } files
with { this.props.data.metrics.lines.toLocaleString() } lines of code.
Scanned { this.props.data.Stats.files.toLocaleString() } files
with { this.props.data.Stats.lines.toLocaleString() } lines of code.
</p>
);
}
@@ -109,7 +109,7 @@ const html = `
var Issues = React.createClass({
render: function() {
if (this.props.data.metrics.files === 0) {
if (this.props.data.Stats.files === 0) {
return (
<div className="notification">
No source files found. Do you even Go?
@@ -117,7 +117,7 @@ const html = `
);
}
if (this.props.data.issues.length === 0) {
if (this.props.data.Issues.length === 0) {
return (
<div>
<div className="notification">
@@ -128,7 +128,7 @@ const html = `
);
}
var issues = this.props.data.issues
var issues = this.props.data.Issues
.filter(function(issue) {
return this.props.severity.includes(issue.severity);
}.bind(this))
@@ -151,7 +151,7 @@ const html = `
<div>
<div className="notification">
No issues matched given filters
(of total { this.props.data.issues.length } issues).
(of total { this.props.data.Issues.length } issues).
</div>
<Stats data={ this.props.data } />
</div>
@@ -182,31 +182,32 @@ const html = `
var highDisabled = !this.props.available.includes("HIGH");
var mediumDisabled = !this.props.available.includes("MEDIUM");
var lowDisabled = !this.props.available.includes("LOW");
var on = "", off = "disabled";
var HIGH = "HIGH", MEDIUM = "MEDIUM", LOW = "LOW";
return (
<span>
<label className={"label checkbox " + (highDisabled ? "disabled" : "") }>
<label className="label checkbox { (highDisabled ? off : on )}">
<input
type="checkbox"
checked={ this.props.selected.includes("HIGH") }
checked={ this.props.selected.includes(HIGH) }
disabled={ highDisabled }
onChange={ this.handleChange("HIGH") }/>
onChange={ this.handleChange(HIGH) }/>
High
</label>
<label className={"label checkbox " + (mediumDisabled ? "disabled" : "") }>
<label className="label checkbox {( mediumDisabled ? off : on )}">
<input
type="checkbox"
checked={ this.props.selected.includes("MEDIUM") }
checked={ this.props.selected.includes(MEDIUM) }
disabled={ mediumDisabled }
onChange={ this.handleChange("MEDIUM") }/>
onChange={ this.handleChange(MEDIUM) }/>
Medium
</label>
<label className={"label checkbox " + (lowDisabled ? "disabled" : "") }>
<label className="label checkbox {( lowDisabled ? off : on )}">
<input
type="checkbox"
checked={ this.props.selected.includes("LOW") }
checked={ this.props.selected.includes(LOW) }
disabled={ lowDisabled }
onChange={ this.handleChange("LOW") }/>
onChange={ this.handleChange(LOW) }/>
Low
</label>
</span>
@@ -231,13 +232,13 @@ const html = `
render: function() {
var issueTypes = this.props.allIssueTypes
.map(function(it) {
var matches = this.props.issueType == it
return (
<option value={ it } selected={ this.props.issueType == it }>
<option value={ it } selected={ matches }>
{ it }
</option>
);
}.bind(this));
return (
<nav className="panel">
<div className="panel-heading">
@@ -282,7 +283,6 @@ const html = `
);
}
});
var IssueBrowser = React.createClass({
getInitialState: function() {
return {};
@@ -291,11 +291,11 @@ const html = `
this.updateIssues(this.props.data);
},
handleSeverity: function(val) {
this.updateIssueTypes(this.props.data.issues, val, this.state.confidence);
this.updateIssueTypes(this.props.data.Issues, val, this.state.confidence);
this.setState({severity: val});
},
handleConfidence: function(val) {
this.updateIssueTypes(this.props.data.issues, this.state.severity, val);
this.updateIssueTypes(this.props.data.Issues, this.state.severity, val);
this.setState({confidence: val});
},
handleIssueType: function(val) {
@@ -306,8 +306,7 @@ const html = `
this.setState({data: data});
return;
}
var allSeverities = data.issues
var allSeverities = data.Issues
.map(function(issue) {
return issue.severity
})
@@ -315,8 +314,7 @@ const html = `
.filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
var allConfidences = data.issues
var allConfidences = data.Issues
.map(function(issue) {
return issue.confidence
})
@@ -324,12 +322,9 @@ const html = `
.filter(function(item, pos, ary) {
return !pos || item != ary[pos - 1];
});
var selectedSeverities = allSeverities;
var selectedConfidences = allConfidences;
this.updateIssueTypes(data.issues, selectedSeverities, selectedConfidences);
this.updateIssueTypes(data.Issues, selectedSeverities, selectedConfidences);
this.setState({
data: data,
severity: selectedSeverities,
@@ -358,7 +353,7 @@ const html = `
if (this.state.issueType && !allTypes.includes(this.state.issueType)) {
this.setState({issueType: null});
}
this.setState({allIssueTypes: allTypes});
},
render: function() {
@@ -391,7 +386,7 @@ const html = `
);
}
});
ReactDOM.render(
<IssueBrowser data={ data } />,
document.getElementById("content")

View File

@@ -56,7 +56,7 @@ func resolveCallExpr(n *ast.CallExpr, c *Context) bool {
// TryResolve will attempt, given a subtree starting at some ATS node, to resolve
// all values contained within to a known constant. It is used to check for any
// unkown values in compound expressions.
// unknown values in compound expressions.
func TryResolve(n ast.Node, c *Context) bool {
switch node := n.(type) {
case *ast.BasicLit:

View File

@@ -27,7 +27,7 @@ type Rule interface {
type RuleBuilder func(id string, c Config) (Rule, []ast.Node)
// A RuleSet maps lists of rules to the type of AST node they should be run on.
// The anaylzer will only invoke rules contained in the list associated with the
// The analyzer will only invoke rules contained in the list associated with the
// type of AST node it is currently visiting.
type RuleSet map[reflect.Type][]Rule

View File

@@ -19,7 +19,7 @@ func (a *archive) ID() string {
// Match inspects AST nodes to determine if the filepath.Joins uses any argument derived from type zip.File
func (a *archive) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
if node := a.calls.ContainsCallExpr(n, c); node != nil {
if node := a.calls.ContainsCallExpr(n, c, false); node != nil {
for _, arg := range node.Args {
var argType types.Type
if selector, ok := arg.(*ast.SelectorExpr); ok {

View File

@@ -33,7 +33,7 @@ func (r *bindsToAllNetworkInterfaces) ID() string {
}
func (r *bindsToAllNetworkInterfaces) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
callExpr := r.calls.ContainsCallExpr(n, c)
callExpr := r.calls.ContainsCallExpr(n, c, false)
if callExpr == nil {
return nil, nil
}

View File

@@ -53,7 +53,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, erro
switch stmt := n.(type) {
case *ast.AssignStmt:
for _, expr := range stmt.Rhs {
if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx) == nil {
if callExpr, ok := expr.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(expr, ctx, false) == nil {
pos := returnsError(callExpr, ctx)
if pos < 0 || pos >= len(stmt.Lhs) {
return nil, nil
@@ -64,7 +64,7 @@ func (r *noErrorCheck) Match(n ast.Node, ctx *gosec.Context) (*gosec.Issue, erro
}
}
case *ast.ExprStmt:
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx) == nil {
if callExpr, ok := stmt.X.(*ast.CallExpr); ok && r.whitelist.ContainsCallExpr(stmt.X, ctx, false) == nil {
pos := returnsError(callExpr, ctx)
if pos >= 0 {
return gosec.NewIssue(ctx, n, r.ID(), r.What, r.Severity, r.Confidence), nil
@@ -81,6 +81,7 @@ func NewNoErrorCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
whitelist := gosec.NewCallList()
whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString")
whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln")
whitelist.AddAll("strings.Builder", "Write", "WriteByte", "WriteRune", "WriteString")
whitelist.Add("io.PipeWriter", "CloseWithError")
if configured, ok := conf["G104"]; ok {

View File

@@ -24,6 +24,7 @@ import (
type readfile struct {
gosec.MetaData
gosec.CallList
pathJoin gosec.CallList
}
// ID returns the identifier for this rule
@@ -31,10 +32,49 @@ func (r *readfile) ID() string {
return r.MetaData.ID
}
// isJoinFunc checks if there is a filepath.Join or other join function
func (r *readfile) isJoinFunc(n ast.Node, c *gosec.Context) bool {
if call := r.pathJoin.ContainsCallExpr(n, c, false); call != nil {
for _, arg := range call.Args {
// edge case: check if one of the args is a BinaryExpr
if binExp, ok := arg.(*ast.BinaryExpr); ok {
// iterate and resolve all found identities from the BinaryExpr
if _, ok := gosec.FindVarIdentities(binExp, c); ok {
return true
}
}
// try and resolve identity
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) {
return true
}
}
}
}
return false
}
// Match inspects AST nodes to determine if the match the methods `os.Open` or `ioutil.ReadFile`
func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil {
if node := r.ContainsCallExpr(n, c, false); node != nil {
for _, arg := range node.Args {
// handles path joining functions in Arg
// eg. os.Open(filepath.Join("/tmp/", file))
if callExpr, ok := arg.(*ast.CallExpr); ok {
if r.isJoinFunc(callExpr, c) {
return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
// handles binary string concatenation eg. ioutil.Readfile("/tmp/" + file + "/blob")
if binExp, ok := arg.(*ast.BinaryExpr); ok {
// resolve all found identities from the BinaryExpr
if _, ok := gosec.FindVarIdentities(binExp, c); ok {
return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) {
@@ -49,6 +89,7 @@ func (r *readfile) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
// NewReadFile detects cases where we read files
func NewReadFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
rule := &readfile{
pathJoin: gosec.NewCallList(),
CallList: gosec.NewCallList(),
MetaData: gosec.MetaData{
ID: id,
@@ -57,6 +98,8 @@ func NewReadFile(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
Confidence: gosec.High,
},
}
rule.pathJoin.Add("path/filepath", "Join")
rule.pathJoin.Add("path", "Join")
rule.Add("io/ioutil", "ReadFile")
rule.Add("os", "Open")
return rule, []ast.Node{(*ast.CallExpr)(nil)}

View File

@@ -32,7 +32,7 @@ func (w *weakKeyStrength) ID() string {
}
func (w *weakKeyStrength) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
if callExpr := w.calls.ContainsCallExpr(n, c); callExpr != nil {
if callExpr := w.calls.ContainsCallExpr(n, c, false); callExpr != nil {
if bits, err := gosec.GetInt(callExpr.Args[1]); err == nil && bits < (int64)(w.bits) {
return gosec.NewIssue(c, n, w.ID(), w.What, w.Severity, w.Confidence), nil
}

View File

@@ -65,6 +65,7 @@ func Generate(filters ...RuleFilter) RuleList {
{"G104", "Audit errors not checked", NewNoErrorCheck},
{"G105", "Audit the use of big.Exp function", NewUsingBigExp},
{"G106", "Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
{"G107", "Url provided to HTTP request as taint input", NewSSRFCheck},
// injection
{"G201", "SQL query construction using format string", NewSQLStrFormat},
@@ -74,7 +75,7 @@ func Generate(filters ...RuleFilter) RuleList {
// filesystem
{"G301", "Poor file permissions used when creating a directory", NewMkdirPerms},
{"G302", "Poor file permisions used when creation file or using chmod", NewFilePerms},
{"G302", "Poor file permissions used when creation file or using chmod", NewFilePerms},
{"G303", "Creating tempfile using a predictable path", NewBadTempFile},
{"G304", "File path provided as taint input", NewReadFile},
{"G305", "File path traversal when extracting zip archive", NewArchive},

View File

@@ -32,7 +32,9 @@ var _ = Describe("gosec rules", func() {
analyzer.Reset()
pkg := testutils.NewTestPackage()
defer pkg.Close()
pkg.AddFile(fmt.Sprintf("sample_%d.go", n), sample.Code)
for i, code := range sample.Code {
pkg.AddFile(fmt.Sprintf("sample_%d_%d.go", n, i), code)
}
err := pkg.Build()
Expect(err).ShouldNot(HaveOccurred())
err = analyzer.Process(buildTags, pkg.Path)
@@ -71,6 +73,10 @@ var _ = Describe("gosec rules", func() {
runner("G106", testutils.SampleCodeG106)
})
It("should detect ssrf via http requests with variable url", func() {
runner("G107", testutils.SampleCodeG107)
})
It("should detect sql injection via format strings", func() {
runner("G201", testutils.SampleCodeG201)
})

View File

@@ -51,10 +51,17 @@ func (s *sqlStrConcat) ID() string {
}
// see if we can figure out what it is
func (s *sqlStrConcat) checkObject(n *ast.Ident) bool {
func (s *sqlStrConcat) checkObject(n *ast.Ident, c *gosec.Context) bool {
if n.Obj != nil {
return n.Obj.Kind != ast.Var && n.Obj.Kind != ast.Fun
}
// Try to resolve unresolved identifiers using other files in same package
for _, file := range c.PkgFiles {
if node, ok := file.Scope.Objects[n.String()]; ok {
return node.Kind != ast.Var && node.Kind != ast.Fun
}
}
return false
}
@@ -69,7 +76,7 @@ func (s *sqlStrConcat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error)
if _, ok := node.Y.(*ast.BasicLit); ok {
return nil, nil // string cat OK
}
if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second) {
if second, ok := node.Y.(*ast.Ident); ok && s.checkObject(second, c) {
return nil, nil
}
return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil
@@ -98,8 +105,9 @@ func NewSQLStrConcat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
type sqlStrFormat struct {
sqlStatement
calls gosec.CallList
noIssue gosec.CallList
calls gosec.CallList
noIssue gosec.CallList
noIssueQuoted gosec.CallList
}
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
@@ -109,7 +117,7 @@ func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error)
argIndex := 0
// TODO(gm) improve confidence if database/sql is being used
if node := s.calls.ContainsCallExpr(n, c); node != nil {
if node := s.calls.ContainsCallExpr(n, c, false); node != 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" {
@@ -125,17 +133,40 @@ func (s *sqlStrFormat) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error)
argIndex = 1
}
}
// no formatter
if len(node.Args) == 0 {
return nil, nil
}
var formatter string
// 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
}
formatter = fullStr
}
} else if arg, e := gosec.GetString(node.Args[argIndex]); e == nil {
formatter = arg
}
if len(formatter) <= 0 {
return nil, nil
}
if arg, e := gosec.GetString(node.Args[argIndex]); s.MatchPatterns(arg) && e == nil {
// If all formatter args are quoted, then the SQL construction is safe
if argIndex+1 < len(node.Args) {
allQuoted := true
for _, arg := range node.Args[argIndex+1:] {
if n := s.noIssueQuoted.ContainsCallExpr(arg, c, true); n == nil {
allQuoted = false
break
}
}
if allQuoted {
return nil, nil
}
}
if s.MatchPatterns(formatter) {
return gosec.NewIssue(c, n, s.ID(), s.What, s.Severity, s.Confidence), nil
}
}
@@ -145,8 +176,9 @@ 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(),
noIssue: gosec.NewCallList(),
calls: gosec.NewCallList(),
noIssue: gosec.NewCallList(),
noIssueQuoted: gosec.NewCallList(),
sqlStatement: sqlStatement{
patterns: []*regexp.Regexp{
regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
@@ -162,5 +194,6 @@ func NewSQLStrFormat(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
}
rule.calls.AddAll("fmt", "Sprint", "Sprintf", "Sprintln", "Fprintf")
rule.noIssue.AddAll("os", "Stdout", "Stderr")
rule.noIssueQuoted.Add("github.com/lib/pq", "QuoteIdentifier")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

59
rules/ssrf.go Normal file
View File

@@ -0,0 +1,59 @@
package rules
import (
"go/ast"
"go/types"
"github.com/securego/gosec"
)
type ssrf struct {
gosec.MetaData
gosec.CallList
}
// ID returns the identifier for this rule
func (r *ssrf) ID() string {
return r.MetaData.ID
}
// ResolveVar tries to resolve the first argument of a call expression
// The first argument is the url
func (r *ssrf) ResolveVar(n *ast.CallExpr, c *gosec.Context) bool {
if len(n.Args) > 0 {
arg := n.Args[0]
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)
if _, ok := obj.(*types.Var); ok && !gosec.TryResolve(ident, c) {
return true
}
}
}
return false
}
// Match inspects AST nodes to determine if certain net/http methods are called with variable input
func (r *ssrf) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
// Call expression is using http package directly
if node := r.ContainsCallExpr(n, c, false); node != nil {
if r.ResolveVar(node, c) {
return gosec.NewIssue(c, n, r.ID(), r.What, r.Severity, r.Confidence), nil
}
}
return nil, nil
}
// NewSSRFCheck detects cases where HTTP requests are sent
func NewSSRFCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
rule := &ssrf{
CallList: gosec.NewCallList(),
MetaData: gosec.MetaData{
ID: id,
What: "Potential HTTP request made with variable url",
Severity: gosec.Medium,
Confidence: gosec.Medium,
},
}
rule.AddAll("net/http", "Do", "Get", "Head", "Post", "PostForm", "RoundTrip")
return rule, []ast.Node{(*ast.CallExpr)(nil)}
}

View File

@@ -40,7 +40,7 @@ func (r *subprocess) ID() string {
//
// syscall.Exec("echo", "foobar" + tainted)
func (r *subprocess) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
if node := r.ContainsCallExpr(n, c); node != nil {
if node := r.ContainsCallExpr(n, c, false); node != nil {
for _, arg := range node.Args {
if ident, ok := arg.(*ast.Ident); ok {
obj := c.Info.ObjectOf(ident)

View File

@@ -32,7 +32,7 @@ func (t *badTempFile) ID() string {
}
func (t *badTempFile) Match(n ast.Node, c *gosec.Context) (gi *gosec.Issue, err error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
if node := t.calls.ContainsCallExpr(n, c, false); node != nil {
if arg, e := gosec.GetString(node.Args[0]); t.args.MatchString(arg) && e == nil {
return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil
}

View File

@@ -30,7 +30,7 @@ func (t *templateCheck) ID() string {
}
func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error) {
if node := t.calls.ContainsCallExpr(n, c); node != nil {
if node := t.calls.ContainsCallExpr(n, c, false); node != nil {
for _, arg := range node.Args {
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
return gosec.NewIssue(c, n, t.ID(), t.What, t.Severity, t.Confidence), nil
@@ -41,7 +41,7 @@ func (t *templateCheck) Match(n ast.Node, c *gosec.Context) (*gosec.Issue, error
}
// NewTemplateCheck constructs the template check rule. This rule is used to
// find use of tempaltes where HTML/JS escaping is not being used
// find use of templates where HTML/JS escaping is not being used
func NewTemplateCheck(id string, conf gosec.Config) (gosec.Rule, []ast.Node) {
calls := gosec.NewCallList()

View File

@@ -29,7 +29,7 @@ type TestPackage struct {
}
// NewTestPackage will create a new and empty package. Must call Close() to cleanup
// auxilary files
// auxiliary files
func NewTestPackage() *TestPackage {
// Files must exist in $GOPATH
sourceDir := path.Join(os.Getenv("GOPATH"), "src")

View File

@@ -2,20 +2,20 @@ package testutils
// CodeSample encapsulates a snippet of source code that compiles, and how many errors should be detected
type CodeSample struct {
Code string
Code []string
Errors int
}
var (
// SampleCodeG101 code snippets for hardcoded credentials
SampleCodeG101 = []CodeSample{{`
SampleCodeG101 = []CodeSample{{[]string{`
package main
import "fmt"
func main() {
username := "admin"
password := "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
fmt.Println("Doing something with: ", username, password)
}`, 1}, {`
}`}, 1}, {[]string{`
// Entropy check should not report this error by default
package main
import "fmt"
@@ -23,21 +23,21 @@ func main() {
username := "admin"
password := "secret"
fmt.Println("Doing something with: ", username, password)
}`, 0}, {`
}`}, 0}, {[]string{`
package main
import "fmt"
var password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
func main() {
username := "admin"
fmt.Println("Doing something with: ", username, password)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import "fmt"
const password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
func main() {
username := "admin"
fmt.Println("Doing something with: ", username, password)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import "fmt"
const (
@@ -46,12 +46,12 @@ const (
)
func main() {
fmt.Println("Doing something with: ", username, password)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
var password string
func init() {
password = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
}`, 1}, {`
}`}, 1}, {[]string{`
package main
const (
ATNStateSomethingElse = 1
@@ -59,19 +59,19 @@ const (
)
func main() {
println(ATNStateTokenStart)
}`, 0}, {`
}`}, 0}, {[]string{`
package main
const (
ATNStateTokenStart = "f62e5bcda4fae4f82370da0c6f20697b8f8447ef"
)
func main() {
println(ATNStateTokenStart)
}`, 1}}
}`}, 1}}
// SampleCodeG102 code snippets for network binding
SampleCodeG102 = []CodeSample{
// Bind to all networks explicitly
{`
{[]string{`
package main
import (
"log"
@@ -83,10 +83,10 @@ func main() {
log.Fatal(err)
}
defer l.Close()
}`, 1},
}`}, 1},
// Bind to all networks implicitly (default if host omitted)
{`
{[]string{`
package main
import (
"log"
@@ -98,11 +98,11 @@ func main() {
log.Fatal(err)
}
defer l.Close()
}`, 1},
}`}, 1},
}
// SampleCodeG103 find instances of unsafe blocks for auditing purposes
SampleCodeG103 = []CodeSample{
{`
{[]string{`
package main
import (
"fmt"
@@ -120,11 +120,11 @@ func main() {
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}}
}`}, 3}}
// SampleCodeG104 finds errors that aren't being handled
SampleCodeG104 = []CodeSample{
{`
{[]string{`
package main
import "fmt"
func test() (int,error) {
@@ -133,7 +133,7 @@ func test() (int,error) {
func main() {
v, _ := test()
fmt.Println(v)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
"io/ioutil"
@@ -155,7 +155,7 @@ func main() {
a()
b()
c()
}`, 3}, {`
}`}, 3}, {[]string{`
package main
import "fmt"
func test() error {
@@ -164,10 +164,24 @@ func test() error {
func main() {
e := test()
fmt.Println(e)
}`, 0}}
}`}, 0}, {[]string{`
// +build go1.10
package main
import "strings"
func main() {
var buf strings.Builder
_, err := buf.WriteString("test string")
if err != nil {
panic(err)
}
}`, `
package main
func dummy(){}
`}, 0}}
// SampleCodeG105 - bignum overflow
SampleCodeG105 = []CodeSample{{`
SampleCodeG105 = []CodeSample{{[]string{`
package main
import (
"math/big"
@@ -181,27 +195,63 @@ func main() {
m := new(big.Int)
m = m.SetUint64(0)
z = z.Exp(x, y, m)
}`, 1}}
}`}, 1}}
// SampleCodeG106 - ssh InsecureIgnoreHostKey
SampleCodeG106 = []CodeSample{{`
SampleCodeG106 = []CodeSample{{[]string{`
package main
import (
"golang.org/x/crypto/ssh"
)
func main() {
_ = ssh.InsecureIgnoreHostKey()
}`, 1}}
}`}, 1}}
// SampleCodeG107 - SSRF via http requests with variable url
SampleCodeG107 = []CodeSample{{[]string{`
package main
import (
"net/http"
"io/ioutil"
"fmt"
"os"
)
func main() {
url := os.Getenv("tainted_url")
resp, err := http.Get(url)
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
panic(err)
}
fmt.Printf("%s", body)
}`}, 1}, {[]string{`
package main
import (
"fmt"
"net/http"
)
const url = "http://127.0.0.1"
func main() {
resp, err := http.Get(url)
if err != nil {
fmt.Println(err)
}
fmt.Println(resp.Status)
}`}, 0}}
// SampleCodeG201 - SQL injection via format string
SampleCodeG201 = []CodeSample{
{`
{[]string{`
// Format string without proper quoting
package main
import (
"database/sql"
"fmt"
"os"
//_ "github.com/mattn/go-sqlite3"
)
func main(){
@@ -215,14 +265,13 @@ func main(){
panic(err)
}
defer rows.Close()
}`, 1}, {`
}`}, 1}, {[]string{`
// Format string false positive, safe string spec.
package main
import (
"database/sql"
"fmt"
"os"
//_ "github.com/mattn/go-sqlite3"
)
func main(){
@@ -236,34 +285,60 @@ func main(){
panic(err)
}
defer rows.Close()
}`, 0}, {
`
}`}, 0}, {[]string{`
// Format string false positive
package main
import (
"database/sql"
//_ "github.com/mattn/go-sqlite3"
)
var staticQuery = "SELECT * FROM foo WHERE age < 32"
const staticQuery = "SELECT * FROM foo WHERE age < 32"
func main(){
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
panic(err)
panic(err)
}
rows, err := db.Query(staticQuery)
if err != nil {
panic(err)
panic(err)
}
defer rows.Close()
}`, 0}}
// SampleCodeG202 - SQL query string building via string concatenation
SampleCodeG202 = []CodeSample{
{`
}`}, 0}, {[]string{`
// Format string false positive, quoted formatter argument.
package main
import (
"database/sql"
"fmt"
"os"
"github.com/lib/pq"
)
func main(){
db, err := sql.Open("postgres", "localhost")
if err != nil {
panic(err)
}
q := fmt.Sprintf("SELECT * FROM %s where id = 1", pq.QuoteIdentifier(os.Args[1]))
rows, err := db.Query(q)
if err != nil {
panic(err)
}
defer rows.Close()
}`}, 0}, {[]string{`
package main
import (
"fmt"
)
func main(){
fmt.Sprintln()
}`}, 0}}
// SampleCodeG202 - SQL query string building via string concatenation
SampleCodeG202 = []CodeSample{
{[]string{`
package main
import (
"database/sql"
//_ "github.com/mattn/go-sqlite3"
"os"
)
func main(){
@@ -276,29 +351,27 @@ func main(){
panic(err)
}
defer rows.Close()
}`, 1}, {`
}`}, 1}, {[]string{`
// false positive
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 {
if err != nil {
panic(err)
}
rows, err := db.Query(staticQuery + "32")
if err != nil {
panic(err)
}
defer rows.Close()
}`, 0}, {`
defer rows.Close()
}`}, 0}, {[]string{`
package main
import (
"database/sql"
//_ "github.com/mattn/go-sqlite3"
)
const age = "32"
var staticQuery = "SELECT * FROM foo WHERE age < "
@@ -313,11 +386,32 @@ func main(){
}
defer rows.Close()
}
`, 0}}
`}, 0}, {[]string{`
package main
const gender = "M"
`, `
package main
import (
"database/sql"
)
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("SELECT * FROM foo WHERE gender = " + gender)
if err != nil {
panic(err)
}
defer rows.Close()
}
`}, 0}}
// SampleCodeG203 - Template checks
SampleCodeG203 = []CodeSample{
{`
{[]string{`
// We assume that hardcoded template strings are safe as the programmer would
// need to be explicitly shooting themselves in the foot (as below)
package main
@@ -333,7 +427,7 @@ func main() {
"Body": template.HTML("<script>alert(1)</script>"),
}
t.Execute(os.Stdout, v)
}`, 0}, {
}`}, 0}, {[]string{
`
// Using a variable to initialize could potentially be dangerous. Under the
// current model this will likely produce some false positives.
@@ -351,7 +445,7 @@ func main() {
"Body": template.HTML(a),
}
t.Execute(os.Stdout, v)
}`, 1}, {
}`}, 1}, {[]string{
`
package main
import (
@@ -367,7 +461,7 @@ func main() {
"Body": template.JS(a),
}
t.Execute(os.Stdout, v)
}`, 1}, {
}`}, 1}, {[]string{
`
package main
import (
@@ -383,15 +477,15 @@ func main() {
"Body": template.URL(a),
}
t.Execute(os.Stdout, v)
}`, 1}}
}`}, 1}}
// SampleCodeG204 - Subprocess auditing
SampleCodeG204 = []CodeSample{{`
SampleCodeG204 = []CodeSample{{[]string{`
package main
import "syscall"
func main() {
syscall.Exec("/bin/cat", []string{ "/etc/passwd" }, nil)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
"log"
@@ -406,7 +500,7 @@ func main() {
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
"log"
@@ -419,7 +513,7 @@ func main() {
log.Fatal(err)
}
log.Printf("Command finished with error: %v", err)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
"log"
@@ -436,20 +530,20 @@ func main() {
log.Printf("Waiting for command to finish...")
err = cmd.Wait()
log.Printf("Command finished with error: %v", err)
}`, 1}}
}`}, 1}}
// SampleCodeG301 - mkdir permission check
SampleCodeG301 = []CodeSample{{`
SampleCodeG301 = []CodeSample{{[]string{`
package main
import "os"
func main() {
os.Mkdir("/tmp/mydir", 0777)
os.Mkdir("/tmp/mydir", 0600)
os.MkdirAll("/tmp/mydir/mysubidr", 0775)
}`, 2}}
}`}, 2}}
// SampleCodeG302 - file create / chmod permissions check
SampleCodeG302 = []CodeSample{{`
SampleCodeG302 = []CodeSample{{[]string{`
package main
import "os"
func main() {
@@ -457,10 +551,10 @@ func main() {
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)
}`, 2}}
}`}, 2}}
// SampleCodeG303 - bad tempfile permissions & hardcoded shared path
SampleCodeG303 = []CodeSample{{`
SampleCodeG303 = []CodeSample{{[]string{`
package samples
import (
"io/ioutil"
@@ -470,10 +564,10 @@ func main() {
file1, _ := os.Create("/tmp/demo1")
defer file1.Close()
ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0644)
}`, 2}}
}`}, 2}}
// SampleCodeG304 - potential file inclusion vulnerability
SampleCodeG304 = []CodeSample{{`
SampleCodeG304 = []CodeSample{{[]string{`
package main
import (
"os"
@@ -488,7 +582,7 @@ if err != nil {
}
log.Print(body)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
@@ -500,7 +594,7 @@ import (
func main() {
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
title := r.URL.Query().Get("title")
title := r.URL.Query().Get("title")
f, err := os.Open(title)
if err != nil {
fmt.Printf("Error: %v\n", err)
@@ -512,10 +606,69 @@ func main() {
fmt.Fprintf(w, "%s", body)
})
log.Fatal(http.ListenAndServe(":3000", nil))
}`, 1}}
}`}, 1}, {[]string{`
package main
import (
"log"
"os"
"io/ioutil"
)
func main() {
f2 := os.Getenv("tainted_file2")
body, err := ioutil.ReadFile("/tmp/" + f2)
if err != nil {
log.Printf("Error: %v\n", err)
}
log.Print(body)
}`}, 1}, {[]string{`
package main
import (
"bufio"
"fmt"
"os"
"path/filepath"
)
func main() {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Please enter file to read: ")
file, _ := reader.ReadString('\n')
file = file[:len(file)-1]
f, err := os.Open(filepath.Join("/tmp/service/", file))
if err != nil {
fmt.Printf("Error: %v\n", err)
}
contents := make([]byte, 15)
if _, err = f.Read(contents); err != nil {
fmt.Printf("Error: %v\n", err)
}
fmt.Println(string(contents))
}`}, 1}, {[]string{`
package main
import (
"log"
"os"
"io/ioutil"
"path/filepath"
)
func main() {
dir := os.Getenv("server_root")
f3 := os.Getenv("tainted_file3")
// edge case where both a binary expression and file Join are used.
body, err := ioutil.ReadFile(filepath.Join("/var/"+dir, f3))
if err != nil {
log.Printf("Error: %v\n", err)
}
log.Print(body)
}`}, 1}}
// SampleCodeG305 - File path traversal when extracting zip archives
SampleCodeG305 = []CodeSample{{`
SampleCodeG305 = []CodeSample{{[]string{`
package unzip
import (
@@ -560,7 +713,7 @@ func unzip(archive, target string) error {
}
return nil
}`, 1}, {`
}`}, 1}, {[]string{`
package unzip
import (
@@ -606,11 +759,11 @@ func unzip(archive, target string) error {
}
return nil
}`, 1}}
}`}, 1}}
// SampleCodeG401 - Use of weak crypto MD5
SampleCodeG401 = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/md5"
@@ -631,11 +784,11 @@ func main() {
log.Fatal(err)
}
fmt.Printf("%x", h.Sum(nil))
}`, 1}}
}`}, 1}}
// SampleCodeG401b - Use of weak crypto SHA1
SampleCodeG401b = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/sha1"
@@ -656,10 +809,10 @@ func main() {
log.Fatal(err)
}
fmt.Printf("%x", h.Sum(nil))
}`, 1}}
}`}, 1}}
// SampleCodeG402 - TLS settings
SampleCodeG402 = []CodeSample{{`
SampleCodeG402 = []CodeSample{{[]string{`
// InsecureSkipVerify
package main
import (
@@ -677,7 +830,7 @@ func main() {
if err != nil {
fmt.Println(err)
}
}`, 1}, {
}`}, 1}, {[]string{
`
// Insecure minimum version
package main
@@ -695,7 +848,7 @@ func main() {
if err != nil {
fmt.Println(err)
}
}`, 1}, {`
}`}, 1}, {[]string{`
// Insecure max version
package main
import (
@@ -713,8 +866,8 @@ func main() {
fmt.Println(err)
}
}
`, 1}, {
`
`}, 1}, {
[]string{`
// Insecure ciphersuite selection
package main
import (
@@ -734,11 +887,11 @@ func main() {
if err != nil {
fmt.Println(err)
}
}`, 1}}
}`}, 1}}
// SampleCodeG403 - weak key strength
SampleCodeG403 = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/rand"
@@ -752,23 +905,23 @@ func main() {
fmt.Println(err)
}
fmt.Println(pvk)
}`, 1}}
}`}, 1}}
// SampleCodeG404 - weak random number
SampleCodeG404 = []CodeSample{
{`
{[]string{`
package main
import "crypto/rand"
func main() {
good, _ := rand.Read(nil)
println(good)
}`, 0}, {`
}`}, 0}, {[]string{`
package main
import "math/rand"
func main() {
bad := rand.Int()
println(bad)
}`, 1}, {`
}`}, 1}, {[]string{`
package main
import (
"crypto/rand"
@@ -779,11 +932,11 @@ func main() {
println(good)
i := mrand.Int31()
println(i)
}`, 0}}
}`}, 0}}
// SampleCodeG501 - Blacklisted import MD5
SampleCodeG501 = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/md5"
@@ -794,11 +947,11 @@ func main() {
for _, arg := range os.Args {
fmt.Printf("%x - %s\n", md5.Sum([]byte(arg)), arg)
}
}`, 1}}
}`}, 1}}
// SampleCodeG502 - Blacklisted import DES
SampleCodeG502 = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/cipher"
@@ -822,10 +975,10 @@ func main() {
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[des.BlockSize:], plaintext)
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
}`, 1}}
}`}, 1}}
// SampleCodeG503 - Blacklisted import RC4
SampleCodeG503 = []CodeSample{{`
SampleCodeG503 = []CodeSample{{[]string{`
package main
import (
"crypto/rc4"
@@ -841,10 +994,10 @@ func main() {
ciphertext := make([]byte, len(plaintext))
cipher.XORKeyStream(ciphertext, plaintext)
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
}`, 1}}
}`}, 1}}
// SampleCodeG504 - Blacklisted import CGI
SampleCodeG504 = []CodeSample{{`
SampleCodeG504 = []CodeSample{{[]string{`
package main
import (
"net/http/cgi"
@@ -852,10 +1005,10 @@ import (
)
func main() {
cgi.Serve(http.FileServer(http.Dir("/usr/share/doc")))
}`, 1}}
}`}, 1}}
// SampleCodeG505 - Blacklisted import SHA1
SampleCodeG505 = []CodeSample{
{`
{[]string{`
package main
import (
"crypto/sha1"
@@ -866,13 +1019,13 @@ func main() {
for _, arg := range os.Args {
fmt.Printf("%x - %s\n", sha1.Sum([]byte(arg)), arg)
}
}`, 1}}
}`}, 1}}
// SampleCode601 - Go build tags
SampleCode601 = []CodeSample{{`
SampleCode601 = []CodeSample{{[]string{`
// +build test
package main
func main() {
fmt.Println("no package imported error")
}`, 1}}
}`}, 1}}
)