Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Jon McClintock 2018-03-02 19:10:42 +00:00
commit 3713168816
22 changed files with 645 additions and 142 deletions

View file

@ -1,14 +1,25 @@
language: go language: go
before_script:
- go vet $(go list ./... | grep -v /vendor/)
go: go:
- 1.5 - 1.7
- 1.8
- 1.9
- tip - tip
install: install:
- go get -u github.com/golang/lint/golint
- go get -v github.com/onsi/ginkgo/ginkgo - go get -v github.com/onsi/ginkgo/ginkgo
- go get -v github.com/onsi/gomega - go get -v github.com/onsi/gomega
- go get -v golang.org/x/crypto/ssh
- go get github.com/GoASTScanner/gas/cmd/gas/...
- go get -v -t ./... - go get -v -t ./...
- export PATH=$PATH:$HOME/gopath/bin - export PATH=$PATH:$HOME/gopath/bin
before_script:
- test -z "$(gofmt -s -l -w $(find . -type f -name '*.go' -not -path './vendor/*') | tee /dev/stderr)"
- test -z "$(golint . | tee /dev/stderr)"
- go vet $(go list ./... | grep -v /vendor/)
- gas ./...
script: ginkgo -r script: ginkgo -r

19
Godeps/Godeps.json generated
View file

