mirror of
https://github.com/securego/gosec.git
synced 2024-11-05 11:35:51 +00:00
Initial public release
This commit is contained in:
commit
4f3d620d37
37 changed files with 3357 additions and 0 deletions
26
.gitignore
vendored
Normal file
26
.gitignore
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
*.so
|
||||
|
||||
# Folders
|
||||
_obj
|
||||
_test
|
||||
|
||||
# Architecture specific extensions/prefixes
|
||||
*.[568vq]
|
||||
[568vq].out
|
||||
|
||||
*.cgo1.go
|
||||
*.cgo2.c
|
||||
_cgo_defun.c
|
||||
_cgo_gotypes.go
|
||||
_cgo_export.*
|
||||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
*.test
|
||||
*.prof
|
||||
|
||||
.DS_Store
|
154
LICENSE.txt
Normal file
154
LICENSE.txt
Normal file
|
@ -0,0 +1,154 @@
|
|||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and
|
||||
distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
||||
owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities
|
||||
that control, are controlled by, or are under common control with that entity.
|
||||
For the purposes of this definition, "control" means (i) the power, direct or
|
||||
indirect, to cause the direction or management of such entity, whether by
|
||||
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
||||
permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including
|
||||
but not limited to software source code, documentation source, and configuration
|
||||
files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or
|
||||
translation of a Source form, including but not limited to compiled object code,
|
||||
generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made
|
||||
available under the License, as indicated by a copyright notice that is included
|
||||
in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
||||
is based on (or derived from) the Work and for which the editorial revisions,
|
||||
annotations, elaborations, or other modifications represent, as a whole, an
|
||||
original work of authorship. For the purposes of this License, Derivative Works
|
||||
shall not include works that remain separable from, or merely link (or bind by
|
||||
name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version
|
||||
of the Work and any modifications or additions to that Work or Derivative Works
|
||||
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
||||
by the copyright owner or by an individual or Legal Entity authorized to submit
|
||||
on behalf of the copyright owner. For the purposes of this definition,
|
||||
"submitted" means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems, and
|
||||
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
||||
the purpose of discussing and improving the Work, but excluding communication
|
||||
that is conspicuously marked or otherwise designated in writing by the copyright
|
||||
owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
||||
of whom a Contribution has been received by Licensor and subsequently
|
||||
incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this
|
||||
License, each Contributor hereby grants to You a perpetual, worldwide,
|
||||
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
||||
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
||||
sublicense, and distribute the Work and such Derivative Works in Source or
|
||||
Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License,
|
||||
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section) patent
|
||||
license to make, have made, use, offer to sell, sell, import, and otherwise
|
||||
transfer the Work, where such license applies only to those patent claims
|
||||
licensable by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s) with the Work
|
||||
to which such Contribution(s) was submitted. If You institute patent litigation
|
||||
against any entity (including a cross-claim or counterclaim in a lawsuit)
|
||||
alleging that the Work or a Contribution incorporated within the Work
|
||||
constitutes direct or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate as of the date
|
||||
such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or
|
||||
Derivative Works thereof in any medium, with or without modifications, and in
|
||||
Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of
|
||||
this License; and You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and You must retain, in the Source form of
|
||||
any Derivative Works that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work, excluding those notices
|
||||
that do not pertain to any part of the Derivative Works; and If the Work
|
||||
includes a "NOTICE" text file as part of its distribution, then any Derivative
|
||||
Works that You distribute must include a readable copy of the attribution
|
||||
notices contained within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one of the following
|
||||
places: within a NOTICE text file distributed as part of the Derivative Works;
|
||||
within the Source form or documentation, if provided along with the Derivative
|
||||
Works; or, within a display generated by the Derivative Works, if and wherever
|
||||
such third-party notices normally appear. The contents of the NOTICE file are
|
||||
for informational purposes only and do not modify the License. You may add Your
|
||||
own attribution notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided that such
|
||||
additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide
|
||||
additional or different license terms and conditions for use, reproduction, or
|
||||
distribution of Your modifications, or for any such Derivative Works as a whole,
|
||||
provided Your use, reproduction, and distribution of the Work otherwise complies
|
||||
with the conditions stated in this License. 5. Submission of Contributions.
|
||||
Unless You explicitly state otherwise, any Contribution intentionally submitted
|
||||
for inclusion in the Work by You to the Licensor shall be under the terms and
|
||||
conditions of this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify the terms of
|
||||
any separate license agreement you may have executed with Licensor regarding
|
||||
such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names,
|
||||
trademarks, service marks, or product names of the Licensor, except as required
|
||||
for reasonable and customary use in describing the origin of the Work and
|
||||
reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
||||
writing, Licensor provides the Work (and each Contributor provides its
|
||||
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied, including, without limitation, any warranties
|
||||
or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any risks
|
||||
associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in
|
||||
tort (including negligence), contract, or otherwise, unless required by
|
||||
applicable law (such as deliberate and grossly negligent acts) or agreed to in
|
||||
writing, shall any Contributor be liable to You for damages, including any
|
||||
direct, indirect, special, incidental, or consequential damages of any character
|
||||
arising as a result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill, work stoppage,
|
||||
computer failure or malfunction, or any and all other commercial damages or
|
||||
losses), even if such Contributor has been advised of the possibility of such
|
||||
damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or
|
||||
Derivative Works thereof, You may choose to offer, and charge a fee for,
|
||||
acceptance of support, warranty, indemnity, or other liability obligations
|
||||
and/or rights consistent with this License. However, in accepting such
|
||||
obligations, You may act only on Your own behalf and on Your sole
|
||||
responsibility, not on behalf of any other Contributor, and only if You agree to
|
||||
indemnify, defend, and hold each Contributor harmless for any liability incurred
|
||||
by, or claims asserted against, such Contributor by reason of your accepting any
|
||||
such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
84
README.md
Normal file
84
README.md
Normal file
|
@ -0,0 +1,84 @@
|
|||
## GAS - Go AST Scanner
|
||||
|
||||
Inspects source code for security problems by scanning the Go AST.
|
||||
|
||||
### Usage
|
||||
|
||||
Gas 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
|
||||
|
||||
By default Gas will run all rules against the supplied file paths. It is however possible to select a subset of rules to run via the '-rule=' flag.
|
||||
|
||||
##### Available rules
|
||||
|
||||
- __crypto__ - Detects use of weak cryptography primatives
|
||||
- __tls__ - Detects if TLS certificate verification is disabled
|
||||
- __sql__ - SQL injection vectors
|
||||
- __hardcoded__ - Potential hardcoded credentials
|
||||
- __perms__ - Insecure file permissions
|
||||
- __tempfile__ - Insecure creation of temporary files
|
||||
- __unsafe__- Detects use of the unsafe pointer functions
|
||||
- __bind__- Listening on all network interfaces
|
||||
- __rsa__- Weak RSA keys
|
||||
|
||||
|
||||
```
|
||||
$ gas -rule=rsa -rule=tls -rule=crypto ./...
|
||||
```
|
||||
|
||||
#### Excluding files:
|
||||
|
||||
Gas will ignore paths that match a supplied pattern via
|
||||
[filepath.Match](https://golang.org/pkg/path/filepath/#Match).
|
||||
Multiple patterns can be specified as follows:
|
||||
|
||||
```
|
||||
$ gas -exclude tests* -exclude *_example.go ./...
|
||||
```
|
||||
|
||||
#### Annotating code
|
||||
|
||||
In cases where Gas reports a failure that has been verified as being safe.
|
||||
In these cases it is possible to annotate the code with a '#nosec' comment.
|
||||
The annotation causes Gas to stop processing any further nodes within the
|
||||
AST so can apply to a whole block or more granularly to a single expression.
|
||||
|
||||
```go
|
||||
|
||||
import "md5" // #nosec
|
||||
|
||||
|
||||
func main(){
|
||||
|
||||
/* # nosec */
|
||||
if x > y {
|
||||
h := md5.New() // this will also be ignored
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
$ gas -nosec=true ./...
|
||||
```
|
||||
|
||||
### Output formats
|
||||
|
||||
Gas currently supports text, json and csv output formats. By default 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:
|
||||
|
||||
```
|
||||
# Write output in json format to results.json
|
||||
$ gas -fmt=json -out=results.json *.go
|
||||
```
|
152
core/analyzer.go
Normal file
152
core/analyzer.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Context struct {
|
||||
FileSet *token.FileSet
|
||||
Comments ast.CommentMap
|
||||
Info *types.Info
|
||||
Pkg *types.Package
|
||||
}
|
||||
|
||||
type Rule interface {
|
||||
Match(ast.Node, *Context) (*Issue, error)
|
||||
}
|
||||
|
||||
type RuleSet map[reflect.Type][]Rule
|
||||
|
||||
type Metrics struct {
|
||||
NumFiles int
|
||||
NumLines int
|
||||
NumNosec int
|
||||
NumFound int
|
||||
}
|
||||
|
||||
type Analyzer struct {
|
||||
annotations bool
|
||||
ruleset RuleSet
|
||||
context Context
|
||||
logger *log.Logger
|
||||
Issues []Issue
|
||||
Stats Metrics
|
||||
}
|
||||
|
||||
func NewAnalyzer(annotations bool, logger *log.Logger) Analyzer {
|
||||
if logger == nil {
|
||||
logger = log.New(os.Stdout, "[gas]", 0)
|
||||
}
|
||||
return Analyzer{
|
||||
annotations: annotations,
|
||||
ruleset: make(RuleSet),
|
||||
Issues: make([]Issue, 0),
|
||||
context: Context{token.NewFileSet(), nil, nil, nil},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (gas *Analyzer) process(filename string, source interface{}) error {
|
||||
mode := parser.ParseComments
|
||||
root, err := parser.ParseFile(gas.context.FileSet, filename, source, mode)
|
||||
if err == nil {
|
||||
gas.context.Comments = ast.NewCommentMap(gas.context.FileSet, root, root.Comments)
|
||||
|
||||
// here we get type info
|
||||
gas.context.Info = &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
}
|
||||
|
||||
conf := types.Config{Importer: importer.Default()}
|
||||
gas.context.Pkg, _ = conf.Check("pkg", gas.context.FileSet, []*ast.File{root}, gas.context.Info)
|
||||
// TODO: we need to look at the err ret
|
||||
|
||||
ast.Walk(gas, root)
|
||||
gas.Stats.NumFiles++
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (gas *Analyzer) AddRule(r Rule, n ast.Node) {
|
||||
t := reflect.TypeOf(n)
|
||||
if val, ok := gas.ruleset[t]; ok {
|
||||
gas.ruleset[t] = append(val, r)
|
||||
} else {
|
||||
gas.ruleset[t] = []Rule{r}
|
||||
}
|
||||
}
|
||||
|
||||
func (gas *Analyzer) Process(filename string) error {
|
||||
err := gas.process(filename, nil)
|
||||
fun := func(f *token.File) bool {
|
||||
gas.Stats.NumLines += f.LineCount()
|
||||
return true
|
||||
}
|
||||
gas.context.FileSet.Iterate(fun)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gas *Analyzer) ProcessSource(filename string, source string) error {
|
||||
err := gas.process(filename, source)
|
||||
fun := func(f *token.File) bool {
|
||||
gas.Stats.NumLines += f.LineCount()
|
||||
return true
|
||||
}
|
||||
gas.context.FileSet.Iterate(fun)
|
||||
return err
|
||||
}
|
||||
|
||||
func (gas *Analyzer) Ignore(n ast.Node) bool {
|
||||
if groups, ok := gas.context.Comments[n]; ok {
|
||||
for _, group := range groups {
|
||||
if strings.Contains(group.Text(), "nosec") {
|
||||
gas.Stats.NumNosec++
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (gas *Analyzer) Visit(n ast.Node) ast.Visitor {
|
||||
if !gas.annotations || gas.Ignore(n) {
|
||||
if val, ok := gas.ruleset[reflect.TypeOf(n)]; ok {
|
||||
for _, rule := range val {
|
||||
ret, err := rule.Match(n, &gas.context)
|
||||
if err != nil {
|
||||
// will want to give more info than this ...
|
||||
gas.logger.Println("internal error running rule:", err)
|
||||
}
|
||||
if ret != nil {
|
||||
gas.Issues = append(gas.Issues, *ret)
|
||||
gas.Stats.NumFound++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return gas
|
||||
}
|
82
core/helpers.go
Normal file
82
core/helpers.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// helpfull "canned" matching routines ----------------------------------------
|
||||
|
||||
func selectName(n ast.Node, s reflect.Type) (string, bool) {
|
||||
t := reflect.TypeOf(&ast.SelectorExpr{})
|
||||
if node, ok := SimpleSelect(n, s, t).(*ast.SelectorExpr); ok {
|
||||
t = reflect.TypeOf(&ast.Ident{})
|
||||
if ident, ok := SimpleSelect(node.X, t).(*ast.Ident); ok {
|
||||
return strings.Join([]string{ident.Name, node.Sel.Name}, "."), ok
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func MatchCall(n ast.Node, r *regexp.Regexp) *ast.CallExpr {
|
||||
t := reflect.TypeOf(&ast.CallExpr{})
|
||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
||||
return n.(*ast.CallExpr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MatchCompLit(n ast.Node, r *regexp.Regexp) *ast.CompositeLit {
|
||||
t := reflect.TypeOf(&ast.CompositeLit{})
|
||||
if name, ok := selectName(n, t); ok && r.MatchString(name) {
|
||||
return n.(*ast.CompositeLit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetInt(n ast.Node) (int64, error) {
|
||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.INT {
|
||||
return strconv.ParseInt(node.Value, 0, 64)
|
||||
}
|
||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||
}
|
||||
|
||||
func GetFloat(n ast.Node) (float64, error) {
|
||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.FLOAT {
|
||||
return strconv.ParseFloat(node.Value, 64)
|
||||
}
|
||||
return 0.0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||
}
|
||||
|
||||
func GetChar(n ast.Node) (byte, error) {
|
||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.CHAR {
|
||||
return node.Value[0], nil
|
||||
}
|
||||
return 0, fmt.Errorf("Unexpected AST node type: %T", n)
|
||||
}
|
||||
|
||||
func GetString(n ast.Node) (string, error) {
|
||||
if node, ok := n.(*ast.BasicLit); ok && node.Kind == token.STRING {
|
||||
return strconv.Unquote(node.Value)
|
||||
}
|
||||
return "", fmt.Errorf("Unexpected AST node type: %T", n)
|
||||
}
|
96
core/issue.go
Normal file
96
core/issue.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Score int
|
||||
|
||||
const (
|
||||
Low Score = iota
|
||||
Medium
|
||||
High
|
||||
)
|
||||
|
||||
type Issue struct {
|
||||
Severity Score
|
||||
Confidence Score
|
||||
What string
|
||||
File string
|
||||
Code string
|
||||
Line int
|
||||
}
|
||||
|
||||
type MetaData struct {
|
||||
Severity Score
|
||||
Confidence Score
|
||||
What string
|
||||
}
|
||||
|
||||
func (c Score) String() string {
|
||||
switch c {
|
||||
case High:
|
||||
return "HIGH"
|
||||
case Medium:
|
||||
return "MEDIUM"
|
||||
case Low:
|
||||
return "LOW"
|
||||
}
|
||||
return "UNDEFINED"
|
||||
}
|
||||
|
||||
func codeSnippet(file *os.File, start int64, end int64, n ast.Node) (string, error) {
|
||||
if n == nil {
|
||||
return "", fmt.Errorf("Invalid AST node provided")
|
||||
}
|
||||
|
||||
size := (int)(end - start) // Go bug, os.File.Read should return int64 ...
|
||||
file.Seek(start, 0)
|
||||
|
||||
buf := make([]byte, size)
|
||||
if nread, err := file.Read(buf); err != nil || nread != size {
|
||||
return "", fmt.Errorf("Unable to read code")
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func NewIssue(ctx *Context, node ast.Node, desc string, severity Score, confidence Score) *Issue {
|
||||
var code string
|
||||
fobj := ctx.FileSet.File(node.Pos())
|
||||
name := fobj.Name()
|
||||
line := fobj.Line(node.Pos())
|
||||
|
||||
if file, err := os.Open(fobj.Name()); err == nil {
|
||||
defer file.Close()
|
||||
s := (int64)(fobj.Position(node.Pos()).Offset) // Go bug, should be int64
|
||||
e := (int64)(fobj.Position(node.End()).Offset) // Go bug, should be int64
|
||||
code, err = codeSnippet(file, s, e, node)
|
||||
if err != nil {
|
||||
code = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
return &Issue{
|
||||
File: name,
|
||||
Line: line,
|
||||
What: desc,
|
||||
Confidence: confidence,
|
||||
Severity: severity,
|
||||
Code: code,
|
||||
}
|
||||
}
|
401
core/select.go
Normal file
401
core/select.go
Normal file
|
@ -0,0 +1,401 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// A selector function. This is like a visitor, but has a richer interface. It
|
||||
// is called with the current ast.Node being visitied and that nodes depth in
|
||||
// the tree. The function can return true to continue traversing the tree, or
|
||||
// false to end traversal here.
|
||||
type SelectFunc func(ast.Node, int) bool
|
||||
|
||||
func walkIdentList(list []*ast.Ident, depth int, fun SelectFunc) {
|
||||
for _, x := range list {
|
||||
depthWalk(x, depth, fun)
|
||||
}
|
||||
}
|
||||
|
||||
func walkExprList(list []ast.Expr, depth int, fun SelectFunc) {
|
||||
for _, x := range list {
|
||||
depthWalk(x, depth, fun)
|
||||
}
|
||||
}
|
||||
|
||||
func walkStmtList(list []ast.Stmt, depth int, fun SelectFunc) {
|
||||
for _, x := range list {
|
||||
depthWalk(x, depth, fun)
|
||||
}
|
||||
}
|
||||
|
||||
func walkDeclList(list []ast.Decl, depth int, fun SelectFunc) {
|
||||
for _, x := range list {
|
||||
depthWalk(x, depth, fun)
|
||||
}
|
||||
}
|
||||
|
||||
func depthWalk(node ast.Node, depth int, fun SelectFunc) {
|
||||
if !fun(node, depth) {
|
||||
return
|
||||
}
|
||||
|
||||
switch n := node.(type) {
|
||||
// Comments and fields
|
||||
case *ast.Comment:
|
||||
|
||||
case *ast.CommentGroup:
|
||||
for _, c := range n.List {
|
||||
depthWalk(c, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.Field:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
walkIdentList(n.Names, depth+1, fun)
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
if n.Tag != nil {
|
||||
depthWalk(n.Tag, depth+1, fun)
|
||||
}
|
||||
if n.Comment != nil {
|
||||
depthWalk(n.Comment, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.FieldList:
|
||||
for _, f := range n.List {
|
||||
depthWalk(f, depth+1, fun)
|
||||
}
|
||||
|
||||
// Expressions
|
||||
case *ast.BadExpr, *ast.Ident, *ast.BasicLit:
|
||||
|
||||
case *ast.Ellipsis:
|
||||
if n.Elt != nil {
|
||||
depthWalk(n.Elt, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.FuncLit:
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.CompositeLit:
|
||||
if n.Type != nil {
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
}
|
||||
walkExprList(n.Elts, depth+1, fun)
|
||||
|
||||
case *ast.ParenExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
depthWalk(n.Sel, depth+1, fun)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
depthWalk(n.Index, depth+1, fun)
|
||||
|
||||
case *ast.SliceExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
if n.Low != nil {
|
||||
depthWalk(n.Low, depth+1, fun)
|
||||
}
|
||||
if n.High != nil {
|
||||
depthWalk(n.High, depth+1, fun)
|
||||
}
|
||||
if n.Max != nil {
|
||||
depthWalk(n.Max, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.TypeAssertExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
if n.Type != nil {
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.CallExpr:
|
||||
depthWalk(n.Fun, depth+1, fun)
|
||||
walkExprList(n.Args, depth+1, fun)
|
||||
|
||||
case *ast.StarExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
|
||||
case *ast.BinaryExpr:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
depthWalk(n.Y, depth+1, fun)
|
||||
|
||||
case *ast.KeyValueExpr:
|
||||
depthWalk(n.Key, depth+1, fun)
|
||||
depthWalk(n.Value, depth+1, fun)
|
||||
|
||||
// Types
|
||||
case *ast.ArrayType:
|
||||
if n.Len != nil {
|
||||
depthWalk(n.Len, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Elt, depth+1, fun)
|
||||
|
||||
case *ast.StructType:
|
||||
depthWalk(n.Fields, depth+1, fun)
|
||||
|
||||
case *ast.FuncType:
|
||||
if n.Params != nil {
|
||||
depthWalk(n.Params, depth+1, fun)
|
||||
}
|
||||
if n.Results != nil {
|
||||
depthWalk(n.Results, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.InterfaceType:
|
||||
depthWalk(n.Methods, depth+1, fun)
|
||||
|
||||
case *ast.MapType:
|
||||
depthWalk(n.Key, depth+1, fun)
|
||||
depthWalk(n.Value, depth+1, fun)
|
||||
|
||||
case *ast.ChanType:
|
||||
depthWalk(n.Value, depth+1, fun)
|
||||
|
||||
// Statements
|
||||
case *ast.BadStmt:
|
||||
|
||||
case *ast.DeclStmt:
|
||||
depthWalk(n.Decl, depth+1, fun)
|
||||
|
||||
case *ast.EmptyStmt:
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
depthWalk(n.Label, depth+1, fun)
|
||||
depthWalk(n.Stmt, depth+1, fun)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
|
||||
case *ast.SendStmt:
|
||||
depthWalk(n.Chan, depth+1, fun)
|
||||
depthWalk(n.Value, depth+1, fun)
|
||||
|
||||
case *ast.IncDecStmt:
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
|
||||
case *ast.AssignStmt:
|
||||
walkExprList(n.Lhs, depth+1, fun)
|
||||
walkExprList(n.Rhs, depth+1, fun)
|
||||
|
||||
case *ast.GoStmt:
|
||||
depthWalk(n.Call, depth+1, fun)
|
||||
|
||||
case *ast.DeferStmt:
|
||||
depthWalk(n.Call, depth+1, fun)
|
||||
|
||||
case *ast.ReturnStmt:
|
||||
walkExprList(n.Results, depth+1, fun)
|
||||
|
||||
case *ast.BranchStmt:
|
||||
if n.Label != nil {
|
||||
depthWalk(n.Label, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.BlockStmt:
|
||||
walkStmtList(n.List, depth+1, fun)
|
||||
|
||||
case *ast.IfStmt:
|
||||
if n.Init != nil {
|
||||
depthWalk(n.Init, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Cond, depth+1, fun)
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
if n.Else != nil {
|
||||
depthWalk(n.Else, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.CaseClause:
|
||||
walkExprList(n.List, depth+1, fun)
|
||||
walkStmtList(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
if n.Init != nil {
|
||||
depthWalk(n.Init, depth+1, fun)
|
||||
}
|
||||
if n.Tag != nil {
|
||||
depthWalk(n.Tag, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.TypeSwitchStmt:
|
||||
if n.Init != nil {
|
||||
depthWalk(n.Init, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Assign, depth+1, fun)
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.CommClause:
|
||||
if n.Comm != nil {
|
||||
depthWalk(n.Comm, depth+1, fun)
|
||||
}
|
||||
walkStmtList(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.SelectStmt:
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.ForStmt:
|
||||
if n.Init != nil {
|
||||
depthWalk(n.Init, depth+1, fun)
|
||||
}
|
||||
if n.Cond != nil {
|
||||
depthWalk(n.Cond, depth+1, fun)
|
||||
}
|
||||
if n.Post != nil {
|
||||
depthWalk(n.Post, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
case *ast.RangeStmt:
|
||||
if n.Key != nil {
|
||||
depthWalk(n.Key, depth+1, fun)
|
||||
}
|
||||
if n.Value != nil {
|
||||
depthWalk(n.Value, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.X, depth+1, fun)
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
|
||||
// Declarations
|
||||
case *ast.ImportSpec:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
if n.Name != nil {
|
||||
depthWalk(n.Name, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Path, depth+1, fun)
|
||||
if n.Comment != nil {
|
||||
depthWalk(n.Comment, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.ValueSpec:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
walkIdentList(n.Names, depth+1, fun)
|
||||
if n.Type != nil {
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
}
|
||||
walkExprList(n.Values, depth+1, fun)
|
||||
if n.Comment != nil {
|
||||
depthWalk(n.Comment, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.TypeSpec:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Name, depth+1, fun)
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
if n.Comment != nil {
|
||||
depthWalk(n.Comment, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.BadDecl:
|
||||
|
||||
case *ast.GenDecl:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
for _, s := range n.Specs {
|
||||
depthWalk(s, depth+1, fun)
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
if n.Recv != nil {
|
||||
depthWalk(n.Recv, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Name, depth+1, fun)
|
||||
depthWalk(n.Type, depth+1, fun)
|
||||
if n.Body != nil {
|
||||
depthWalk(n.Body, depth+1, fun)
|
||||
}
|
||||
|
||||
// Files and packages
|
||||
case *ast.File:
|
||||
if n.Doc != nil {
|
||||
depthWalk(n.Doc, depth+1, fun)
|
||||
}
|
||||
depthWalk(n.Name, depth+1, fun)
|
||||
walkDeclList(n.Decls, depth+1, fun)
|
||||
// don't walk n.Comments - they have been
|
||||
// visited already through the individual
|
||||
// nodes
|
||||
|
||||
case *ast.Package:
|
||||
for _, f := range n.Files {
|
||||
depthWalk(f, depth+1, fun)
|
||||
}
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("gas.depthWalk: unexpected node type %T", n))
|
||||
}
|
||||
}
|
||||
|
||||
type Selector interface {
|
||||
Final(ast.Node)
|
||||
Partial(ast.Node) bool
|
||||
}
|
||||
|
||||
func Select(s Selector, n ast.Node, bits ...reflect.Type) {
|
||||
fun := func(n ast.Node, d int) bool {
|
||||
if d < len(bits) && reflect.TypeOf(n) == bits[d] {
|
||||
if d == len(bits)-1 {
|
||||
s.Final(n)
|
||||
return false
|
||||
} else if s.Partial(n) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
depthWalk(n, 0, fun)
|
||||
}
|
||||
|
||||
func SimpleSelect(n ast.Node, bits ...reflect.Type) ast.Node {
|
||||
var found ast.Node
|
||||
fun := func(n ast.Node, d int) bool {
|
||||
if found != nil {
|
||||
return false // short cut logic if we have found a match
|
||||
}
|
||||
|
||||
if d < len(bits) && reflect.TypeOf(n) == bits[d] {
|
||||
if d == len(bits)-1 {
|
||||
found = n
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
depthWalk(n, 0, fun)
|
||||
return found
|
||||
}
|
46
filelist.go
Normal file
46
filelist.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type filelist []string
|
||||
|
||||
func (f *filelist) String() string {
|
||||
return strings.Join([]string(*f), ", ")
|
||||
}
|
||||
|
||||
func (f *filelist) Set(val string) error {
|
||||
*f = append(*f, val)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *filelist) Contains(path string) bool {
|
||||
// Ignore dot files
|
||||
_, filename := filepath.Split(path)
|
||||
if strings.HasPrefix(filename, ".") {
|
||||
return true
|
||||
}
|
||||
for _, pattern := range *f {
|
||||
// Match entire path
|
||||
if rv, err := filepath.Match(pattern, path); rv && err == nil {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
158
main.go
Normal file
158
main.go
Normal file
|
@ -0,0 +1,158 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"github.com/HewlettPackard/gas/output"
|
||||
)
|
||||
|
||||
// #nosec flag
|
||||
var flagNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set")
|
||||
|
||||
// format output
|
||||
var flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, csv of text")
|
||||
|
||||
// output file
|
||||
var flagOutput = flag.String("out", "", "Set output file for results")
|
||||
|
||||
var usageText = `
|
||||
GAS - Go AST Scanner
|
||||
|
||||
Gas analyzes Go source code to look for common programming mistakes that
|
||||
can lead to security problems.
|
||||
|
||||
USAGE:
|
||||
|
||||
# Check a single Go file
|
||||
$ gas example.go
|
||||
|
||||
# Check all files under the current directory and save results in
|
||||
# json format.
|
||||
$ gas -fmt=json -out=results.json ./...
|
||||
|
||||
# Run a specific set of rules (by default all rules will be run):
|
||||
$ gas -rule=sql -rule=sql ./...
|
||||
|
||||
`
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, usageText)
|
||||
fmt.Fprint(os.Stderr, "OPTIONS:\n\n")
|
||||
flag.PrintDefaults()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
// Setup usage description
|
||||
flag.Usage = usage
|
||||
|
||||
// Exclude files
|
||||
var excluded filelist = []string{"*_test.go"}
|
||||
flag.Var(&excluded, "exclude", "File pattern to exclude from scan")
|
||||
|
||||
// Rule configuration
|
||||
rules := newRulelist()
|
||||
flag.Var(&rules, "rule", "GAS rules enabled when performing a scan")
|
||||
|
||||
// Custom commands / utilities to run instead of default analyzer
|
||||
tools := newUtils()
|
||||
flag.Var(tools, "tool", "GAS utilities to assist with rule development")
|
||||
|
||||
// Parse command line arguments
|
||||
flag.Parse()
|
||||
|
||||
// Setup logging
|
||||
logger := log.New(os.Stderr, "[gas]", log.LstdFlags)
|
||||
|
||||
// Ensure at least one file was specified
|
||||
if flag.NArg() == 0 {
|
||||
|
||||
fmt.Fprintf(os.Stderr, "\nerror: FILE [FILE...] or './...' expected\n")
|
||||
flag.Usage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Run utils instead of analysis
|
||||
if len(tools.call) > 0 {
|
||||
tools.run(flag.Args()...)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Setup analyzer
|
||||
analyzer := gas.NewAnalyzer(*flagNoSec, logger)
|
||||
if !rules.overwritten {
|
||||
rules.useDefaults()
|
||||
}
|
||||
rules.apply(&analyzer)
|
||||
|
||||
// Traverse directory structure if './...'
|
||||
if flag.NArg() == 1 && flag.Arg(0) == "./..." {
|
||||
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
logger.Fatalf("Unable to traverse path %s, reason - %s", flag.Arg(0), err)
|
||||
}
|
||||
filepath.Walk(cwd, func(path string, info os.FileInfo, err error) error {
|
||||
if excluded.Contains(path) && info.IsDir() {
|
||||
logger.Printf("Skipping %s\n", path)
|
||||
return filepath.SkipDir
|
||||
}
|
||||
if !info.IsDir() && !excluded.Contains(path) &&
|
||||
strings.HasSuffix(path, ".go") {
|
||||
err = analyzer.Process(path)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
} else {
|
||||
|
||||
// Process each file individually
|
||||
for _, filename := range flag.Args() {
|
||||
if finfo, err := os.Stat(filename); err == nil {
|
||||
if !finfo.IsDir() && !excluded.Contains(filename) &&
|
||||
strings.HasSuffix(filename, ".go") {
|
||||
if err = analyzer.Process(filename); err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create output report
|
||||
if *flagOutput != "" {
|
||||
outfile, err := os.Create(*flagOutput)
|
||||
if err != nil {
|
||||
logger.Fatalf("Couldn't open: %s for writing. Reason - %s", *flagOutput, err)
|
||||
}
|
||||
defer outfile.Close()
|
||||
output.CreateReport(outfile, *flagFormat, &analyzer)
|
||||
} else {
|
||||
output.CreateReport(os.Stdout, *flagFormat, &analyzer)
|
||||
}
|
||||
}
|
95
output/formatter.go
Normal file
95
output/formatter.go
Normal file
|
@ -0,0 +1,95 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package output
|
||||
|
||||
import (
|
||||
"io"
|
||||
"text/template"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
// The output format for reported issues
|
||||
type ReportFormat int
|
||||
|
||||
const (
|
||||
ReportText ReportFormat = iota // Plain text format
|
||||
ReportJSON // Json format
|
||||
ReportCSV // CSV format
|
||||
)
|
||||
|
||||
var text = `Results:
|
||||
{{ range $index, $issue := .Issues }}
|
||||
[{{ $issue.File }}:{{ $issue.Line }}] - {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }})
|
||||
> {{ $issue.Code }}
|
||||
|
||||
{{ end }}
|
||||
Summary:
|
||||
Files: {{.Stats.NumFiles}}
|
||||
Lines: {{.Stats.NumLines}}
|
||||
Nosec: {{.Stats.NumNosec}}
|
||||
Issues: {{.Stats.NumFound}}
|
||||
|
||||
`
|
||||
|
||||
var json = `{
|
||||
"metrics": [
|
||||
Files: {{.Stats.NumFiles}},
|
||||
Lines: {{.Stats.NumLines}},
|
||||
Nosec: {{.Stats.NumNosec}},
|
||||
Issues: {{.Stats.NumFound}}],
|
||||
|
||||
"issues": [
|
||||
{{ range $index, $issue := .Issues }}{{ if $index }}, {{ end }}{
|
||||
"file": "{{ $issue.File }}",
|
||||
"line": "{{ $issue.Line }}",
|
||||
"details": "{{ $issue.What }}",
|
||||
"confidence": "{{ $issue.Confidence }}",
|
||||
"severity": "{{ $issue.Severity }}",
|
||||
"code": "{{ js $issue.Code }}"
|
||||
}{{ end }}
|
||||
]
|
||||
}`
|
||||
|
||||
var csv = `{{ range $index, $issue := .Issues -}}
|
||||
{{- $issue.File -}},
|
||||
{{- $issue.Line -}},
|
||||
{{- $issue.What -}},
|
||||
{{- $issue.Severity -}},
|
||||
{{- $issue.Confidence -}},
|
||||
{{- printf "%q" $issue.Code }}
|
||||
{{ end }}`
|
||||
|
||||
func CreateReport(w io.Writer, format string, data *gas.Analyzer) error {
|
||||
reportType := text
|
||||
|
||||
switch format {
|
||||
case "csv":
|
||||
reportType = csv
|
||||
case "json":
|
||||
reportType = json
|
||||
case "text":
|
||||
reportType = text
|
||||
default:
|
||||
reportType = text
|
||||
}
|
||||
|
||||
t, e := template.New("gas").Parse(reportType)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
return t.Execute(w, data)
|
||||
}
|
99
rulelist.go
Normal file
99
rulelist.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"strings"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"github.com/HewlettPackard/gas/rules"
|
||||
)
|
||||
|
||||
type ruleMaker func() (gas.Rule, ast.Node)
|
||||
type ruleConfig struct {
|
||||
enabled bool
|
||||
constructors []ruleMaker
|
||||
}
|
||||
|
||||
type rulelist struct {
|
||||
rules map[string]*ruleConfig
|
||||
overwritten bool
|
||||
}
|
||||
|
||||
func newRulelist() rulelist {
|
||||
var rs rulelist
|
||||
rs.rules = make(map[string]*ruleConfig)
|
||||
rs.overwritten = false
|
||||
rs.register("sql", rules.NewSqlStrConcat, rules.NewSqlStrFormat)
|
||||
rs.register("crypto", rules.NewImportsWeakCryptography, rules.NewUsesWeakCryptography)
|
||||
rs.register("hardcoded", rules.NewHardcodedCredentials)
|
||||
rs.register("perms", rules.NewMkdirPerms, rules.NewChmodPerms)
|
||||
rs.register("tempfile", rules.NewBadTempFile)
|
||||
rs.register("tls_good", rules.NewModernTlsCheck)
|
||||
rs.register("tls_ok", rules.NewIntermediateTlsCheck)
|
||||
rs.register("tls_old", rules.NewCompatTlsCheck)
|
||||
rs.register("bind", rules.NewBindsToAllNetworkInterfaces)
|
||||
rs.register("unsafe", rules.NewUsingUnsafe)
|
||||
rs.register("rsa", rules.NewWeakKeyStrength)
|
||||
rs.register("templates", rules.NewTemplateCheck)
|
||||
rs.register("exec", rules.NewSubproc)
|
||||
rs.register("errors", rules.NewNoErrorCheck)
|
||||
return rs
|
||||
}
|
||||
|
||||
func (r *rulelist) register(name string, cons ...ruleMaker) {
|
||||
r.rules[name] = &ruleConfig{false, cons}
|
||||
}
|
||||
|
||||
func (r *rulelist) useDefaults() {
|
||||
for k := range r.rules {
|
||||
r.rules[k].enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rulelist) list() []string {
|
||||
i := 0
|
||||
keys := make([]string, len(r.rules))
|
||||
for k := range r.rules {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (r *rulelist) apply(g *gas.Analyzer) {
|
||||
for _, v := range r.rules {
|
||||
if v.enabled {
|
||||
for _, ctor := range v.constructors {
|
||||
g.AddRule(ctor())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rulelist) String() string {
|
||||
return strings.Join(r.list(), ", ")
|
||||
}
|
||||
|
||||
func (r *rulelist) Set(opt string) error {
|
||||
r.overwritten = true
|
||||
if x, ok := r.rules[opt]; ok {
|
||||
x.enabled = true
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Valid rules are: %s", r)
|
||||
}
|
53
rules/bind.go
Normal file
53
rules/bind.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// Looks for net.Listen("0.0.0.0") or net.Listen(":8080")
|
||||
type BindsToAllNetworkInterfaces struct {
|
||||
gas.MetaData
|
||||
call *regexp.Regexp
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *BindsToAllNetworkInterfaces) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCall(n, r.call); node != nil {
|
||||
if arg, err := gas.GetString(node.Args[1]); err == nil {
|
||||
if r.pattern.MatchString(arg) {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewBindsToAllNetworkInterfaces() (r gas.Rule, n ast.Node) {
|
||||
r = &BindsToAllNetworkInterfaces{
|
||||
call: regexp.MustCompile(`^net.Listen$`),
|
||||
pattern: regexp.MustCompile(`^(0.0.0.0|:).*$`),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "Binds to all network interfaces",
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
62
rules/bind_test.go
Normal file
62
rules/bind_test.go
Normal file
|
@ -0,0 +1,62 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBind0000(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewBindsToAllNetworkInterfaces())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
func main() {
|
||||
l, err := net.Listen("tcp", "0.0.0.0:2000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Binds to all network interfaces")
|
||||
}
|
||||
|
||||
func TestBindEmptyHost(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewBindsToAllNetworkInterfaces())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
func main() {
|
||||
l, err := net.Listen("tcp", ":2000")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer l.Close()
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Binds to all network interfaces")
|
||||
}
|
63
rules/errors.go
Normal file
63
rules/errors.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"go/ast"
|
||||
"go/types"
|
||||
"reflect"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
type NoErrorCheck struct {
|
||||
gas.MetaData
|
||||
}
|
||||
|
||||
func (r *NoErrorCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node, ok := n.(*ast.AssignStmt); ok {
|
||||
sel := reflect.TypeOf(&ast.CallExpr{})
|
||||
if call, ok := gas.SimpleSelect(node.Rhs[0], sel).(*ast.CallExpr); ok {
|
||||
if t := c.Info.Types[call].Type; t != nil {
|
||||
if typeVal, typeErr := t.(*types.Tuple); typeErr {
|
||||
for i := 0; i < typeVal.Len(); i++ {
|
||||
if typeVal.At(i).Type().String() == "error" { // TODO(tkelsey): is there a better way?
|
||||
if id, ok := node.Lhs[i].(*ast.Ident); ok && id.Name == "_" {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if t.String() == "error" { // TODO(tkelsey): is there a better way?
|
||||
if id, ok := node.Lhs[0].(*ast.Ident); ok && id.Name == "_" {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewNoErrorCheck() (r gas.Rule, n ast.Node) {
|
||||
r = &NoErrorCheck{
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Low,
|
||||
Confidence: gas.High,
|
||||
What: "Errors unhandled.",
|
||||
},
|
||||
}
|
||||
n = (*ast.AssignStmt)(nil)
|
||||
return
|
||||
}
|
87
rules/errors_test.go
Normal file
87
rules/errors_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
func TestErrorsMulti(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewNoErrorCheck())
|
||||
|
||||
issues := gasTestRunner(
|
||||
`package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func test() (val int, err error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
v, _ := test()
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Errors unhandled")
|
||||
}
|
||||
|
||||
func TestErrorsSingle(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewNoErrorCheck())
|
||||
|
||||
issues := gasTestRunner(
|
||||
`package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func test() (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
_ := test()
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Errors unhandled")
|
||||
}
|
||||
|
||||
func TestErrorsGood(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewNoErrorCheck())
|
||||
|
||||
issues := gasTestRunner(
|
||||
`package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func test() err error {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
e := test()
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 0, "")
|
||||
}
|
67
rules/fileperms.go
Normal file
67
rules/fileperms.go
Normal file
|
@ -0,0 +1,67 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type FilePermissions struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
mode int64
|
||||
}
|
||||
|
||||
func (r *FilePermissions) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
||||
if val, err := gas.GetInt(node.Args[1]); err == nil && val > r.mode {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewChmodPerms() (r gas.Rule, n ast.Node) {
|
||||
mode := 0600
|
||||
r = &FilePermissions{
|
||||
pattern: regexp.MustCompile(`^os.Chmod$`),
|
||||
mode: (int64)(mode),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: fmt.Sprintf("Expect chmod permissions to be %#o or less", mode),
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func NewMkdirPerms() (r gas.Rule, n ast.Node) {
|
||||
mode := 0700
|
||||
r = &FilePermissions{
|
||||
pattern: regexp.MustCompile(`^(os.Mkdir|os.MkdirAll)$`),
|
||||
mode: (int64)(mode),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: fmt.Sprintf("Expect directory permissions to be %#o or less", mode),
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
51
rules/fileperms_test.go
Normal file
51
rules/fileperms_test.go
Normal file
|
@ -0,0 +1,51 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestChmod(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewChmodPerms())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
import "os"
|
||||
func main() {
|
||||
os.Chmod("/tmp/somefile", 0777)
|
||||
os.Chmod("/tmp/someotherfile", 0600)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Expect chmod permissions")
|
||||
}
|
||||
|
||||
func TestMkdir(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewMkdirPerms())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
import "os"
|
||||
func main() {
|
||||
os.Mkdir("/tmp/mydir", 0777)
|
||||
os.Mkdir("/tmp/mydir", 0600)
|
||||
os.MkdirAll("/tmp/mydir/mysubidr", 0775)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 2, "Expect directory permissions")
|
||||
}
|
53
rules/hardcoded_credentials.go
Normal file
53
rules/hardcoded_credentials.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type CredsAssign struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *CredsAssign) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node, ok := n.(*ast.AssignStmt); ok {
|
||||
for _, i := range node.Lhs {
|
||||
if ident, ok := i.(*ast.Ident); ok {
|
||||
if r.pattern.MatchString(ident.Name) {
|
||||
gi = gas.NewIssue(c, n, r.What, r.Severity, r.Confidence)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewHardcodedCredentials() (r gas.Rule, n ast.Node) {
|
||||
r = &CredsAssign{
|
||||
pattern: regexp.MustCompile("(?i)passwd|pass|password|pwd|secret|token"),
|
||||
MetaData: gas.MetaData{
|
||||
What: "Potential hardcoded credentials",
|
||||
Confidence: gas.Low,
|
||||
Severity: gas.High,
|
||||
},
|
||||
}
|
||||
n = (*ast.AssignStmt)(nil)
|
||||
return
|
||||
}
|
39
rules/hardcoded_credentials_test.go
Normal file
39
rules/hardcoded_credentials_test.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHardcoded(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewHardcodedCredentials())
|
||||
|
||||
issues := gasTestRunner(
|
||||
`
|
||||
package samples
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
username := "admin"
|
||||
password := "admin"
|
||||
fmt.Println("Doing something with: ", username, password)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Potential hardcoded credentials")
|
||||
}
|
53
rules/rsa.go
Normal file
53
rules/rsa.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
type WeakKeyStrength struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
bits int
|
||||
}
|
||||
|
||||
func (w *WeakKeyStrength) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
if node := gas.MatchCall(n, w.pattern); node != nil {
|
||||
if bits, err := gas.GetInt(node.Args[1]); err == nil && bits < (int64)(w.bits) {
|
||||
return gas.NewIssue(c, n, w.What, w.Severity, w.Confidence), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewWeakKeyStrength() (r gas.Rule, n ast.Node) {
|
||||
bits := 2048
|
||||
r = &WeakKeyStrength{
|
||||
pattern: regexp.MustCompile(`^rsa.GenerateKey$`),
|
||||
bits: bits,
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: fmt.Sprintf("RSA keys should be at least %d bits", bits),
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
48
rules/rsa_test.go
Normal file
48
rules/rsa_test.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRSAKeys(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewWeakKeyStrength())
|
||||
|
||||
issues := gasTestRunner(
|
||||
`package main
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
//Generate Private Key
|
||||
pvk, err := rsa.GenerateKey(rand.Reader, 1024)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
fmt.Println(pvk)
|
||||
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "RSA keys should")
|
||||
}
|
89
rules/sql.go
Normal file
89
rules/sql.go
Normal file
|
@ -0,0 +1,89 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type SqlStatement struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
type SqlStrConcat struct {
|
||||
SqlStatement
|
||||
}
|
||||
|
||||
// Look for "SELECT * FROM table WHERE " + " ' OR 1=1"
|
||||
func (s *SqlStrConcat) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
a := reflect.TypeOf(&ast.BinaryExpr{})
|
||||
b := reflect.TypeOf(&ast.BasicLit{})
|
||||
if node := gas.SimpleSelect(n, a, b); node != nil {
|
||||
if str, _ := gas.GetString(node); s.pattern.MatchString(str) {
|
||||
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewSqlStrConcat() (r gas.Rule, n ast.Node) {
|
||||
r = &SqlStrConcat{
|
||||
SqlStatement: SqlStatement{
|
||||
pattern: regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "SQL string concatenation",
|
||||
},
|
||||
},
|
||||
}
|
||||
n = (*ast.BinaryExpr)(nil)
|
||||
return
|
||||
}
|
||||
|
||||
type SqlStrFormat struct {
|
||||
SqlStatement
|
||||
call *regexp.Regexp
|
||||
}
|
||||
|
||||
// Looks for "fmt.Sprintf("SELECT * FROM foo where '%s', userInput)"
|
||||
func (s *SqlStrFormat) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCall(n, s.call); node != nil {
|
||||
if arg, _ := gas.GetString(node.Args[0]); s.pattern.MatchString(arg) {
|
||||
return gas.NewIssue(c, n, s.What, s.Severity, s.Confidence), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewSqlStrFormat() (r gas.Rule, n ast.Node) {
|
||||
r = &SqlStrFormat{
|
||||
call: regexp.MustCompile("^fmt.Sprintf$"),
|
||||
SqlStatement: SqlStatement{
|
||||
pattern: regexp.MustCompile("(?)(SELECT|DELETE|INSERT|UPDATE|INTO|FROM|WHERE) "),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "SQL string formatting",
|
||||
},
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
146
rules/sql_test.go
Normal file
146
rules/sql_test.go
Normal file
|
@ -0,0 +1,146 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSQLInjectionViaConcatenation(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSqlStrConcat())
|
||||
|
||||
source := `
|
||||
package main
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
func main(){
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rows, err := db.Query("SELECT * FROM foo WHERE name = " + os.Args[1])
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
}
|
||||
`
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 1, "SQL string concatenation")
|
||||
}
|
||||
|
||||
func TestSQLInjectionViaIntepolation(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSqlStrFormat())
|
||||
|
||||
source := `
|
||||
package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
func main(){
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
q := fmt.Sprintf("SELECT * FROM foo where name = '%s'", os.Args[1])
|
||||
rows, err := db.Query(q)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
}
|
||||
`
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 1, "SQL string formatting")
|
||||
}
|
||||
|
||||
func TestSQLInjectionFalsePositiveA(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSqlStrConcat())
|
||||
analyzer.AddRule(NewSqlStrFormat())
|
||||
|
||||
source := `
|
||||
|
||||
package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var staticQuery = "SELECT * FROM foo WHERE age < 32"
|
||||
|
||||
func main(){
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rows, err := db.Query(staticQuery)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
}
|
||||
|
||||
`
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 0, "Not expected to match")
|
||||
}
|
||||
|
||||
func TestSQLInjectionFalsePositiveB(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSqlStrConcat())
|
||||
analyzer.AddRule(NewSqlStrFormat())
|
||||
|
||||
source := `
|
||||
|
||||
package main
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
var staticQuery = "SELECT * FROM foo WHERE age < 32"
|
||||
|
||||
func main(){
|
||||
db, err := sql.Open("sqlite3", ":memory:")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rows, err := db.Query(staticQuery)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
}
|
||||
|
||||
`
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 0, "Not expected to match")
|
||||
}
|
59
rules/subproc.go
Normal file
59
rules/subproc.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Subprocess struct {
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *Subprocess) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
||||
// call with variable command or arguments
|
||||
for _, arg := range node.Args {
|
||||
if _, test := arg.(*ast.BasicLit); !test {
|
||||
// TODO: try to resolve the symbol ...
|
||||
what := "Subprocess launching with variable."
|
||||
return gas.NewIssue(c, n, what, gas.High, gas.High), nil
|
||||
}
|
||||
}
|
||||
|
||||
// call with partially qualified command
|
||||
if str, err := gas.GetString(node.Args[0]); err == nil {
|
||||
if !strings.HasPrefix(str, "/") {
|
||||
what := "Subprocess launching with partial path."
|
||||
return gas.NewIssue(c, n, what, gas.Medium, gas.High), nil
|
||||
}
|
||||
}
|
||||
|
||||
what := "Subprocess launching should be audited."
|
||||
return gas.NewIssue(c, n, what, gas.Low, gas.High), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewSubproc() (r gas.Rule, n ast.Node) {
|
||||
r = &Subprocess{
|
||||
pattern: regexp.MustCompile(`^exec.Command$`),
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
99
rules/subproc_test.go
Normal file
99
rules/subproc_test.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSubprocess(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSubproc())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := exec.Command("/bin/sleep", "5")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Waiting for command to finish...")
|
||||
err = cmd.Wait()
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Subprocess launching should be audited.")
|
||||
}
|
||||
|
||||
func TestSubprocessVar(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSubproc())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
run := "sleep"
|
||||
cmd := exec.Command(run, "5")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Waiting for command to finish...")
|
||||
err = cmd.Wait()
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Subprocess launching with variable.")
|
||||
}
|
||||
|
||||
func TestSubprocessPath(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewSubproc())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := exec.Command("sleep", "5")
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
log.Printf("Waiting for command to finish...")
|
||||
err = cmd.Wait()
|
||||
log.Printf("Command finished with error: %v", err)
|
||||
}`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "Subprocess launching with partial path.")
|
||||
}
|
50
rules/tempfiles.go
Normal file
50
rules/tempfiles.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type BadTempFile struct {
|
||||
gas.MetaData
|
||||
args *regexp.Regexp
|
||||
call *regexp.Regexp
|
||||
}
|
||||
|
||||
func (t *BadTempFile) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCall(n, t.call); node != nil {
|
||||
if arg, _ := gas.GetString(node.Args[0]); t.args.MatchString(arg) {
|
||||
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewBadTempFile() (r gas.Rule, n ast.Node) {
|
||||
r = &BadTempFile{
|
||||
call: regexp.MustCompile("ioutil.WriteFile|os.Create"),
|
||||
args: regexp.MustCompile("^/tmp/.*$|^/var/tmp/.*$"),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "File creation in shared tmp directory without using ioutil.Tempfile",
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
45
rules/tempfiles_test.go
Normal file
45
rules/tempfiles_test.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTempfiles(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewBadTempFile())
|
||||
|
||||
source := `
|
||||
package samples
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
file1, _ := os.Create("/tmp/demo1")
|
||||
defer file1.Close()
|
||||
|
||||
ioutil.WriteFile("/tmp/demo2", []byte("This is some data"), 0644)
|
||||
}
|
||||
`
|
||||
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 2, "shared tmp directory")
|
||||
}
|
50
rules/templates.go
Normal file
50
rules/templates.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type TemplateCheck struct {
|
||||
gas.MetaData
|
||||
call *regexp.Regexp
|
||||
}
|
||||
|
||||
func (t *TemplateCheck) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCall(n, t.call); node != nil {
|
||||
for _, arg := range node.Args {
|
||||
if _, ok := arg.(*ast.BasicLit); !ok { // basic lits are safe
|
||||
return gas.NewIssue(c, n, t.What, t.Severity, t.Confidence), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewTemplateCheck() (r gas.Rule, n ast.Node) {
|
||||
r = &TemplateCheck{
|
||||
call: regexp.MustCompile("^template.(HTML|JS|URL)$"),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.Low,
|
||||
What: "this method will not auto-escape HTML. Verify data is well formed.",
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
131
rules/templates_test.go
Normal file
131
rules/templates_test.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTemplateCheckSafe(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewTemplateCheck())
|
||||
|
||||
source := `
|
||||
package samples
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
const tmpl = ""
|
||||
|
||||
func main() {
|
||||
t := template.Must(template.New("ex").Parse(tmpl))
|
||||
v := map[string]interface{}{
|
||||
"Title": "Test <b>World</b>",
|
||||
"Body": template.HTML("<script>alert(1)</script>"),
|
||||
}
|
||||
t.Execute(os.Stdout, v)
|
||||
}`
|
||||
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 0, "this method will not auto-escape HTML. Verify data is well formed")
|
||||
}
|
||||
|
||||
func TestTemplateCheckBadHTML(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewTemplateCheck())
|
||||
|
||||
source := `
|
||||
package samples
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
const tmpl = ""
|
||||
|
||||
func main() {
|
||||
a := "something from another place"
|
||||
t := template.Must(template.New("ex").Parse(tmpl))
|
||||
v := map[string]interface{}{
|
||||
"Title": "Test <b>World</b>",
|
||||
"Body": template.HTML(a),
|
||||
}
|
||||
t.Execute(os.Stdout, v)
|
||||
}`
|
||||
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
||||
}
|
||||
|
||||
func TestTemplateCheckBadJS(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewTemplateCheck())
|
||||
|
||||
source := `
|
||||
package samples
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
const tmpl = ""
|
||||
|
||||
func main() {
|
||||
a := "something from another place"
|
||||
t := template.Must(template.New("ex").Parse(tmpl))
|
||||
v := map[string]interface{}{
|
||||
"Title": "Test <b>World</b>",
|
||||
"Body": template.JS(a),
|
||||
}
|
||||
t.Execute(os.Stdout, v)
|
||||
}`
|
||||
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
||||
}
|
||||
|
||||
func TestTemplateCheckBadURL(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewTemplateCheck())
|
||||
|
||||
source := `
|
||||
package samples
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"os"
|
||||
)
|
||||
|
||||
const tmpl = ""
|
||||
|
||||
func main() {
|
||||
a := "something from another place"
|
||||
t := template.Must(template.New("ex").Parse(tmpl))
|
||||
v := map[string]interface{}{
|
||||
"Title": "Test <b>World</b>",
|
||||
"Body": template.URL(a),
|
||||
}
|
||||
t.Execute(os.Stdout, v)
|
||||
}`
|
||||
|
||||
issues := gasTestRunner(source, analyzer)
|
||||
checkTestResults(t, issues, 1, "this method will not auto-escape HTML. Verify data is well formed")
|
||||
}
|
185
rules/tls.go
Normal file
185
rules/tls.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
type InsecureConfigTLS struct {
|
||||
MinVersion int16
|
||||
MaxVersion int16
|
||||
pattern *regexp.Regexp
|
||||
goodCiphers []string
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *InsecureConfigTLS) processTlsCipherSuites(n ast.Node, c *gas.Context) *gas.Issue {
|
||||
a := reflect.TypeOf(&ast.KeyValueExpr{})
|
||||
b := reflect.TypeOf(&ast.CompositeLit{})
|
||||
if node, ok := gas.SimpleSelect(n, a, b).(*ast.CompositeLit); ok {
|
||||
for _, elt := range node.Elts {
|
||||
if ident, ok := elt.(*ast.SelectorExpr); ok {
|
||||
if !stringInSlice(ident.Sel.Name, t.goodCiphers) {
|
||||
str := fmt.Sprintf("TLS Bad Cipher Suite: %s", ident.Sel.Name)
|
||||
return gas.NewIssue(c, n, str, gas.High, gas.High)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *InsecureConfigTLS) processTlsConfVal(n *ast.KeyValueExpr, c *gas.Context) *gas.Issue {
|
||||
if ident, ok := n.Key.(*ast.Ident); ok {
|
||||
switch ident.Name {
|
||||
case "InsecureSkipVerify":
|
||||
if node, ok := n.Value.(*ast.Ident); ok {
|
||||
if node.Name != "false" {
|
||||
return gas.NewIssue(c, n, "TLS InsecureSkipVerify set true.", gas.High, gas.High)
|
||||
}
|
||||
} else {
|
||||
// TODO(tk): symbol tab look up to get the actual value
|
||||
return gas.NewIssue(c, n, "TLS InsecureSkipVerify may be true.", gas.High, gas.Low)
|
||||
}
|
||||
|
||||
case "MinVersion":
|
||||
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
|
||||
if (int16)(ival) < t.MinVersion {
|
||||
return gas.NewIssue(c, n, "TLS MinVersion too low.", gas.High, gas.High)
|
||||
}
|
||||
// TODO(tk): symbol tab look up to get the actual value
|
||||
return gas.NewIssue(c, n, "TLS MinVersion may be too low.", gas.High, gas.Low)
|
||||
}
|
||||
|
||||
case "MaxVersion":
|
||||
if ival, ierr := gas.GetInt(n.Value); ierr == nil {
|
||||
if (int16)(ival) < t.MaxVersion {
|
||||
return gas.NewIssue(c, n, "TLS MaxVersion too low.", gas.High, gas.High)
|
||||
}
|
||||
// TODO(tk): symbol tab look up to get the actual value
|
||||
return gas.NewIssue(c, n, "TLS MaxVersion may be too low.", gas.High, gas.Low)
|
||||
}
|
||||
|
||||
case "CipherSuites":
|
||||
if ret := t.processTlsCipherSuites(n, c); ret != nil {
|
||||
return ret
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *InsecureConfigTLS) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCompLit(n, t.pattern); node != nil {
|
||||
for _, elt := range node.Elts {
|
||||
if kve, ok := elt.(*ast.KeyValueExpr); ok {
|
||||
gi = t.processTlsConfVal(kve, c)
|
||||
if gi != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func NewModernTlsCheck() (r gas.Rule, n ast.Node) {
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Modern_compatibility
|
||||
r = &InsecureConfigTLS{
|
||||
pattern: regexp.MustCompile("^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_RSA_WITH_AES_128_GCM_SHA256",
|
||||
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
|
||||
},
|
||||
}
|
||||
n = (*ast.CompositeLit)(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func NewIntermediateTlsCheck() (r gas.Rule, n ast.Node) {
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29
|
||||
r = &InsecureConfigTLS{
|
||||
pattern: regexp.MustCompile("^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",
|
||||
},
|
||||
}
|
||||
n = (*ast.CompositeLit)(nil)
|
||||
return
|
||||
}
|
||||
|
||||
func NewCompatTlsCheck() (r gas.Rule, n ast.Node) {
|
||||
// https://wiki.mozilla.org/Security/Server_Side_TLS#Old_compatibility_.28default.29
|
||||
r = &InsecureConfigTLS{
|
||||
pattern: regexp.MustCompile("^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",
|
||||
},
|
||||
}
|
||||
n = (*ast.CompositeLit)(nil)
|
||||
return
|
||||
}
|
136
rules/tls_test.go
Normal file
136
rules/tls_test.go
Normal file
|
@ -0,0 +1,136 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
func TestInsecureSkipVerify(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewModernTlsCheck())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
_, err := client.Get("https://golang.org/")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "TLS InsecureSkipVerify set true")
|
||||
}
|
||||
|
||||
func TestInsecureMinVersion(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewModernTlsCheck())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{MinVersion: 0},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
_, err := client.Get("https://golang.org/")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "TLS MinVersion too low")
|
||||
}
|
||||
|
||||
func TestInsecureMaxVersion(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewModernTlsCheck())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{MaxVersion: 0},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
_, err := client.Get("https://golang.org/")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "TLS MaxVersion too low")
|
||||
}
|
||||
|
||||
func TestInsecureCipherSuite(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewModernTlsCheck())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tr := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_DERP,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
},},
|
||||
}
|
||||
client := &http.Client{Transport: tr}
|
||||
_, err := client.Get("https://golang.org/")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 1, "TLS Bad Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_DERP")
|
||||
}
|
46
rules/unsafe.go
Normal file
46
rules/unsafe.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type UsingUnsafe struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *UsingUnsafe) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func NewUsingUnsafe() (r gas.Rule, n ast.Node) {
|
||||
r = &UsingUnsafe{
|
||||
pattern: regexp.MustCompile("unsafe.*"),
|
||||
MetaData: gas.MetaData{
|
||||
What: "Use of unsafe calls should be audited",
|
||||
Severity: gas.Low,
|
||||
Confidence: gas.High,
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
47
rules/unsafe_test.go
Normal file
47
rules/unsafe_test.go
Normal file
|
@ -0,0 +1,47 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnsafe(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewUsingUnsafe())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func main() {
|
||||
intArray := [...]int{1, 2}
|
||||
fmt.Printf("\nintArray: %v\n", intArray)
|
||||
intPtr := &intArray[0]
|
||||
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n", intPtr, *intPtr)
|
||||
addressHolder := uintptr(unsafe.Pointer(intPtr)) + unsafe.Sizeof(intArray[0])
|
||||
intPtr = (*int)(unsafe.Pointer(addressHolder))
|
||||
fmt.Printf("\nintPtr=%p, *intPtr=%d.\n\n", intPtr, *intPtr)
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 3, "Use of unsafe calls")
|
||||
|
||||
}
|
40
rules/utils_test.go
Normal file
40
rules/utils_test.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
)
|
||||
|
||||
func gasTestRunner(source string, analyzer gas.Analyzer) []gas.Issue {
|
||||
analyzer.ProcessSource("dummy.go", source)
|
||||
return analyzer.Issues
|
||||
}
|
||||
|
||||
func checkTestResults(t *testing.T, issues []gas.Issue, expected int, msg string) {
|
||||
found := len(issues)
|
||||
if found != expected {
|
||||
t.Errorf("Found %d issues, expected %d", found, expected)
|
||||
}
|
||||
|
||||
for _, issue := range issues {
|
||||
if !strings.Contains(issue.What, msg) {
|
||||
t.Errorf("Unexpected issue identified: %s", issue.What)
|
||||
}
|
||||
}
|
||||
}
|
78
rules/weakcrypto.go
Normal file
78
rules/weakcrypto.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"go/ast"
|
||||
"reflect"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type ImportsWeakCryptography struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *ImportsWeakCryptography) Match(n ast.Node, c *gas.Context) (gi *gas.Issue, err error) {
|
||||
a := reflect.TypeOf(&ast.ImportSpec{})
|
||||
b := reflect.TypeOf(&ast.BasicLit{})
|
||||
if node := gas.SimpleSelect(n, a, b); node != nil {
|
||||
if str, _ := gas.GetString(node); r.pattern.MatchString(str) {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Imports crypto/md5, crypto/des crypto/rc4
|
||||
func NewImportsWeakCryptography() (r gas.Rule, n ast.Node) {
|
||||
r = &ImportsWeakCryptography{
|
||||
pattern: regexp.MustCompile("crypto/md5|crypto/des|crypto/rc4"),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "Import of weak cryptographic primitive",
|
||||
},
|
||||
}
|
||||
n = (*ast.ImportSpec)(nil)
|
||||
return
|
||||
}
|
||||
|
||||
type UsesWeakCryptography struct {
|
||||
gas.MetaData
|
||||
pattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func (r *UsesWeakCryptography) Match(n ast.Node, c *gas.Context) (*gas.Issue, error) {
|
||||
if node := gas.MatchCall(n, r.pattern); node != nil {
|
||||
return gas.NewIssue(c, n, r.What, r.Severity, r.Confidence), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Uses des.* md5.* or rc4.*
|
||||
func NewUsesWeakCryptography() (r gas.Rule, n ast.Node) {
|
||||
r = &UsesWeakCryptography{
|
||||
pattern: regexp.MustCompile("des.NewCipher|des.NewTripleDESCipher|md5.New|md5.Sum|rc4.NewCipher"),
|
||||
MetaData: gas.MetaData{
|
||||
Severity: gas.Medium,
|
||||
Confidence: gas.High,
|
||||
What: "Use of weak cryptographic primitive",
|
||||
},
|
||||
}
|
||||
n = (*ast.CallExpr)(nil)
|
||||
return
|
||||
}
|
110
rules/weakcrypto_test.go
Normal file
110
rules/weakcrypto_test.go
Normal file
|
@ -0,0 +1,110 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rules
|
||||
|
||||
import (
|
||||
gas "github.com/HewlettPackard/gas/core"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMD5(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewImportsWeakCryptography())
|
||||
analyzer.AddRule(NewUsesWeakCryptography())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
func main() {
|
||||
for _, arg := range os.Args {
|
||||
fmt.Printf("%x - %s\n", md5.Sum([]byte(arg)), arg)
|
||||
}
|
||||
}
|
||||
`, analyzer)
|
||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
||||
}
|
||||
|
||||
func TestDES(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewImportsWeakCryptography())
|
||||
analyzer.AddRule(NewUsesWeakCryptography())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/des"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
func main() {
|
||||
block, err := des.NewCipher([]byte("sekritz"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
|
||||
ciphertext := make([]byte, des.BlockSize+len(plaintext))
|
||||
iv := ciphertext[:des.BlockSize]
|
||||
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stream := cipher.NewCFBEncrypter(block, iv)
|
||||
stream.XORKeyStream(ciphertext[des.BlockSize:], plaintext)
|
||||
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
||||
}
|
||||
|
||||
func TestRC4(t *testing.T) {
|
||||
analyzer := gas.NewAnalyzer(false, nil)
|
||||
analyzer.AddRule(NewImportsWeakCryptography())
|
||||
analyzer.AddRule(NewUsesWeakCryptography())
|
||||
|
||||
issues := gasTestRunner(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/rc4"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cipher, err := rc4.NewCipher([]byte("sekritz"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
plaintext := []byte("I CAN HAZ SEKRIT MSG PLZ")
|
||||
ciphertext := make([]byte, len(plaintext))
|
||||
cipher.XORKeyStream(ciphertext, plaintext)
|
||||
fmt.Println("Secret message is: %s", hex.EncodeToString(ciphertext))
|
||||
}
|
||||
`, analyzer)
|
||||
|
||||
checkTestResults(t, issues, 2, "weak cryptographic")
|
||||
}
|
77
tools.go
Normal file
77
tools.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
// (c) Copyright 2016 Hewlett Packard Enterprise Development LP
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type command func(args ...string)
|
||||
type utilities struct {
|
||||
commands map[string]command
|
||||
call []string
|
||||
}
|
||||
|
||||
// Custom commands / utilities to run instead of default analyzer
|
||||
func newUtils() *utilities {
|
||||
utils := make(map[string]command)
|
||||
utils["dump"] = dumpAst
|
||||
return &utilities{utils, make([]string, 0)}
|
||||
}
|
||||
|
||||
func (u *utilities) String() string {
|
||||
i := 0
|
||||
keys := make([]string, len(u.commands))
|
||||
for k := range u.commands {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
return strings.Join(keys, ", ")
|
||||
}
|
||||
|
||||
func (u *utilities) Set(opt string) error {
|
||||
if _, ok := u.commands[opt]; !ok {
|
||||
return fmt.Errorf("valid tools are: %s", u.String())
|
||||
|
||||
}
|
||||
u.call = append(u.call, opt)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *utilities) run(args ...string) {
|
||||
for _, util := range u.call {
|
||||
if cmd, ok := u.commands[util]; ok {
|
||||
cmd(args...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dumpAst(files ...string) {
|
||||
for _, arg := range files {
|
||||
// Create the AST by parsing src.
|
||||
fset := token.NewFileSet() // positions are relative to fset
|
||||
f, err := parser.ParseFile(fset, arg, nil, 0)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Print the AST.
|
||||
ast.Print(fset, f)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue