mirror of
https://github.com/securego/gosec.git
synced 2026-01-15 09:53:40 +08:00
Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2695567487 | ||
|
|
ae82798b9c | ||
|
|
adb42220da | ||
|
|
9b966a447e | ||
|
|
41809946d4 | ||
|
|
443f84fd4d | ||
|
|
3116b07de4 | ||
|
|
e0a150bfa3 | ||
|
|
97bc137c5b | ||
|
|
8c09a83248 | ||
|
|
d032909e3f | ||
|
|
027dc2b8a7 | ||
|
|
f9b41874b1 | ||
|
|
1ecd47e007 | ||
|
|
2cc6838ca3 | ||
|
|
64d58c2e51 | ||
|
|
d3f1980e7a | ||
|
|
5f98926a7b | ||
|
|
7f6509a916 | ||
|
|
762ff3a709 | ||
|
|
ec32ce68d8 | ||
|
|
145f1a0bf4 | ||
|
|
419c9292c8 | ||
|
|
63b25c147f | ||
|
|
7fd94463ed |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,8 +1,12 @@
|
||||
# transient files
|
||||
/image
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
*.swp
|
||||
/gosec
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
---
|
||||
project_name: gosec
|
||||
|
||||
release:
|
||||
github:
|
||||
owner: securego
|
||||
name: gosec
|
||||
|
||||
builds:
|
||||
- main : ./cmd/gosec/
|
||||
binary: gosec
|
||||
|
||||
@@ -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
|
||||
|
||||
19
Dockerfile
19
Dockerfile
@@ -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"]
|
||||
|
||||
2
Makefile
2
Makefile
@@ -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
146
README.md
@@ -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
|
||||
|
||||
[](https://travis-ci.org/securego/gosec)
|
||||
[](https://godoc.org/github.com/securego/gosec)
|
||||
[](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 ./...
|
||||
```
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
22
call_list.go
22
call_list.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
${BIN} "$@"
|
||||
22
go.mod
Normal file
22
go.mod
Normal 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
39
go.sum
Normal 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=
|
||||
36
helpers.go
36
helpers.go
@@ -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
381
install.sh
Normal 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
|
||||
4
issue.go
4
issue.go
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
2
rule.go
2
rule.go
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
57
rules/sql.go
57
rules/sql.go
@@ -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
59
rules/ssrf.go
Normal 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)}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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}}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user