@ -1,8 +1,20 @@
{ {
"ImportPath": "github.com/GoASTScanner/gas", "ImportPath": "github.com/GoASTScanner/gas",
"GoVersion": "go1.9", "GoVersion": "go1.9",
"GodepVersion": "v79", "GodepVersion": "v80",
"Packages": [
"./..."
],
"Deps": [ "Deps": [
{
"ImportPath": "github.com/kisielk/gotool",
"Rev": "0de1eaf82fa3f583ce21fde859f1e7e0c5e9b220"
},
{
"ImportPath": "github.com/mozilla/tls-observatory/constants",
"Comment": "1.2.32-17-g17e0ce4b",
"Rev": "17e0ce4bfc46eae3d57acf13a2d7c7517655d493"
},
{ {
"ImportPath": "github.com/nbutton23/zxcvbn-go", "ImportPath": "github.com/nbutton23/zxcvbn-go",
"Rev": "a22cb81b2ecdde8b68e9ffb8824731cbf88e1de4" "Rev": "a22cb81b2ecdde8b68e9ffb8824731cbf88e1de4"
@ -189,6 +201,11 @@
"Comment": "v1.2.0-2-gdcabb60", "Comment": "v1.2.0-2-gdcabb60",
"Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02" "Rev": "dcabb60a477c2b6f456df65037cb6708210fbb02"
}, },
{
"ImportPath": "github.com/ryanuber/go-glob",
"Comment": "v0.1-4-g256dc44",
"Rev": "256dc444b735e061061cf46c809487313d5b0065"
},
{ {
"ImportPath": "golang.org/x/net/html", "ImportPath": "golang.org/x/net/html",
"Rev": "8351a756f30f1297fe94bbf4b767ec589c6ea6d0" "Rev": "8351a756f30f1297fe94bbf4b767ec589c6ea6d0"

View file

@ -41,6 +41,7 @@ or to specify a set of rules to explicitly exclude using the '-exclude=' flag.
- G103: Audit the use of unsafe block - G103: Audit the use of unsafe block
- G104: Audit errors not checked - G104: Audit errors not checked
- G105: Audit the use of math/big.Int.Exp - G105: Audit the use of math/big.Int.Exp
- G106: Audit the use of ssh.InsecureIgnoreHostKey
- G201: SQL query construction using format string - G201: SQL query construction using format string
- G202: SQL query construction using string concatenation - G202: SQL query construction using string concatenation
- G203: Use of unescaped data in HTML templates - G203: Use of unescaped data in HTML templates
@ -104,7 +105,7 @@ $ gas -nosec=true ./...
### Output formats ### Output formats
Gas currently supports text, json and csv output formats. By default Gas currently supports text, json, csv and JUnit XML output formats. By default
results will be reported to stdout, but can also be written to an output 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: file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows:
@ -113,3 +114,21 @@ file. The output format is controlled by the '-fmt' flag, and the output file is
$ gas -fmt=json -out=results.json *.go $ gas -fmt=json -out=results.json *.go
``` ```
### 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:
```
go get github.com/GoASTScanner/gas/cmd/tlsconfig/...
```
You can invoke now the `go generate` in the root of the project:
```
go generate ./...
```
This will generate the `rules/tls_config.go` file with will contain the current ciphers recommendation from Mozilla.

View file

@ -102,7 +102,10 @@ func (gas *Analyzer) Process(packagePaths ...string) error {
AllowErrors: true, AllowErrors: true,
} }
for _, packagePath := range packagePaths { for _, packagePath := range packagePaths {
abspath, _ := filepath.Abs(packagePath) abspath, err := filepath.Abs(packagePath)
if err != nil {
return err
}
gas.logger.Println("Searching directory:", abspath) gas.logger.Println("Searching directory:", abspath)
basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment) basePackage, err := build.Default.ImportDir(packagePath, build.ImportComment)

View file

@ -59,7 +59,7 @@ var (
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
// format output // format output
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, html, or text") flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv, junit-xml, html, or text")
// output file // output file
flagOutput = flag.String("out", "", "Set output file for results") flagOutput = flag.String("out", "", "Set output file for results")
@ -79,6 +79,9 @@ var (
// log to file or stderr // log to file or stderr
flagLogfile = flag.String("log", "", "Log messages to file rather than stderr") flagLogfile = flag.String("log", "", "Log messages to file rather than stderr")
// sort the issues by severity
flagSortIssues = flag.Bool("sort", true, "Sort issues by severity")
logger *log.Logger logger *log.Logger
) )
@ -149,9 +152,15 @@ func saveOutput(filename, format string, issues []*gas.Issue, metrics *gas.Metri
return err return err
} }
defer outfile.Close() defer outfile.Close()
output.CreateReport(outfile, format, issues, metrics) err = output.CreateReport(outfile, format, issues, metrics)
if err != nil {
return err
}
} else { } else {
output.CreateReport(os.Stdout, format, issues, metrics) err := output.CreateReport(os.Stdout, format, issues, metrics)
if err != nil {
return err
}
} }
return nil return nil
} }
@ -166,7 +175,7 @@ func main() {
// Ensure at least one file was specified // Ensure at least one file was specified
if flag.NArg() == 0 { if flag.NArg() == 0 {
fmt.Fprintf(os.Stderr, "\nError: FILE [FILE...] or './...' expected\n") fmt.Fprintf(os.Stderr, "\nError: FILE [FILE...] or './...' expected\n") // #nosec
flag.Usage() flag.Usage()
os.Exit(1) os.Exit(1)
} }
@ -225,13 +234,18 @@ func main() {
os.Exit(0) os.Exit(0)
} }
// Sort the issue by severity
if *flagSortIssues {
sortIssues(issues)
}
// Create output report // Create output report
if err := saveOutput(*flagOutput, *flagFormat, issues, metrics); err != nil { if err := saveOutput(*flagOutput, *flagFormat, issues, metrics); err != nil {
logger.Fatal(err) logger.Fatal(err)
} }
// Finialize logging // Finialize logging
logWriter.Close() logWriter.Close() // #nosec
// Do we have an issue? If so exit 1 // Do we have an issue? If so exit 1
if issuesFound { if issuesFound {

20
cmd/gas/sort_issues.go Normal file
View file

@ -0,0 +1,20 @@
package main
import (
"sort"
"github.com/GoASTScanner/gas"
)
type sortBySeverity []*gas.Issue
func (s sortBySeverity) Len() int { return len(s) }
func (s sortBySeverity) Less(i, j int) bool { return s[i].Severity > s[i].Severity }
func (s sortBySeverity) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// sortIssues sorts the issues by severity in descending order
func sortIssues(issues []*gas.Issue) {
sort.Sort(sortBySeverity(issues))
}

View file

@ -0,0 +1,13 @@
package main
import "text/template"
var generatedHeaderTmpl = template.Must(template.New("generated").Parse(`
package {{.}}
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
`))

View file

@ -0,0 +1,19 @@
package main
import "text/template"
var generatedRuleTmpl = template.Must(template.New("generated").Parse(`
// New{{.Name}}TLSCheck creates a check for {{.Name}} TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func New{{.Name}}TLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: {{ .MinVersion }},
MaxVersion: {{ .MaxVersion }},
goodCiphers: []string{
{{range $cipherName := .Ciphers }} "{{$cipherName}}",
{{end}}
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
`))

204
cmd/tlsconfig/tlsconfig.go Normal file
View file

@ -0,0 +1,204 @@
package main
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"flag"
"fmt"
"go/format"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"sort"
"strings"
"github.com/mozilla/tls-observatory/constants"
)
var (
pkg = flag.String("pkg", "rules", "package name to be added to the output file")
outputFile = flag.String("outputFile", "tls_config.go", "name of the output file")
)
// TLSConfURL url where Mozilla publishes the TLS ciphers recommendations
const TLSConfURL = "https://statics.tls.security.mozilla.org/server-side-tls-conf.json"
// ServerSideTLSJson contains all the available configurations and the version of the current document.
type ServerSideTLSJson struct {
Configurations map[string]Configuration `json:"configurations"`
Version float64 `json:"version"`
}
// Configuration represents configurations levels declared by the Mozilla server-side-tls
// see https://wiki.mozilla.org/Security/Server_Side_TLS
type Configuration struct {
OpenSSLCiphersuites string `json:"openssl_ciphersuites"`
Ciphersuites []string `json:"ciphersuites"`
TLSVersions []string `json:"tls_versions"`
TLSCurves []string `json:"tls_curves"`
CertificateTypes []string `json:"certificate_types"`
CertificateCurves []string `json:"certificate_curves"`
CertificateSignatures []string `json:"certificate_signatures"`
RsaKeySize float64 `json:"rsa_key_size"`
DHParamSize float64 `json:"dh_param_size"`
ECDHParamSize float64 `json:"ecdh_param_size"`
HstsMinAge float64 `json:"hsts_min_age"`
OldestClients []string `json:"oldest_clients"`
}
type goCipherConfiguration struct {
Name string
Ciphers []string
MinVersion string
MaxVersion string
}
type goTLSConfiguration struct {
cipherConfigs []goCipherConfiguration
}
// getTLSConfFromURL retrieves the json containing the TLS configurations from the specified URL.
func getTLSConfFromURL(url string) (*ServerSideTLSJson, error) {
r, err := http.Get(url)
if err != nil {
return nil, err
}
defer r.Body.Close()
var sstls ServerSideTLSJson
err = json.NewDecoder(r.Body).Decode(&sstls)
if err != nil {
return nil, err
}
return &sstls, nil
}
func getGoCipherConfig(name string, sstls ServerSideTLSJson) (goCipherConfiguration, error) {
cipherConf := goCipherConfiguration{Name: strings.Title(name)}
conf, ok := sstls.Configurations[name]
if !ok {
return cipherConf, fmt.Errorf("TLS configuration '%s' not found", name)
}
for _, cipherName := range conf.Ciphersuites {
cipherSuite, ok := constants.CipherSuites[cipherName]
if !ok {
log.Printf("Warning: cannot map cipher '%s'\n", cipherName)
}
if len(cipherSuite.IANAName) > 0 {
cipherConf.Ciphers = append(cipherConf.Ciphers, cipherSuite.IANAName)
}
}
versions := mapTLSVersions(conf.TLSVersions)
if len(versions) > 0 {
cipherConf.MinVersion = fmt.Sprintf("0x%04x", versions[0])
cipherConf.MaxVersion = fmt.Sprintf("0x%04x", versions[len(versions)-1])
} else {
return cipherConf, fmt.Errorf("No TLS versions found for configuration '%s'", name)
}
return cipherConf, nil
}
func mapTLSVersions(tlsVersions []string) []int {
var versions []int
for _, tlsVersion := range tlsVersions {
switch tlsVersion {
case "TLSv1.2":
versions = append(versions, tls.VersionTLS12)
case "TLSv1.1":
versions = append(versions, tls.VersionTLS11)
case "TLSv1":
versions = append(versions, tls.VersionTLS10)
case "SSLv3":
versions = append(versions, tls.VersionSSL30)
default:
continue
}
}
sort.Ints(versions)
return versions
}
func getGoTLSConf() (goTLSConfiguration, error) {
sstls, err := getTLSConfFromURL(TLSConfURL)
if err != nil || sstls == nil {
msg := fmt.Sprintf("Could not load the Server Side TLS configuration from Mozilla's website. Check the URL: %s. Error: %v\n",
TLSConfURL, err)
panic(msg)
}
tlsConfg := goTLSConfiguration{}
modern, err := getGoCipherConfig("modern", *sstls)
if err != nil {
return tlsConfg, err
}
tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, modern)
intermediate, err := getGoCipherConfig("intermediate", *sstls)
if err != nil {
return tlsConfg, err
}
tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, intermediate)
old, err := getGoCipherConfig("old", *sstls)
if err != nil {
return tlsConfg, err
}
tlsConfg.cipherConfigs = append(tlsConfg.cipherConfigs, old)
return tlsConfg, nil
}
func getCurrentDir() (string, error) {
dir := "."
if args := flag.Args(); len(args) == 1 {
dir = args[0]
} else if len(args) > 1 {
return "", errors.New("only one directory at a time")
}
dir, err := filepath.Abs(dir)
if err != nil {
return "", err
}
return dir, nil
}
func main() {
dir, err := getCurrentDir()
if err != nil {
log.Fatalln(err)
}
tlsConfig, err := getGoTLSConf()
if err != nil {
log.Fatalln(err)
}
var buf bytes.Buffer
err = generatedHeaderTmpl.Execute(&buf, *pkg)
if err != nil {
log.Fatalf("Failed to generate the header: %v", err)
}
for _, cipherConfig := range tlsConfig.cipherConfigs {
err := generatedRuleTmpl.Execute(&buf, cipherConfig)
if err != nil {
log.Fatalf("Failed to generated the cipher config: %v", err)
}
}
src, err := format.Source(buf.Bytes())
if err != nil {
log.Printf("warnings: Failed to format the code: %v", err)
src = buf.Bytes()
}
outputPath := filepath.Join(dir, *outputFile)
if err := ioutil.WriteFile(outputPath, src, 0644); err != nil {
log.Fatalf("Writing output: %s", err)
}
}

View file

@ -1,30 +0,0 @@
package gas_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("ImportTracker", func() {
var (
source string
)
BeforeEach(func() {
source = `// TODO(gm)`
})
Context("when I have a valid go package", func() {
It("should record all import specs", func() {
Expect(source).To(Equal(source))
Skip("Not implemented")
})
It("should correctly track aliased package imports", func() {
Skip("Not implemented")
})
It("should correctly track init only packages", func() {
Skip("Not implemented")
})
})
})

View file

@ -76,7 +76,7 @@ func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, err
} }
size := (int)(end - start) // Go bug, os.File.Read should return int64 ... size := (int)(end - start) // Go bug, os.File.Read should return int64 ...
file.Seek(start, 0) file.Seek(start, 0) // #nosec
buf := make([]byte, size) buf := make([]byte, size)
if nread, err := file.Read(buf); err != nil || nread != size { if nread, err := file.Read(buf); err != nil || nread != size {

View file

@ -17,6 +17,7 @@ package output
import ( import (
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"encoding/xml"
htmlTemplate "html/template" htmlTemplate "html/template"
"io" "io"
plainTemplate "text/template" plainTemplate "text/template"
@ -36,6 +37,9 @@ const (
// ReportCSV set the output format to csv // ReportCSV set the output format to csv
ReportCSV // CSV format ReportCSV // CSV format
// ReportJUnitXML set the output format to junit xml
ReportJUnitXML // JUnit XML format
) )
var text = `Results: var text = `Results:
@ -70,6 +74,8 @@ func CreateReport(w io.Writer, format string, issues []*gas.Issue, metrics *gas.
err = reportJSON(w, data) err = reportJSON(w, data)
case "csv": case "csv":
err = reportCSV(w, data) err = reportCSV(w, data)
case "junit-xml":
err = reportJUnitXML(w, data)
case "html": case "html":
err = reportFromHTMLTemplate(w, html, data) err = reportFromHTMLTemplate(w, html, data)
case "text": case "text":
@ -112,6 +118,25 @@ func reportCSV(w io.Writer, data *reportInfo) error {
return nil return nil
} }
func reportJUnitXML(w io.Writer, data *reportInfo) error {
groupedData := groupDataByRules(data)
junitXMLStruct := createJUnitXMLStruct(groupedData)
raw, err := xml.MarshalIndent(junitXMLStruct, "", "\t")
if err != nil {
return err
}
xmlHeader := []byte("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n")
raw = append(xmlHeader, raw...)
_, err = w.Write(raw)
if err != nil {
return err
}
return nil
}
func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error { func reportFromPlaintextTemplate(w io.Writer, reportTemplate string, data *reportInfo) error {
t, e := plainTemplate.New("gas").Parse(reportTemplate) t, e := plainTemplate.New("gas").Parse(reportTemplate)
if e != nil { if e != nil {

View file

@ -0,0 +1,74 @@
package output
import (
"encoding/xml"
htmlLib "html"
"strconv"
"github.com/GoASTScanner/gas"
)
type junitXMLReport struct {
XMLName xml.Name `xml:"testsuites"`
Testsuites []testsuite `xml:"testsuite"`
}
type testsuite struct {
XMLName xml.Name `xml:"testsuite"`
Name string `xml:"name,attr"`
Tests int `xml:"tests,attr"`
Testcases []testcase `xml:"testcase"`
}
type testcase struct {
XMLName xml.Name `xml:"testcase"`
Name string `xml:"name,attr"`
Failure failure `xml:"failure"`
}
type failure struct {
XMLName xml.Name `xml:"failure"`
Message string `xml:"message,attr"`
Text string `xml:",innerxml"`
}
func generatePlaintext(issue *gas.Issue) string {
return "Results:\n" +
"[" + issue.File + ":" + issue.Line + "] - " +
issue.What + " (Confidence: " + strconv.Itoa(int(issue.Confidence)) +
", Severity: " + strconv.Itoa(int(issue.Severity)) + ")\n" + "> " + htmlLib.EscapeString(issue.Code)
}
func groupDataByRules(data *reportInfo) map[string][]*gas.Issue {
groupedData := make(map[string][]*gas.Issue)
for _, issue := range data.Issues {
if _, ok := groupedData[issue.What]; ok {
groupedData[issue.What] = append(groupedData[issue.What], issue)
} else {
groupedData[issue.What] = []*gas.Issue{issue}
}
}
return groupedData
}
func createJUnitXMLStruct(groupedData map[string][]*gas.Issue) junitXMLReport {
var xmlReport junitXMLReport
for what, issues := range groupedData {
testsuite := testsuite{
Name: what,
Tests: len(issues),
}
for _, issue := range issues {
testcase := testcase{
Name: issue.File,
Failure: failure{
Message: "Found 1 vulnerability. See stacktrace for details.",
Text: generatePlaintext(issue),
},
}
testsuite.Testcases = append(testsuite.Testcases, testcase)
}
xmlReport.Testsuites = append(xmlReport.Testsuites, testsuite)
}
return xmlReport
}

View file

@ -77,7 +77,7 @@ func NewNoErrorCheck(conf gas.Config) (gas.Rule, []ast.Node) {
// black list instead. // black list instead.
whitelist := gas.NewCallList() whitelist := gas.NewCallList()
whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString") whitelist.AddAll("bytes.Buffer", "Write", "WriteByte", "WriteRune", "WriteString")
whitelist.AddAll("fmt", "Print", "Printf", "Println") whitelist.AddAll("fmt", "Print", "Printf", "Println", "Fprint", "Fprintf", "Fprintln")
whitelist.Add("io.PipeWriter", "CloseWithError") whitelist.Add("io.PipeWriter", "CloseWithError")
if configured, ok := conf["G104"]; ok { if configured, ok := conf["G104"]; ok {

View file

@ -75,7 +75,7 @@ func NewFilePerms(conf gas.Config) (gas.Rule, []ast.Node) {
// NewMkdirPerms creates a rule to detect directory creation with more permissive than // NewMkdirPerms creates a rule to detect directory creation with more permissive than
// configured permission mask. // configured permission mask.
func NewMkdirPerms(conf gas.Config) (gas.Rule, []ast.Node) { func NewMkdirPerms(conf gas.Config) (gas.Rule, []ast.Node) {
mode := getConfiguredMode(conf, "G301", 0700) mode := getConfiguredMode(conf, "G301", 0750)
return &filePermissions{ return &filePermissions{
mode: mode, mode: mode,
pkg: "os", pkg: "os",

View file

@ -60,34 +60,35 @@ func NewRuleFilter(action bool, ruleIDs ...string) RuleFilter {
func Generate(filters ...RuleFilter) RuleList { func Generate(filters ...RuleFilter) RuleList {
rules := map[string]RuleDefinition{ rules := map[string]RuleDefinition{
// misc // misc
"G101": RuleDefinition{"Look for hardcoded credentials", NewHardcodedCredentials}, "G101": {"Look for hardcoded credentials", NewHardcodedCredentials},
"G102": RuleDefinition{"Bind to all interfaces", NewBindsToAllNetworkInterfaces}, "G102": {"Bind to all interfaces", NewBindsToAllNetworkInterfaces},
"G103": RuleDefinition{"Audit the use of unsafe block", NewUsingUnsafe}, "G103": {"Audit the use of unsafe block", NewUsingUnsafe},
"G104": RuleDefinition{"Audit errors not checked", NewNoErrorCheck}, "G104": {"Audit errors not checked", NewNoErrorCheck},
"G105": RuleDefinition{"Audit the use of big.Exp function", NewUsingBigExp}, "G105": {"Audit the use of big.Exp function", NewUsingBigExp},
"G106": {"Audit the use of ssh.InsecureIgnoreHostKey function", NewSSHHostKey},
// injection // injection
"G201": RuleDefinition{"SQL query construction using format string", NewSQLStrFormat}, "G201": {"SQL query construction using format string", NewSQLStrFormat},
"G202": RuleDefinition{"SQL query construction using string concatenation", NewSQLStrConcat}, "G202": {"SQL query construction using string concatenation", NewSQLStrConcat},
"G203": RuleDefinition{"Use of unescaped data in HTML templates", NewTemplateCheck}, "G203": {"Use of unescaped data in HTML templates", NewTemplateCheck},
"G204": RuleDefinition{"Audit use of command execution", NewSubproc}, "G204": {"Audit use of command execution", NewSubproc},
// filesystem // filesystem
"G301": RuleDefinition{"Poor file permissions used when creating a directory", NewMkdirPerms}, "G301": {"Poor file permissions used when creating a directory", NewMkdirPerms},
"G302": RuleDefinition{"Poor file permisions used when creation file or using chmod", NewFilePerms}, "G302": {"Poor file permisions used when creation file or using chmod", NewFilePerms},
"G303": RuleDefinition{"Creating tempfile using a predictable path", NewBadTempFile}, "G303": {"Creating tempfile using a predictable path", NewBadTempFile},
// crypto // crypto
"G401": RuleDefinition{"Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography}, "G401": {"Detect the usage of DES, RC4, or MD5", NewUsesWeakCryptography},
"G402": RuleDefinition{"Look for bad TLS connection settings", NewIntermediateTLSCheck}, "G402": {"Look for bad TLS connection settings", NewIntermediateTLSCheck},
"G403": RuleDefinition{"Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength}, "G403": {"Ensure minimum RSA key length of 2048 bits", NewWeakKeyStrength},
"G404": RuleDefinition{"Insecure random number source (rand)", NewWeakRandCheck}, "G404": {"Insecure random number source (rand)", NewWeakRandCheck},
// blacklist // blacklist
"G501": RuleDefinition{"Import blacklist: crypto/md5", NewBlacklistedImportMD5}, "G501": {"Import blacklist: crypto/md5", NewBlacklistedImportMD5},
"G502": RuleDefinition{"Import blacklist: crypto/des", NewBlacklistedImportDES}, "G502": {"Import blacklist: crypto/des", NewBlacklistedImportDES},
"G503": RuleDefinition{"Import blacklist: crypto/rc4", NewBlacklistedImportRC4}, "G503": {"Import blacklist: crypto/rc4", NewBlacklistedImportRC4},
"G504": RuleDefinition{"Import blacklist: net/http/cgi", NewBlacklistedImportCGI}, "G504": {"Import blacklist: net/http/cgi", NewBlacklistedImportCGI},
} }
for rule := range rules { for rule := range rules {

View file

@ -65,6 +65,10 @@ var _ = Describe("gas rules", func() {
runner("G105", testutils.SampleCodeG105) runner("G105", testutils.SampleCodeG105)
}) })
It("should detect of ssh.InsecureIgnoreHostKey function", func() {
runner("G106", testutils.SampleCodeG106)
})
It("should detect sql injection via format strings", func() { It("should detect sql injection via format strings", func() {
runner("G201", testutils.SampleCodeG201) runner("G201", testutils.SampleCodeG201)
}) })

33
rules/ssh.go Normal file
View file

@ -0,0 +1,33 @@
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
type sshHostKey struct {
gas.MetaData
pkg string
calls []string
}
func (r *sshHostKey) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
if _, matches := gas.MatchCallByPackage(n, c, r.pkg, r.calls...); matches {
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
}
return nil, nil
}
// NewSSHHostKey rule detects the use of insecure ssh HostKeyCallback.
func NewSSHHostKey(conf gas.Config) (gas.Rule, []ast.Node) {
return &sshHostKey{
pkg: "golang.org/x/crypto/ssh",
calls: []string{"InsecureIgnoreHostKey"},
MetaData: gas.MetaData{
What: "Use of ssh InsecureIgnoreHostKey should be audited",
Severity: gas.Medium,
Confidence: gas.High,
},
}, []ast.Node{(*ast.CallExpr)(nil)}
}

View file

@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
//go:generate tlsconfig
package rules package rules
import ( import (
@ -106,86 +108,18 @@ func (t *insecureConfigTLS) processTLSConfVal(n *ast.KeyValueExpr, c *gas.Contex
} }
func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) { func (t *insecureConfigTLS) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil && c.Info.TypeOf(complit.Type).String() == t.requiredType { if complit, ok := n.(*ast.CompositeLit); ok && complit.Type != nil {
for _, elt := range complit.Elts { actualType := c.Info.TypeOf(complit.Type)
if kve, ok := elt.(*ast.KeyValueExpr); ok { if actualType != nil && actualType.String() == t.requiredType {
issue := t.processTLSConfVal(kve, c) for _, elt := range complit.Elts {
if issue != nil { if kve, ok := elt.(*ast.KeyValueExpr); ok {
return issue, nil issue := t.processTLSConfVal(kve, c)
if issue != nil {
return issue, nil
}
} }
} }
} }
} }
return nil, nil return nil, nil
} }
// NewModernTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0303, // TLS 1.2 only
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewIntermediateTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewCompatTLSCheck see: https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29
func NewCompatTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0301, // TLS 1.2, 1.1, 1.0
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_RSA_WITH_RC4_128_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_RC4_128_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}

132
rules/tls_config.go Normal file
View file

@ -0,0 +1,132 @@
package rules
import (
"go/ast"
"github.com/GoASTScanner/gas"
)
// NewModernTLSCheck creates a check for Modern TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewModernTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0303,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewIntermediateTLSCheck creates a check for Intermediate TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewIntermediateTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0301,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}
// NewOldTLSCheck creates a check for Old TLS ciphers
// DO NOT EDIT - generated by tlsconfig tool
func NewOldTLSCheck(conf gas.Config) (gas.Rule, []ast.Node) {
return &insecureConfigTLS{
requiredType: "crypto/tls.Config",
MinVersion: 0x0300,
MaxVersion: 0x0303,
goodCiphers: []string{
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_128_GCM_SHA256",
"TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
"TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_RSA_WITH_AES_128_GCM_SHA256",
"TLS_RSA_WITH_AES_256_GCM_SHA384",
"TLS_RSA_WITH_AES_128_CBC_SHA256",
"TLS_RSA_WITH_AES_256_CBC_SHA256",
"TLS_RSA_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
"TLS_RSA_WITH_3DES_EDE_CBC_SHA",
"TLS_ECDHE_RSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_256_CBC_SHA384",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_ECDHE_ECDSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256",
"TLS_RSA_WITH_CAMELLIA_128_CBC_SHA",
"TLS_DHE_RSA_WITH_SEED_CBC_SHA",
"TLS_DHE_DSS_WITH_SEED_CBC_SHA",
"TLS_RSA_WITH_SEED_CBC_SHA",
},
}, []ast.Node{(*ast.CompositeLit)(nil)}
}

View file

@ -128,6 +128,7 @@ func (p *TestPackage) CreateContext(filename string) *gas.Context {
// Close will delete the package and all files in that directory // Close will delete the package and all files in that directory
func (p *TestPackage) Close() { func (p *TestPackage) Close() {
if p.ondisk { if p.ondisk {
os.RemoveAll(p.Path) err := os.RemoveAll(p.Path)
log.Println(err)
} }
} }

View file

@ -183,6 +183,15 @@ func main() {
z = z.Exp(x, y, m) z = z.Exp(x, y, m)
}`, 1}} }`, 1}}
// SampleCodeG106 - ssh InsecureIgnoreHostKey
SampleCodeG106 = []CodeSample{{`
package main
import (
"golang.org/x/crypto/ssh"
)
func main() {
_ = ssh.InsecureIgnoreHostKey()
}`, 1}}
// SampleCodeG201 - SQL injection via format string // SampleCodeG201 - SQL injection via format string
SampleCodeG201 = []CodeSample{ SampleCodeG201 = []CodeSample{
{` {`