diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b4774d0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,107 @@ +# Copyright 2020 Praetorian Security, Inc. +# +# 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. + +# Used as inspiration: https://github.com/caddyserver/caddy/blob/master/.github/workflows/ci.yml + +name: CI + +on: + push: + branches: + - main + - development + pull_request: + branches: + - main + - development + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Print Go version and environment + id: vars + run: | + printf "Using go at: $(which go)\n" + printf "Go version: $(go version)\n" + printf "\n\nGo environment:\n\n" + go env + printf "\n\nSystem environment:\n\n" + env + # Calculate the short SHA1 hash of the git commit + echo "::set-output name=short_sha::$(git rev-parse --short HEAD)" + echo "::set-output name=go_cache::$(go env GOCACHE)" + + # - name: Cache the build cache + # uses: actions/cache@v2 + # with: + # path: ${{ steps.vars.outputs.go_cache }} + # key: ${{ runner.os }}-go-ci-${{ hashFiles('**/go.sum') }} + # restore-keys: | + # ${{ runner.os }}-go-ci + + - name: Get dependencies + run: | + go mod download + + - name: Build all binaries + env: + CGO_ENABLED: 0 + run: | + go build + + # Commented bits below were useful to allow the job to continue + # even if the tests fail, so we can publish the report separately + # For info about set-output, see https://stackoverflow.com/questions/57850553/github-actions-check-steps-status + - name: Run tests + # id: step_test + # continue-on-error: true + run: | + go test -v ./... + + # From https://github.com/reviewdog/action-golangci-lint +# golangci-lint: +# name: golangci-lint +# runs-on: ubuntu-latest +# steps: +# - name: Checkout code into the Go module directory +# uses: actions/checkout@v2 +# +# - name: Run golangci-lint +# uses: reviewdog/action-golangci-lint@v1 +# # uses: docker://reviewdog/action-golangci-lint:v1 # pre-build docker image +# with: +# github_token: ${{ secrets.github_token }} +# +# goreleaser-check: +# runs-on: ubuntu-latest +# steps: +# - name: checkout +# uses: actions/checkout@v2 +# - uses: goreleaser/goreleaser-action@v2 +# with: +# version: latest +# args: check +# env: +# TAG: ${{ steps.vars.outputs.version_tag }} +# diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1923a29 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,44 @@ +# Copyright 2020 Praetorian Security, Inc. +# +# 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. + +on: + push: + # Sequence of patterns matched against refs/tags + tags: + - 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10 + +name: Release + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - + name: Set up Go + uses: actions/setup-go@v2 + with: + go-version: 1.16 + - + name: Run GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ab95986 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Visual Studio Code +.devcontainer/ + +# macOS +.DS_Store + +# Binaries +ssa-poc +gokart diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..62a1211 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,43 @@ +# Copyright 2021 Praetorian Security, Inc. +# +# 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. + +# Documentation on this file structure: https://goreleaser.com +before: + hooks: + - go mod download +builds: + - + id: gokart + main: ./main.go + binary: gokart + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + goarch: + - amd64 +archives: + - replacements: + darwin: darwin_macOS + amd64: x86_64 +changelog: + # skip changelog generation for now + skip: true + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..71989a2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +opensource@praetorian.com. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..bcf377f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,35 @@ +# How to contribute + +The entire point of us making this available is so that we can get input and contributions, so we are really glad you're thinking about contributing. +A good place to start thinking about places we need help is our [Issues](issues) page. + +Before you begin: +- Check to see if you're on the must up to date master branch of GoKart +- Have you read the [code of conduct](CODE_OF_CONDUCT.md)? + +If you want to reach out to us to chat, you can email the GoKart team at . + +## Testing + +Make sure your code submission runs well against some test repos. Consider testing both positive and negative use cases - that is, if you update a signature +to find more vulnerabilities, that's great. However, **please** make sure it doesn't find vulnerabilities where there are none. + +## Submitting changes + +Please send a [GitHub Pull Request to GoKart](pull/new/master) with a clear list of what you've done +(read more about [pull requests](http://help.github.com/pull-requests/)). When you send a pull request, you'll be forever adored if you help us also add +to our tests so we can make sure this feature or bug fix stays working. Please make sure all of your commits are atomic (one feature per commit). + +Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: + + $ git commit -m "A brief summary of the commit + > + > A paragraph describing what changed and its impact." + +## Coding conventions + +Start reading our code and things should be pretty clear. We optimize for readability and simplicity, wherever possible. Clear beats out clever every time :) + +## Attribution + +These guidelines are very loosely adapted from . diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f49a4e1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..47bcacd --- /dev/null +++ b/README.md @@ -0,0 +1,156 @@ +

+ +

+ +# GoKart - Go Security Static Analysis + +[![CI](https://github.com/praetorian-inc/gokart/workflows/CI/badge.svg)](actions?query=branch%3Adevelopment) +[![Release](https://github.com/praetorian-inc/gokart/workflows/Release/badge.svg)](releases) + +GoKart is a static analysis tool for Go that finds vulnerabilities using +the SSA (single static assignment) form of Go source code. It is capable of +tracing the source of variables and function arguments to determine whether +input sources are safe, which reduces the number of false positives compared +to other Go security scanners. For instance, a SQL query that is concatenated with a variable might +traditionally be flagged as SQL injection; however, GoKart can figure out if the variable +is actually a constant or constant equivalent, in which case there is no vulnerability. + +## Why We Built GoKart + +Static analysis is a powerful technique for finding vulnerabilities in source code. +However, the approach has suffered from being noisy - that is, many static analysis +tools find quite a few "vulnerabilities" that are not actually real. This has led +to developer friction as users get tired of the tools "crying wolf" one time too +many. + +The motivation for GoKart was to address this: could we create a scanner with +significantly lower false positive rates than existing tools? Based on our experimentation +the answer is yes. By leveraging source-to-sink tracing and SSA, GoKart is capable +of tracking variable taint between variable assignments, significantly improving the +accuracy of findings. Our focus is on usability: pragmatically, that means we +have optimized our approaches to reduce false alarms. + +## Install + +You can install GoKart locally by using any one of the options listed below. + +### Install with `go install` + +```shell +$ go install github.com/praetorian-inc/gokart@latest +``` + +### Install a release binary + +1. Download the binary for your OS from the [releases page](https://github.com/praetorian-inc/gokart/releases). + +2. (OPTIONAL) Download the `checksums.txt` file to verify the integrity of the archive + +```shell +# Check the checksum of the downloaded archive +$ shasum -a 256 gokart_${VERSION}_${ARCH}.tar.gz +b05c4d7895be260aa16336f29249c50b84897dab90e1221c9e96af9233751f22 gokart_${VERSION}_${ARCH}.tar.gz + +$ cat gokart_${VERSION}_${ARCH}_checksums.txt | grep gokart_${VERSION}_${ARCH}.tar.gz +b05c4d7895be260aa16336f29249c50b84897dab90e1221c9e96af9233751f22 gokart_${VERSION}_${ARCH}.tar.gz +``` + +3. Extract the downloaded archive + +```shell +$ tar -xvf gokart_${VERSION}_${ARCH}.tar.gz +``` + +4. Move the `gokart` binary into your path: + +```shell +$ mv ./gokart /usr/local/bin/ +``` + +### Clone and build yourself + +```shell +# clone the GoKart repo +$ git clone https://github.com/praetorian-inc/gokart.git + +# navigate into the repo directory and build +$ cd gokart +$ go build + +# Move the gokart binary into your path +$ mv ./gokart /usr/local/bin +``` + +## Usage + +### Run GoKart on a Go module in the current directory + +```shell +# running without a directory specified defaults to '.' +gokart scan +``` + +### Scan a Go module in a different directory + +```shell +gokart scan +``` + +### Get Help + +```shell +gokart help +``` + +## Getting Started - Scanning an Example App + +You can follow the steps below to run GoKart on [Go Test Bench](https://github.com/Contrast-Security-OSS/go-test-bench), +an intentionally vulnerable Go application from the Contrast Security team. + +```shell +# Clone sample vulnerable application +git clone https://github.com/Contrast-Security-OSS/go-test-bench.git +gokart scan go-test-bench/ +``` + +Output should show some identified vulnerabilities, each with a Vulnerable Function and Source of +User Input identified. + +To test some additional GoKart features, you can scan with the CLI flags suggested below. + +```shell +# Use verbose flag to show full traces of these vulnerabilities +gokart scan go-test-bench/ -v + +# Use globalsTainted flag to ignore whitelisted Sources +# may increase false positive results +gokart scan go-test-bench/ -v -g + +# Use debug flag to display internal analysis information +# which is useful for development and debugging +gokart scan go-test-bench/ -d + +# Output results in sarif format +gokart scan go-test-bench/ -s +``` + +To test out the extensibility of GoKart, you can modify the configuration file that GoKart uses to +introduce a new vulnerable sink into analysis. There is a Test Sink analyzer defined in the included +default config file at `util/analyzers.yml`. Modify `util/analyzers.yml` to remove the comments on +the Test Sink analyzer and then direct GoKart to use the modified config file with the `-i` flag. + +```shell +# Scan using modified analyzers.yml file and output full traces +gokart scan go-test-bench/ -v -i /util/analyzers.yml +``` + +Output should now contain additional vulnerabilities, including new "Test Sink reachable by user input" +vulnerabilities. + +## Run GoKart Tests + +You can run the included tests with the following command, invoked from the GoKart root directory. + +```shell +go test -v ./... +``` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..b3ddf0f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,3 @@ +# GoKart Security Reporting Guidelines + +To report a security issue, please email opensource@praetorian.com with a description of the issue, the steps you took to reproduce the issue, affected versions, and if known, mitigations for the issue. Our developers will acknowledge receiving your email within 3 working days. This project follows a 90 day disclosure timeline. diff --git a/analyzers/cmdi.go b/analyzers/cmdi.go new file mode 100644 index 0000000..a32b4d5 --- /dev/null +++ b/analyzers/cmdi.go @@ -0,0 +1,87 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "github.com/praetorian-inc/gokart/util" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" +) + +// CommandInjectionAnalyzer constructs Sinks from a set of functions known to be vulnerable to command injection, +// converts all variables to SSA form to construct a call graph and performs +// recursive taint analysis to search for input sources of user-controllable data +var CommandInjectionAnalyzer = &analysis.Analyzer{ + Name: "command_injection", + Doc: "reports when command injection can occur", + Run: cmdInjectionRun, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, +} + +// vulnCmdInjectionFuncs() returns a map of command injection functions that may be vulnerable when used with user controlled input +func vulnCmdInjectionFuncs() map[string][]string { + return map[string][]string{ + "os/exec": {"Command", "CommandContext"}, + } +} + +// command_injection_run runs the command injection analyzer +func cmdInjectionRun(pass *analysis.Pass) (interface{}, error) { + results := []util.Finding{} + + // Builds SSA model of Go code + ssaFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Creates call graph of function calls + call_graph := make(util.CallGraph) + + // Fills in call graph + for _, fn := range ssaFuncs { + call_graph.AnalyzeFunction(fn) + } + + // Grabs vulnerable functions to scan for + vulnPathFuncs := vulnCmdInjectionFuncs() + + // Iterate over every specified vulnerable package + for pkg, funcs := range vulnPathFuncs { + + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + + // Construct full name of function + current_function := pkg + "." + fn + + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range call_graph[current_function] { + + // Check if argument of vulnerable function is tainted by possibly user-controlled input + taintAnalyzer := util.CreateTaintAnalyzer(pass, vulnFunc.Fn.Pos()) + for i := 0; i < len(vulnFunc.Instr.Call.Args); i++ { + if taintAnalyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[i], call_graph) { + message := "Danger: possible command injection detected" + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taintAnalyzer.TaintSource + finding := util.MakeFinding(message, targetFunc, taintSource, "Command Injection") + results = append(results, finding) + } + } + } + } + } + + return results, nil +} diff --git a/analyzers/cmdi_test.go b/analyzers/cmdi_test.go new file mode 100644 index 0000000..752776e --- /dev/null +++ b/analyzers/cmdi_test.go @@ -0,0 +1,45 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "testing" + + "github.com/praetorian-inc/gokart/test/testutil" +) + +func TestCommandInjection(t *testing.T) { + testFiles := []string{ + "command_context_injection_safe.go", + "command_injection.go", + "commandContext.go", + } + + // Append directory to each entry + for i := 0; i < len(testFiles); i++ { + testFiles[i] = "command_injection/" + testFiles[i] + } + + testResults := []int{ + 0, + 2, + 2, + } + for i := 0; i < len(testFiles); i++ { + t.Run(testFiles[i], func(t *testing.T) { + testutil.RunTest(testFiles[i], testResults[i], "Command Injection", CommandInjectionAnalyzer, t) + }) + } +} diff --git a/analyzers/generic.go b/analyzers/generic.go new file mode 100644 index 0000000..49fd8b4 --- /dev/null +++ b/analyzers/generic.go @@ -0,0 +1,119 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "io/ioutil" + "log" + + "github.com/praetorian-inc/gokart/util" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "gopkg.in/yaml.v3" +) + +// Creates generic taint analyzer based on Sources and Sinks defined in analyzers.yaml file +func genericFunctionRun(pass *analysis.Pass, vulnPathFuncs map[string][]string, + name string, message string) (interface{}, error) { + results := []util.Finding{} + + // Build SSA model of Go code + ssaFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Create call graph of function calls + cg := make(util.CallGraph) + + // Fill in call graph + for _, fn := range ssaFuncs { + cg.AnalyzeFunction(fn) + } + + // Iterate over every specified vulnerable package + for pkg, funcs := range vulnPathFuncs { + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + // Construct full name of function + currentFunc := pkg + "." + fn + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range cg[currentFunc] { + // Check if argument of vulnerable function is tainted by possibly user-controlled input + taintAnalyzer := util.CreateTaintAnalyzer(pass, vulnFunc.Fn.Pos()) + for i := 0; i < len(vulnFunc.Instr.Call.Args); i++ { + if taintAnalyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[i], cg) { + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taintAnalyzer.TaintSource + results = append(results, util.MakeFinding(message, targetFunc, taintSource, name)) + } + } + } + } + } + return results, nil +} + +// LoadGenericAnalyzers creates generic taint anlalyzers from custom Sources and Sinks defined in analyzers.yaml +// converts all variables to SSA form to construct a call graph and performs +// recursive taint analysis to search for input sources of user-controllable data + +func LoadGenericAnalyzers(yaml_path string) []*analysis.Analyzer { + yfile, err := ioutil.ReadFile(yaml_path) + if err != nil { + log.Fatal(err) + } + + data := make(map[interface{}]map[interface{}]map[interface{}]interface{}) + err = yaml.Unmarshal(yfile, &data) + if err != nil { + log.Fatal(err) + } + + // Load analyzers from the interface + analyzers := []*analysis.Analyzer{} + m := data["analyzers"] + for analyzerName, analyzerDict := range m { + // Get the vulnerability message + message := "" + if analyzerDict["message"] != nil { + message = analyzerDict["message"].(string) + } + + // Load the map of vulnerable functions + vulnCalls := make(map[string][]string) + yamlCallsMap := analyzerDict["vuln_calls"].(map[string]interface{}) + for pkgName, packageVulnFuncs := range yamlCallsMap { + var newList []string + vulnCalls[pkgName] = newList + packageVulnFuncsList := packageVulnFuncs.([]interface{}) + for _, val := range packageVulnFuncsList { + vulnCalls[pkgName] = append(vulnCalls[pkgName], val.(string)) + } + } + + // Wrap generic_function_run with a function that the analyze package can use + analyzerFunc := func(pass *analysis.Pass) (interface{}, error) { + return genericFunctionRun(pass, vulnCalls, analyzerName.(string), message) + } + + // Form the analyzer object and append to the analyzer list + analysisRun := new(analysis.Analyzer) + analysisRun.Name = "path_traversal" + analysisRun.Doc = analyzerDict["doc"].(string) + analysisRun.Run = analyzerFunc + analysisRun.Requires = []*analysis.Analyzer{buildssa.Analyzer} + analyzers = append(analyzers, analysisRun) + } + + return analyzers +} diff --git a/analyzers/rsa.go b/analyzers/rsa.go new file mode 100644 index 0000000..8409a32 --- /dev/null +++ b/analyzers/rsa.go @@ -0,0 +1,185 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "fmt" + "go/constant" + "go/token" + + "github.com/praetorian-inc/gokart/util" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/ssa" +) + +// RSAKeyLenAnalyzer is used to resolve constant values used for RSA key generation in order to more accurately +// detect use of an insecure RSA key length constructed +// all variables are converted to SSA form and a call graph is contructed +// recursive analysis is then used to resolve variables used as a key length to a final constant value at the callsite +var RsaKeylenAnalyzer = &analysis.Analyzer{ + Name: "rsa_keylen", + Doc: "reports when rsa keys are too short", + Run: rsaRun, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, +} + +const RECOMMENDED_KEYLEN = 2048 + +// vulnerableRsaFuncs() returns a map of functions that generate RSA keys +func vulnerableRsaFuncs() map[string][]string { + return map[string][]string{ + "crypto/rsa": {"GenerateKey"}, + } +} + +// EvalConst attempts to take a value, and simplify it down to a single constant +// it returns a tuple of (the constant, whether or not it successfully simplified) +func EvalConst(expr ssa.Value, cg util.CallGraph) (*ssa.Const, bool) { + + switch expr := expr.(type) { + + case *ssa.Const: + return expr, true + case *ssa.BinOp: + X, okX := EvalConst(expr.X, cg) + Y, okY := EvalConst(expr.Y, cg) + + if okX && okY { + return merge(X, Y, expr) + } + case *ssa.Call: + if dest := expr.Common().StaticCallee(); dest != nil { + rets := util.ReturnValues(dest) + if len(rets) == 1 && len(rets[0]) == 1 { + return EvalConst(rets[0][0], cg) + } + } + case *ssa.Parameter: + var values []*ssa.Value + values = cg.ResolveParam(expr) + return EvalConst(*values[0], cg) + + case *ssa.Phi: + var res bool + var val *ssa.Const + val, res = EvalConst(expr.Edges[0], cg) + + for _, edge := range expr.Edges { + var tmp *ssa.Const + var tmp2 bool + tmp, tmp2 = EvalConst(edge, cg) + if tmp.Int64() < val.Int64() { + val = tmp //val ends up being the shortest value that this phi node could be + } + res = res && tmp2 //res is whether or not the boolean expr could be evaluated + } + return val, res + } + + return nil, false +} + +// Merge merges two Consts to a BinOp +func merge(x, y *ssa.Const, op *ssa.BinOp) (*ssa.Const, bool) { + switch op.Op { + case token.ADD, token.SUB, token.MUL: + return ssa.NewConst(constant.BinaryOp(x.Value, op.Op, y.Value), x.Type()), true + case token.QUO: + return ssa.NewConst(constant.BinaryOp(x.Value, token.QUO_ASSIGN, y.Value), x.Type()), true + + } + return nil, false +} + +// keylen_check recursively checks if a vulnerable function that relies on RSA is using a number of bits that is less than RECOMMENDED_KEYLEN +func keylen_check(pass *analysis.Pass, keylen ssa.Value, cg util.CallGraph) bool { + unsafe := false + + switch keylen := keylen.(type) { + case *ssa.Const: + real_len := keylen.Int64() + if real_len < RECOMMENDED_KEYLEN { + unsafe = true + } + case *ssa.Phi: + for _, edge := range keylen.Edges { + if keylen != edge { + unsafe = unsafe || keylen_check(pass, edge, cg) + } + } + case *ssa.BinOp: + if val, ok := EvalConst(keylen, cg); ok { + unsafe = keylen_check(pass, val, cg) + } + case *ssa.Call: + if dest := keylen.Common().StaticCallee(); dest != nil { + returns := util.ReturnValues(dest) + for _, retval := range returns { + unsafe = unsafe || keylen_check(pass, retval[0], cg) + } + } + case *ssa.Parameter: + var values []*ssa.Value + values = cg.ResolveParam(keylen) + if len(values) > 0 { + unsafe = unsafe || keylen_check(pass, *values[0], cg) + } + } + return unsafe +} + +// rsaRun runs the rsa keylength analyzer +func rsaRun(pass *analysis.Pass) (interface{}, error) { + results := []util.Finding{} + // Builds SSA model of Go code + ssa_functions := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Creates call graph of function calls + call_graph := make(util.CallGraph) + + // Fills in call graph + for _, fn := range ssa_functions { + call_graph.AnalyzeFunction(fn) + } + + // Grabs vulnerable functions to scan for + vuln_rsa_funcs := vulnerableRsaFuncs() + + // Iterate over every specified vulnerable package + for pkg, funcs := range vuln_rsa_funcs { + + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + + // Construct full name of function + current_function := pkg + "." + fn + + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range call_graph[current_function] { + + // Check if argument of vulnerable function has keylen that is less than RECOMMENDED_KEYLEN + if keylen_check(pass, vulnFunc.Instr.Call.Args[1], call_graph) { + message := fmt.Sprintf("Danger: key length is too short, recommend %d", RECOMMENDED_KEYLEN) + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + results = append(results, util.MakeFinding(message, targetFunc, nil, "RSA Key Length")) + } + } + } + } + + return results, nil +} diff --git a/analyzers/rsa_test.go b/analyzers/rsa_test.go new file mode 100644 index 0000000..64185b6 --- /dev/null +++ b/analyzers/rsa_test.go @@ -0,0 +1,57 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "testing" + + "github.com/praetorian-inc/gokart/test/testutil" +) + +func TestRsaKeylen(t *testing.T) { + testFiles := []string{ + "rsa_direct_bad.go", + "rsa_direct_good.go", + "rsa_indirect_bad.go", + "rsa_indirect_good.go", + "rsa_phi_bad.go", + "rsa_phi_good.go", + "rsa_multiple_params_bad.go", + "rsa_multiple_params_good.go", + "rsa_multiple_issues.go", + } + + // Append directory to each entry + for i := 0; i < len(testFiles); i++ { + testFiles[i] = "rsa_keylength/" + testFiles[i] + } + + testResults := []int{ + 1, + 0, + 1, + 0, + 1, + 0, + 1, + 0, + 2, + } + for i := 0; i < len(testFiles); i++ { + t.Run(testFiles[i], func(t *testing.T) { + testutil.RunTest(testFiles[i], testResults[i], "RSA Key Length", RsaKeylenAnalyzer, t) + }) + } +} diff --git a/analyzers/scan.go b/analyzers/scan.go new file mode 100644 index 0000000..c71472d --- /dev/null +++ b/analyzers/scan.go @@ -0,0 +1,140 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers implements individual security scanners for Go +and a generic analyzer based on recursive taint propagation +*/ +package analyzers + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + "time" + + "github.com/praetorian-inc/gokart/run" + "github.com/praetorian-inc/gokart/util" + "golang.org/x/tools/go/analysis" +) + +var Analyzers = []*analysis.Analyzer{ + RsaKeylenAnalyzer, + PathTraversalAnalyzer, + SQLInjectionAnalyzer, + CommandInjectionAnalyzer, + SSRFAnalyzer, +} + +func Scan(args []string) { + if util.Config.OutputSarif { + util.InitSarifReporting() + } else { + fmt.Printf("\nRevving engines VRMMM VRMMM\n3...2...1...Go!\n") + } + + // If we're given a target path, we do some slight changes to make sure that + // gokart will behave as expected. Specifically we turn the path into an absolute + // path, and then we append /... to the end to make sure the package loading is recursive. + // Finally we update the current working directory to the target + if len(args) > 0 { + target_path := args[0] + if !filepath.IsAbs(target_path) { + target_path, _ = filepath.Abs(args[0]) + args[0] = target_path + } + + // Fix up the path to make sure it is pointed at a directory (even if given a file) + fileInfo, err := os.Stat(strings.TrimRight(target_path, "...")) + if err != nil { + log.Fatal(err) + } + target_dir := filepath.Dir(target_path) + if fileInfo.IsDir() { + // Adding an extra / to the end of the path to make sure we still target the directory + target_dir = filepath.Dir(target_path + "/") + } + + if !strings.HasSuffix(target_path, "...") { + target_path = filepath.Join(target_dir, "...") + args[0] = target_path + if util.Config.Debug { + fmt.Printf("Setting target_path to %s\n", args[0]) + } + } + + err = os.Chdir(strings.TrimRight(target_path, "...")) + if err != nil { + log.Fatal(err) + } + cwd, _ := os.Getwd() + if util.Config.Debug { + fmt.Printf("Current working directory is %s\n", cwd) + } + } + + generic_analyzers := LoadGenericAnalyzers(util.Config.YMLPath) + Analyzers = append(Analyzers, generic_analyzers[:]...) + + // Begin timer + run_begin_time := time.Now() + // Run analyzers + results, success, err := run.Run(Analyzers, args...) + if err != nil { + panic(err) + } + // Calculate time taken + scan_time := time.Since(run_begin_time) + + /* Unless the argument given is an absolute path, the path to the source file for findings are trimmed + * to be relative to the most specific path shared by the argument and the current working directory. + */ + parent_dir := "" + if len(args) > 0 && !filepath.IsAbs(args[0]) { + full_path, _ := filepath.Abs(args[0]) + full_path = strings.TrimSuffix(full_path, "...") + cwd, _ := os.Getwd() + full_path_split := strings.Split(full_path, "/")[1:] + cwd_split := strings.Split(cwd, "/")[1:] + i := 0 + for i < len(full_path_split) && i < len(cwd_split) && full_path_split[i] == cwd_split[i] { + parent_dir += "/" + full_path_split[i] + i++ + } + parent_dir += "/" + } + + count := 0 + for _, finding := range results { + finding.Vulnerable_Function.SourceFilename = strings.TrimPrefix(finding.Vulnerable_Function.SourceFilename, parent_dir) + if finding.Untrusted_Source != nil { + for i, source := range finding.Untrusted_Source { + finding.Untrusted_Source[i].SourceFilename = strings.TrimPrefix(source.SourceFilename, parent_dir) + } + } + if util.OutputFinding(finding) { + count++ + } + } + // if packages were able to be scanned, print the correct output message + if util.Config.OutputSarif && success { + util.SarifPrintReport() + fmt.Println() + } else if success { + fmt.Println("\nRace Complete! Analysis took", scan_time, "and", util.FilesFound, "Go files were scanned (including imported packages)") + fmt.Printf("GoKart found %d potentially vulnerable functions\n", count) + } +} diff --git a/analyzers/sqli.go b/analyzers/sqli.go new file mode 100644 index 0000000..0d34353 --- /dev/null +++ b/analyzers/sqli.go @@ -0,0 +1,90 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "strings" + + "github.com/praetorian-inc/gokart/util" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" +) + +// SQLInjectionAnalyzer constructs Sinks from a set of functions known to be vulnerable to SQL injection +// all variables are converted to SSA form and a call graph is contructed +// recursive taint analysis is then used to search from a given Sink up the callgraph for Sources of user-controllable data +var SQLInjectionAnalyzer = &analysis.Analyzer{ + Name: "sql_injection", + Doc: "reports when SQL injection can occur", + Run: sqlRun, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, +} + +// grab_vulnerable_sql_functions() creates map of vulnerable functions that the scanner will check +func getVulnSqlFuncs() map[string][]string { + return map[string][]string{ + "(*database/sql.DB)": {"Exec", "ExecContext", "Query", "QueryContext", "QueryRow", "QueryRowContext"}, + } +} + +// sql_run runs the path traversal analyzer +func sqlRun(pass *analysis.Pass) (interface{}, error) { + results := []util.Finding{} + // Builds SSA model of Go code + ssaFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Creates call graph of function calls + cg := make(util.CallGraph) + + // Fills in call graph + for _, fn := range ssaFuncs { + cg.AnalyzeFunction(fn) + } + + // Grabs vulnerable functions to scan for + vuln_db_funcs := getVulnSqlFuncs() + + // Iterate over every specified vulnerable package + for pkg, funcs := range vuln_db_funcs { + + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + + // Construct full name of function + current_function := pkg + "." + fn + + // For SQL injections we only care about the argument that holds the query string (index 1 for normal query and index 2 for Context query) + argIndex := 1 + if strings.Contains(current_function, "Context") { + argIndex = 2 + } + + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range cg[current_function] { + + // Check if argument of vulnerable function is tainted by possibly user-controlled input + taint_analyzer := util.CreateTaintAnalyzer(pass, vulnFunc.Fn.Pos()) + if taint_analyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[argIndex], cg) { + message := "Danger: possible SQL injection detected" + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taint_analyzer.TaintSource + results = append(results, util.MakeFinding(message, targetFunc, taintSource, "SQL Injection")) + } + } + } + } + + return results, nil +} diff --git a/analyzers/sqli_test.go b/analyzers/sqli_test.go new file mode 100644 index 0000000..35cbb25 --- /dev/null +++ b/analyzers/sqli_test.go @@ -0,0 +1,45 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "testing" + + "github.com/praetorian-inc/gokart/test/testutil" +) + +func TestSQLInjection(t *testing.T) { + testFiles := []string{ + "sql1.go", + "sql2.go", + "sql3.go", + } + + // Append directory to each entry + for i := 0; i < len(testFiles); i++ { + testFiles[i] = "sql_injection/" + testFiles[i] + } + + testResults := []int{ + 2, + 0, + 5, + } + for i := 0; i < len(testFiles); i++ { + t.Run(testFiles[i], func(t *testing.T) { + testutil.RunTest(testFiles[i], testResults[i], "SQL Injection", SQLInjectionAnalyzer, t) + }) + } +} diff --git a/analyzers/ssrf.go b/analyzers/ssrf.go new file mode 100644 index 0000000..bb49337 --- /dev/null +++ b/analyzers/ssrf.go @@ -0,0 +1,182 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "github.com/praetorian-inc/gokart/util" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/ssa" +) + +// SSRF Analyzer constructs Sinks from a set of functions known to be vulnerable to Server Side Request Forgery, +// converts all variables to SSA form to construct a call graph and performs +// recursive taint analysis to search for input sources of user-controllable data +var SSRFAnalyzer = &analysis.Analyzer{ + Name: "SSRF", + Doc: "reports when SSRF vulnerabilities can occur", + Run: ssrfRun, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, +} + +// vulnerable_ssrf_funcs() returns a map of networking functions that may be vulnerable when used with user controlled input +func vulnSsrfFuncs() map[string][]string { + return map[string][]string{ + "net/http": {"Do", "Get", "Head", "Post", "PostForm"}, + "(*net/http.Client)": {"Do", "Get", "Head", "Post", "PostForm"}, + } +} + +func taintCheck(val *ssa.Value) bool { + //Alloc of http client + alloc, ok := (*val).(*ssa.Alloc) + if ok { + refs := alloc.Referrers() + for _, ref := range *refs { + //fieldAddr of http client + fieldAddr, ok := (ref).(*ssa.FieldAddr) + if !ok { + continue + } + + //refs to that field + refs = fieldAddr.Referrers() + for _, ref := range *refs { + store, ok := (ref).(*ssa.Store) + if !ok { + continue + } + makeInterface, ok := store.Val.(*ssa.MakeInterface) + if !ok { + continue + } + transportAlloc, ok := makeInterface.X.(*ssa.Alloc) + if !ok { + continue + } + refs = transportAlloc.Referrers() + for _, ref := range *refs { + fieldAddr, ok = (ref).(*ssa.FieldAddr) + if !ok || fieldAddr.Type().String() != "*func(ctx context.Context, network string, addr string) (net.Conn, error)" { + continue + } + //refs to that field + refs = fieldAddr.Referrers() + for _, ref := range *refs { + store, ok = (ref).(*ssa.Store) + if !ok { + continue + } + closure, ok := store.Val.(*ssa.MakeClosure) + if !ok { + continue + } + for _, binding := range closure.Bindings { + transportAlloc, ok := binding.(*ssa.Alloc) + if !ok { + continue + } + + refs = transportAlloc.Referrers() + for _, ref := range *refs { + fieldAddr, ok = (ref).(*ssa.FieldAddr) + if !ok || fieldAddr.Type().String() != "*func(network string, address string, c syscall.RawConn) error" { + continue + } + refs = fieldAddr.Referrers() + for _, ref := range *refs { + store, ok = (ref).(*ssa.Store) + if !ok { + continue + } + if store.Val.String() != "nil:func(network string, address string, c syscall.RawConn) error" { + return false + } + } + } + } + } + } + } + } + } + + return true +} + +// ssrfRun() runs the command injection analyzer +func ssrfRun(pass *analysis.Pass) (interface{}, error) { + results := []util.Finding{} + + // Builds SSA model of Go code + ssaFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Creates call graph of function calls + cg := make(util.CallGraph) + + // Fills in call graph + for _, fn := range ssaFuncs { + cg.AnalyzeFunction(fn) + } + + // Grabs vulnerable functions to scan for + vuln_path_funcs := vulnSsrfFuncs() + + // Iterate over every specified vulnerable package + for pkg, funcs := range vuln_path_funcs { + + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + + // Construct full name of function + curFunc := pkg + "." + fn + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range cg[curFunc] { + // Check if argument of vulnerable function is tainted by possibly user-controlled input + taintAnalyzer := util.CreateTaintAnalyzer(pass, vulnFunc.Fn.Pos()) + //the first arg of any http client calls is the client itself we don't care about checking this for + //taint since it's not part of the SSRF however, if the control attribute is set on the client then we + //know it's safe, and we should never mark it as tainted (even if there is a get with user controlled + //input) + if pkg == "(*net/http.Client)" { + if taintCheck(&vulnFunc.Instr.Call.Args[0]) { + for i := 1; i < len(vulnFunc.Instr.Call.Args); i++ { + if taintAnalyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[i], cg) { + message := "Danger: possible SSRF detected" + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taintAnalyzer.TaintSource + results = append(results, util.MakeFinding(message, targetFunc, taintSource, "SSRF")) + + } + } + } + } else { + for i := 0; i < len(vulnFunc.Instr.Call.Args); i++ { + if taintAnalyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[i], cg) { + message := "Danger: possible SSRF detected" + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taintAnalyzer.TaintSource + results = append(results, util.MakeFinding(message, targetFunc, taintSource, "SSRF")) + + } + } + } + } + } + } + + return results, nil +} diff --git a/analyzers/ssrf_test.go b/analyzers/ssrf_test.go new file mode 100644 index 0000000..a09f2a6 --- /dev/null +++ b/analyzers/ssrf_test.go @@ -0,0 +1,53 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "testing" + + "github.com/praetorian-inc/gokart/test/testutil" +) + +func TestSSRF(t *testing.T) { + testFiles := []string{ + "ssrf.go", + "ssrf_alloc_taint.go", + "ssrf_client.go", + "ssrf_client_good.go", + "ssrf_good.go", + "ssrf_client_control.go", + "ssrf_struct.go", + } + + // Append directory to each entry + for i := 0; i < len(testFiles); i++ { + testFiles[i] = "ssrf/" + testFiles[i] + } + + testResults := []int{ + 1, + 1, + 1, + 0, + 0, + 0, + 1, + } + for i := 0; i < len(testFiles); i++ { + t.Run(testFiles[i], func(t *testing.T) { + testutil.RunTest(testFiles[i], testResults[i], "SSRF", SSRFAnalyzer, t) + }) + } +} diff --git a/analyzers/traversal.go b/analyzers/traversal.go new file mode 100644 index 0000000..941f8f9 --- /dev/null +++ b/analyzers/traversal.go @@ -0,0 +1,86 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "github.com/praetorian-inc/gokart/util" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" +) + +// PathTraversalAnalyzer constructs Sinks from a set of functions known to be vulnerable to path injection +// all variables are converted to SSA form and a call graph is contructed +// recursive taint analysis is then used to search from a given Sink up the callgraph for Sources of user-controllable data +var PathTraversalAnalyzer = &analysis.Analyzer{ + Name: "path_traversal", + Doc: "reports when path traversal can occur", + Run: traveralRun, + Requires: []*analysis.Analyzer{buildssa.Analyzer}, +} + +// getVulnerableInjectionFunctions() returns a map of functions that may be vulnerable to path traversal when used with user controlled input +func getVulnInjectionFunctions() map[string][]string { + return map[string][]string{ + "os": {"Create", "Open", "OpenFile"}, + "io/ioutil": {"ReadFile", "WriteFile"}, + } +} + +// traveralRun runs the path traversal analyzer +func traveralRun(pass *analysis.Pass) (interface{}, error) { + + results := []util.Finding{} + // Builds SSA model of Go code + ssaFuncs := pass.ResultOf[buildssa.Analyzer].(*buildssa.SSA).SrcFuncs + + // Creates call graph of function calls + cg := make(util.CallGraph) + + // Fills in call graph + for _, fn := range ssaFuncs { + cg.AnalyzeFunction(fn) + } + + // Grabs vulnerable functions to scan for + pathFuncs := getVulnInjectionFunctions() + + // Iterate over every specified vulnerable package + for pkg, funcs := range pathFuncs { + + // Iterate over every specified vulnerable function per package + for _, fn := range funcs { + + // Construct full name of function + curFunc := pkg + "." + fn + + // Iterate over occurences of vulnerable function in call graph + for _, vulnFunc := range cg[curFunc] { + + // Check if argument of vulnerable function is tainted by possibly user-controlled input + taintAnalyzer := util.CreateTaintAnalyzer(pass, vulnFunc.Fn.Pos()) + if taintAnalyzer.ContainsTaint(&vulnFunc.Instr.Call, &vulnFunc.Instr.Call.Args[0], cg) { + message := "Danger: possible path traversal injection detected" + + targetFunc := util.GenerateTaintedCode(pass, vulnFunc.Fn, vulnFunc.Instr.Pos()) + taintSource := taintAnalyzer.TaintSource + results = append(results, util.MakeFinding(message, targetFunc, taintSource, "Path Traversal")) + + } + } + } + } + + return results, nil +} diff --git a/analyzers/traversal_test.go b/analyzers/traversal_test.go new file mode 100644 index 0000000..2734b39 --- /dev/null +++ b/analyzers/traversal_test.go @@ -0,0 +1,57 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 analyzers + +import ( + "testing" + + "github.com/praetorian-inc/gokart/test/testutil" +) + +func TestPathTraversal(t *testing.T) { + testFiles := []string{ + "path1.go", + "path2.go", + "path3.go", + "path4.go", + "pathBin.go", + "pathBinPhi.go", + "pathMultiParams.go", + "pathPhi.go", + "pathReg.go", + } + + // Append directory to each entry + for i := 0; i < len(testFiles); i++ { + testFiles[i] = "path_traversal/" + testFiles[i] + } + + testResults := []int{ + 5, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + } + for i := 0; i < len(testFiles); i++ { + t.Run(testFiles[i], func(t *testing.T) { + testutil.RunTest(testFiles[i], testResults[i], "Path Traversal", PathTraversalAnalyzer, t) + }) + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..0765c1a --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,66 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 cmd + +import ( + "github.com/lithammer/dedent" + "github.com/spf13/cobra" +) + +var ( + goKartCmd = &cobra.Command{ + Use: "gokart scan", + Short: "A static analysis security scanner for Go", + Long: dedent.Dedent(` + ╭────────────────────────── GoKart ──────────────────────────╮ + │ │ + │ An open-source static analysis security scanner for Go │ + │ │ + │ https://github.com/praetorian-inc/gokart │ + │ │ + ╰────────────────────────────────────────────────────────────╯ + + + ╭────────────────────── Example usage ───────────────────────╮ + │ │ + │ Recursively scan current directory │ + │ ────────────────────────────────────────────────────── │ + │ $ gokart scan │ + │ │ + │ Scan specific directory │ + │ ────────────────────────────────────────────────────── │ + │ $ gokart scan │ + │ │ + │ Get info about flags │ + │ ────────────────────────────────────────────────────── │ + │ $ gokart scan -h │ + │ │ + ╰────────────────────────────────────────────────────────────╯ + + Please report any bugs or feature requests by opening a new + issue at https://github.com/praetorian-inc/gokart`), + SilenceErrors: true, + SilenceUsage: true, + } +) + +// Execute is a wrapper to call the GoKart root command +func Execute() error { + return goKartCmd.Execute() +} + +func init() { + goKartCmd.CompletionOptions.DisableDefaultCmd = true +} diff --git a/cmd/scan.go b/cmd/scan.go new file mode 100644 index 0000000..e8ae567 --- /dev/null +++ b/cmd/scan.go @@ -0,0 +1,55 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 cmd implements a simple command line interface using cobra +*/ +package cmd + +import ( + "github.com/praetorian-inc/gokart/analyzers" + "github.com/praetorian-inc/gokart/util" + "github.com/spf13/cobra" +) + +var yml string + +func init() { + goKartCmd.AddCommand(scanCmd) + scanCmd.Flags().BoolP("sarif", "s", false, "outputs findings in SARIF form") + scanCmd.Flags().BoolP("globalsTainted", "g", false, "marks global variables as dangerous") + scanCmd.Flags().BoolP("verbose", "v", false, "outputs full trace of taint analysis") + scanCmd.Flags().BoolP("debug", "d", false, "outputs debug logs") + scanCmd.Flags().StringVarP(&yml, "input", "i", "", "input path to custom yml file") + goKartCmd.MarkFlagRequired("scan") +} + +var scanCmd = &cobra.Command{ + Use: "scan [flags] [directory]", + Short: "Scans a Go module directory", + Long: ` +Scans a Go module directory. To scan the current directory recursively, use gokart scan. To scan a specific directory, use gokart scan .`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + // recursively scan the current directory if no arguments are passed in + args = append(args, "./...") + } + sarif, _ := cmd.Flags().GetBool("sarif") + globals, _ := cmd.Flags().GetBool("globalsTainted") + verbose, _ := cmd.Flags().GetBool("verbose") + debug, _ := cmd.Flags().GetBool("debug") + util.InitConfig(globals, sarif, verbose, debug, yml) + analyzers.Scan(args) + }, +} diff --git a/docs/EXTENDING_ANALYZERS.md b/docs/EXTENDING_ANALYZERS.md new file mode 100644 index 0000000..025df90 --- /dev/null +++ b/docs/EXTENDING_ANALYZERS.md @@ -0,0 +1,46 @@ +## Add New Vulnerabilities or Untrusted Input Sources +New and custom vulnerabilities or untrusted input sources can be added by editing [analyzers.yml](analyzers.yml). +Analyzers.yml is organized into two sections: analyzers and sources. + +1) Analyzers: Analyzers describe a specific vulnerability type (like command injection). + They configure which functions to look for to identify the vulnerability, the message to be displayed when + the vulnerability is found, a description of the vulnerability, and the vulnerability name displayed when the vulnerability is found. When an analyzer is added, GoKart will look for any functions specified as vulnerable. If a function listed is found, GoKart will trace the input back to the source. If the source is determined to be untrusted (as defined in the sources section of the yml), GoKart will output the vulnerability. The template to add a new vulnerability scanner looks like this: + + ``` + analyzers: + "vulnerability1 name to be printed out": + doc: "vulnerability1 description" + message: "vulnerability1 message to be printed out" + vuln_calls: + "vulnerable functions package1": + - "vulnerable function1 name" + - "vulnerable function2 name" + "vulnerable functions package2": + ... + "vulnerability2 name to be printed out": + doc: "vulnerability2 description" + ... + ``` + + Here is an example of a vulnerability that would go in the "analyzers" section: + + ``` + "Command Injection": + doc: "OS Command Injection" + message: "Danger: possible command injection detected" + vuln_calls: + "os/exec": + - "Command" + - "CommandContext" + ``` + + To add a new vulnerability scanner (an analyzer), add a correctly formatted vulnerability with all fields filled out to the "analyzers" section. Make sure all indents are correct (use current vulnerabilities in the yml for reference on proper indentation). Note that not all vulnerabilities are able to be configured in the yml. Take RSA Keylength Checking for example- to test if an RSA key is too short, mathematical calculations often have to be performed. Vulnerabilities like these that require more than identifying functions and tracing back the input will have to be manually configured by adding an analyzer in the "analyzers" folder (not in the yml). The yml is for adding vulnerabilities that (1) only require identifying vulnerable functions and (2) are only dangerous if they take untrusted input. + +2) Sources: Sources describe a source that should be considered untrusted. An untrusted source is a source that may contain user input and thus cannot be trusted to be used in a potentially vulnerable function. For example, the potentially vulnerable function `DB.Query(params)` is considered vulnerable and will be output as a vulnerability by GoKart if `params` can be traced back to an untrusted input source. GoKart identifies whether or not a source is untrusted by looking through the sources listed in the yml. Sources can be variables, functions, or types. To add a source that should be considered untrusted, go to the correct category and add these lines: + + ``` + "package name": + - "variable/function/type name" + ``` + + If the package already exists in the sources section, add the variable/function/type underneath that package as each package can contain multiple vulnerable sources. \ No newline at end of file diff --git a/docs/img/logo.png b/docs/img/logo.png new file mode 100644 index 0000000..bb7bcaf Binary files /dev/null and b/docs/img/logo.png differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5acc55a --- /dev/null +++ b/go.mod @@ -0,0 +1,14 @@ +module github.com/praetorian-inc/gokart + +go 1.16 + +require ( + github.com/fatih/color v1.12.0 + github.com/lithammer/dedent v1.1.0 + github.com/owenrumney/go-sarif v1.0.11 + github.com/segmentio/fasthash v1.0.3 + github.com/spf13/cobra v1.2.1 + golang.org/x/text v0.3.6 // indirect + golang.org/x/tools v0.1.2 + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..98fbd6d --- /dev/null +++ b/go.sum @@ -0,0 +1,598 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY= +github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/owenrumney/go-sarif v1.0.11 h1:7k4TLSi6h3vAozSECjO0arcQoeUNDMgvA7LDac95sJo= +github.com/owenrumney/go-sarif v1.0.11/go.mod h1:hTBFbxU7GuVRUvwMx+eStp9M/Oun4xHCS3vqpPvket8= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM= +github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.2.1 h1:+KmjbUw1hriSNMF55oPrkZcb27aECyrj8V2ytv7kWDw= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/zclconf/go-cty v1.8.4 h1:pwhhz5P+Fjxse7S7UriBrMu6AUJSZM5pKqGem1PjGAs= +github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1eb7d4c --- /dev/null +++ b/main.go @@ -0,0 +1,46 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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. + +/* + +GoKart is a scanner for go applications + +Run it in the go module directory of the module you want to scan with gokart scan + +GoKart is split up into a series of analyzers that each look for a specific vulnerability class. These are contained in +the `gokart/analyzers` package. + +GoKart uses SSA to track the sources of data, to perform taint analysis. This means that GoKart can track how data flows +through an application, to remove false positives from data that comes from a trusted source +*/ + +package main + +import ( + "flag" + "fmt" + + "github.com/praetorian-inc/gokart/cmd" +) + +func main() { + cmd.Execute() + flag.Parse() // get the arguments from command line + + // if there is a first argument that is not scan, print out an error message + arg := flag.Arg(0) + if arg != "scan" && arg != "" && arg != "help" { + fmt.Printf("\nGoKart is fishtailing! Make sure to use \"gokart scan\" as the beginning of the command to steer GoKart in the right direction.\n\n") + } +} diff --git a/run/run.go b/run/run.go new file mode 100755 index 0000000..ecf25a1 --- /dev/null +++ b/run/run.go @@ -0,0 +1,192 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 run controls the loading of go code and the running of analyzers. +*/ +package run + +import ( + "fmt" + "go/token" + + "github.com/praetorian-inc/gokart/util" + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/analysis/passes/buildssa" + "golang.org/x/tools/go/packages" +) + +// Load go packages and run the analyzers on them. Returns a list of findings +func Run(analyzers []*analysis.Analyzer, packages ...string) ([]util.Finding, bool, error) { + + pkgs, success, err := LoadPackages(packages...) + if err != nil { + return nil, false, err + } + + results := []util.Finding{} + for _, pkg := range pkgs { + result, err := RunAnalyzers(analyzers, pkg) + if err != nil { + return nil, false, err + } + results = append(results, result...) + } + + return results, success, nil + +} + +// Load go packages +func LoadPackages(packagesList ...string) ([]*packages.Package, bool, error) { + success := true + hadBadpkgs := false + conf := packages.Config{ + Mode: packages.LoadSyntax, + //Disable loading tests. If we enable this, then packages will be loaded twice. Once with tests, once without. + //This causes us to report findings twice, even if there are no tests in the package + Tests: false, + } + + //Load all packages that have been configured to be scanned, watch out for memory errors + pkgs, err := packages.Load(&conf, packagesList...) + + if err != nil { + return nil, false, err + } + // Detect any packages that are unable to be scanned due to compilation or accessibility errors + var badpkgs []*packages.Package + packages.Visit(pkgs, nil, func(pkg *packages.Package) { + for range pkg.Errors { + badpkgs = append(badpkgs, pkg) + break + } + }) + // Print error message if a package was unable to be loaded + if len(badpkgs) > 0 { + fmt.Printf("\nUh oh, a dashboard light is on! GoKart was unable to load the following packages: \n") + hadBadpkgs = true + } + + for _, v := range badpkgs { + pkgs = RemoveItem(v, pkgs) + } + // Only print separator if we've found removed bad packages + if hadBadpkgs { + fmt.Printf("\n\n") + } + // Print error mssage if no scannable packages are found + if len(pkgs) == 0 { + fmt.Printf("CRASH! GoKart didn't find any files to scan! Make sure the usage is correct to get GoKart back on track. \n") + success = false + } + return pkgs, success, nil +} + +// Remove bad packages from the list of packages to be scanned +func RemoveItem(pkg *packages.Package, pkglist []*packages.Package) []*packages.Package { + for x, val := range pkglist { + if pkg == val { + if util.Config.Debug { + fmt.Printf("\"%s\" with errors:\n", pkg.Name) + } else { + fmt.Printf("- \"%s\"\n", pkg.PkgPath) + } + + if util.Config.Debug { + for _, pkgError := range pkg.Errors { + fmt.Printf("- %s\n", pkgError.Error()) + } + } + if len(pkglist) < 2 { + return pkglist[0:0] + } + pkglist[x] = pkglist[len(pkglist)-1] + return pkglist[0 : len(pkglist)-2] + } + } + return pkglist +} + +// Run analyzers on a package +func RunAnalyzers(analyzers []*analysis.Analyzer, pkg *packages.Package) ([]util.Finding, error) { + //run ssa first since the other analyzers require it + + ssaPass := &analysis.Pass{ + Analyzer: buildssa.Analyzer, + Fset: pkg.Fset, + Files: pkg.Syntax, + OtherFiles: pkg.OtherFiles, + IgnoredFiles: pkg.IgnoredFiles, + Pkg: pkg.Types, + TypesInfo: pkg.TypesInfo, + TypesSizes: pkg.TypesSizes, + ResultOf: nil, + Report: nil, + ImportObjectFact: nil, + ExportObjectFact: nil, + ImportPackageFact: nil, + ExportPackageFact: nil, + AllObjectFacts: nil, + AllPackageFacts: nil, + } + + ssaResult, err := ssaPass.Analyzer.Run(ssaPass) + if err != nil { + return nil, err + } + + //feed the results of ssa into the other analyzers + resultMap := make(map[*analysis.Analyzer]interface{}) + resultMap[buildssa.Analyzer] = ssaResult + + results := []util.Finding{} + + // Calculate number of Go files parsed + full_size := 0 + pkg.Fset.Iterate( + func(f *token.File) bool { + full_size += 1 + return true + }) + util.FilesFound = full_size + + for _, analyzer := range analyzers { + //run the analyzer + pass := &analysis.Pass{ + Analyzer: analyzer, + Fset: pkg.Fset, + Files: pkg.Syntax, + OtherFiles: pkg.OtherFiles, + IgnoredFiles: pkg.IgnoredFiles, + Pkg: pkg.Types, + TypesInfo: pkg.TypesInfo, + TypesSizes: pkg.TypesSizes, + ResultOf: resultMap, + Report: func(d analysis.Diagnostic) {}, + ImportObjectFact: nil, + ExportObjectFact: nil, + ImportPackageFact: nil, + ExportPackageFact: nil, + AllObjectFacts: nil, + AllPackageFacts: nil, + } + result, err := pass.Analyzer.Run(pass) + if err != nil { + return nil, err + } + results = append(results, (result.([]util.Finding))...) + } + return results, nil +} diff --git a/test/testdata/vulnerablemodule/combined/serverTest.go b/test/testdata/vulnerablemodule/combined/serverTest.go new file mode 100644 index 0000000..b06c9fa --- /dev/null +++ b/test/testdata/vulnerablemodule/combined/serverTest.go @@ -0,0 +1,129 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "crypto/rand" + "crypto/rsa" + "database/sql" + "log" + "net/http" + "os" + "os/exec" +) + +var ( + db5 *sql.DB + globalTest int = 500 +) + +func main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} + +func handler(w http.ResponseWriter, r *http.Request) { + + keys, ok := r.URL.Query()["key"] + + if !ok || len(keys[0]) < 1 { + log.Println("Url Param 'key' is missing") + return + } + + // Query()["key"] will return an array of items, + // we only want the single item. + + // path traversal + key := keys[0] + f, _ := os.Create(key) + f.Close() + log.Println("Url Param 'key' is: " + string(key)) + os.Open(key) + + // sql injection + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + db5.Query("SELECT name FROM users WHERE username=" + hiddenMessage) + db5.Query("SELECT name FROM users WHERE username=" + key) + + // command injection + if key == "safe" { + key = "safe" + } + cmd := exec.Command("echo", key, "Yes it is.") + cmd.Run() + + // RSA + rsa.GenerateKey(rand.Reader, 1050) + + //testing global variable + rsa.GenerateKey(rand.Reader, globalTest) + + //from here down should only be false positives + + //directory trav safe scenarios + tmp := "./list1/images/" + x := 5 + if x != 5 { + tmp = "./etc/passwd" + } + DirTraversal6(tmp) + os.Open(temp8(key)) //another false negative here by gosec!! + + // rsa safe scenario + tmp2 := 200 + tmp3 := 1000 + keylenMultParam(tmp2, tmp3) + + //sql safe scenarios + db5.Query("SELECT name FROM users WHERE username=" + temp7()) + db5.Query("SELECT name FROM users WHERE username=" + "dummy") + + //command injection safe scenarios + msg := "This echo is safe." + exec.Command("echo", msg, "Yes it is.") + exec.Command("echo", temp7(), "Yes it is.") + +} + +func keylenMultParam(tmp2 int, tmp3 int) { + rsa.GenerateKey(rand.Reader, tmp2+tmp3) //gosec false negative?? +} + +func DirTraversal6(folderPath string) { + f2, _ := os.Create(folderPath) + os.Open(folderPath) + x5 := "safe" + num := 1000 + cmdInjection(x5, num) + f2.Close() +} + +func temp7() string { + return "a" +} +func temp8(key string) string { + return key +} + +func cmdInjection(x string, num int) { + cmd := exec.Command(x) + cmd.Run() + rsa.GenerateKey(rand.Reader, num+50) // false negative in gosec??? Look into this + db5.Query("SELECT name FROM users WHERE username=" + x) + +} diff --git a/test/testdata/vulnerablemodule/command_injection/commandContext.go b/test/testdata/vulnerablemodule/command_injection/commandContext.go new file mode 100644 index 0000000..5084be3 --- /dev/null +++ b/test/testdata/vulnerablemodule/command_injection/commandContext.go @@ -0,0 +1,39 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "context" + "os" + "os/exec" + "time" +) + +func main8() { + reader1 := bufio.NewReader(os.Stdin) + reader2 := bufio.NewReader(os.Stdin) + + text1, _ := reader1.ReadString('\n') + text2, _ := reader2.ReadString('\n') + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + if err := exec.CommandContext(ctx, text1, text2).Run(); err != nil { + // This will fail after 100 milliseconds. The 5 second sleep + // will be interrupted. + } +} diff --git a/test/testdata/vulnerablemodule/command_injection/command_context_injection_safe.go b/test/testdata/vulnerablemodule/command_injection/command_context_injection_safe.go new file mode 100644 index 0000000..793626c --- /dev/null +++ b/test/testdata/vulnerablemodule/command_injection/command_context_injection_safe.go @@ -0,0 +1,48 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "context" + "fmt" + "os/exec" + "time" +) + +func main() { + // Create a new context and add a timeout to it + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() // The cancel should be deferred so resources are cleaned up + + // Create the command with our context + cmd := exec.CommandContext(ctx, "ping", "-c 4", "-i 1", "8.8.8.8") + + // This time we can simply use Output() to get the result. + out, err := cmd.Output() + + // We want to check the context error to see if the timeout was executed. + // The error returned by cmd.Output() will be OS specific based on what + // happens when a process is killed. + if ctx.Err() == context.DeadlineExceeded { + fmt.Println("Command timed out") + return + } + + // If there's no context error, we know the command completed (or errored). + fmt.Println("Output:", string(out)) + if err != nil { + fmt.Println("Non-zero exit code:", err) + } +} diff --git a/test/testdata/vulnerablemodule/command_injection/command_injection.go b/test/testdata/vulnerablemodule/command_injection/command_injection.go new file mode 100644 index 0000000..1bd4a2c --- /dev/null +++ b/test/testdata/vulnerablemodule/command_injection/command_injection.go @@ -0,0 +1,65 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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. + +// 2 vulnerabilities should be reported, on lines 28, 34 +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" +) + +func get_user_input() string { + reader := bufio.NewReader(os.Stdin) + fmt.Print("Enter command: ") + text, _ := reader.ReadString('\n') + return text +} + +func constant_func() string { + return "test1" + "test2" +} + +func no_command_injection() { + msg := "This echo is safe." + cmd := exec.Command("echo", msg, "Yes it is.") + cmd.Run() +} + +func command_injection_user_input() { + text := get_user_input() + cmd := exec.Command("sh", "echo", "hi", "&&", text) + cmd.Run() +} + +func command_injection_firstarg() { + msg := get_user_input() + cmd := exec.Command(msg, "echo", "hi") + cmd.Run() +} + +func no_command_injection_function() { + msg := constant_func() + cmd := exec.Command("sh", "echo", "hi", "&&", msg) + cmd.Run() +} + +func command_injection_main() { + no_command_injection() + command_injection_user_input() + command_injection_firstarg() + no_command_injection_function() +} diff --git a/test/testdata/vulnerablemodule/go.mod b/test/testdata/vulnerablemodule/go.mod new file mode 100644 index 0000000..938a377 --- /dev/null +++ b/test/testdata/vulnerablemodule/go.mod @@ -0,0 +1,3 @@ +module github.com/praetorian-inc/gokart/test/testdata/vulnerablemodule + +go 1.16 diff --git a/test/testdata/vulnerablemodule/old_tests/gosecFalsePositive2.go b/test/testdata/vulnerablemodule/old_tests/gosecFalsePositive2.go new file mode 100644 index 0000000..a20a7f5 --- /dev/null +++ b/test/testdata/vulnerablemodule/old_tests/gosecFalsePositive2.go @@ -0,0 +1,29 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +func rsaGenerator() { + key_length := 100 + rsaHelper(key_length) +} + +func rsaHelper(length int) { + rsa.GenerateKey(rand.Reader, length) +} diff --git a/test/testdata/vulnerablemodule/old_tests/rsaBin.go b/test/testdata/vulnerablemodule/old_tests/rsaBin.go new file mode 100644 index 0000000..59a49d0 --- /dev/null +++ b/test/testdata/vulnerablemodule/old_tests/rsaBin.go @@ -0,0 +1,32 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file +func bin() int { + tmp2 := 2045 + keylenBin(tmp2) + return tmp2 +} + +func keylenBin(tmp2 int) { + + rsa.GenerateKey(rand.Reader, tmp2+1) +} diff --git a/test/testdata/vulnerablemodule/old_tests/rsaBinPhi.go b/test/testdata/vulnerablemodule/old_tests/rsaBinPhi.go new file mode 100644 index 0000000..d87f468 --- /dev/null +++ b/test/testdata/vulnerablemodule/old_tests/rsaBinPhi.go @@ -0,0 +1,36 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file +func binPhi() int { + tmp2 := 2040 + tmp3 := 6 + if tmp2 == 2040 { + tmp3 = 50 + } + keylenBinPhi(tmp2, tmp3) + return tmp2 +} + +func keylenBinPhi(tmp2 int, tmp3 int) { + + rsa.GenerateKey(rand.Reader, tmp3+tmp2+1) +} diff --git a/test/testdata/vulnerablemodule/path_traversal/gosecFalsePositive1.go b/test/testdata/vulnerablemodule/path_traversal/gosecFalsePositive1.go new file mode 100644 index 0000000..9886fd1 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/gosecFalsePositive1.go @@ -0,0 +1,37 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +import ( + "net/http" + "os" +) + +func gosecFalsePositive1Main() { + http.HandleFunc("/", handler) + http.ListenAndServe(":8080", nil) +} + +func handler(w http.ResponseWriter, r *http.Request) { + + keys := r.URL.Query()["key"] + user_input := keys[0] + + os.Open(temp8(user_input)) +} + +func temp8(str string) string { + return str +} diff --git a/test/testdata/vulnerablemodule/path_traversal/path1.go b/test/testdata/vulnerablemodule/path_traversal/path1.go new file mode 100644 index 0000000..41846c0 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/path1.go @@ -0,0 +1,42 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// Expect 7 vulnerabilities found + +import ( + "bufio" + "io/ioutil" + "os" + "path" + "path/filepath" +) + +func DirTraversal1(filename string, folderPath string) { + os.Create(folderPath + filename) + os.Open(folderPath + filename) + os.OpenFile(folderPath+filename, os.O_RDONLY, 0775) + ioutil.ReadFile(folderPath + filename) + ioutil.WriteFile(folderPath+filename, []byte("Hello, Gophers!"), 0775) + path.Join(folderPath, filename) + filepath.Join(folderPath, filename) +} + +func tempTest() { + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + hiddenMessage2 := "hidden2" + DirTraversal1(hiddenMessage, hiddenMessage2) +} diff --git a/test/testdata/vulnerablemodule/path_traversal/path2.go b/test/testdata/vulnerablemodule/path_traversal/path2.go new file mode 100644 index 0000000..25c3ba0 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/path2.go @@ -0,0 +1,34 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +import ( + "bufio" + "os" +) + +func getVuln() string { + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + return hiddenMessage +} + +func DirTraversal2(filename string) string { + folderPath := "./list1/images/" + // f, _ := os.Create(folderPath + filename + getVuln()) + f, _ := os.Create(folderPath + getVuln()) + f.Close() + return folderPath +} diff --git a/test/testdata/vulnerablemodule/path_traversal/path3.go b/test/testdata/vulnerablemodule/path_traversal/path3.go new file mode 100644 index 0000000..9ce0b93 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/path3.go @@ -0,0 +1,29 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +import ( + "os" +) + +func getFile1() string { + return "hacked" +} + +func DirTraversal3() { + folderPath := "./list1/images/" + f, _ := os.Create(folderPath + getFile1()) + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/path4.go b/test/testdata/vulnerablemodule/path_traversal/path4.go new file mode 100644 index 0000000..3ccd2c7 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/path4.go @@ -0,0 +1,30 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +import ( + "os" +) + +func getFile2(temp string) string { + return temp +} + +func DirTraversal4() { + safe := "hello" + folderPath := "./list1/images/" + f, _ := os.Create(folderPath + getFile2(safe)) + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/pathBin.go b/test/testdata/vulnerablemodule/path_traversal/pathBin.go new file mode 100644 index 0000000..aebf7b5 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/pathBin.go @@ -0,0 +1,31 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// zero vulns should be reported +import ( + "os" +) + +func getFile3(temp string) string { + tmp := "./list1/images/" + DirTraversal5(tmp) + return "str" +} + +func DirTraversal5(folderPath string) { + f, _ := os.Create(folderPath + "test") + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/pathBinPhi.go b/test/testdata/vulnerablemodule/path_traversal/pathBinPhi.go new file mode 100644 index 0000000..a6e8375 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/pathBinPhi.go @@ -0,0 +1,36 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// zero vulns should be reported + +import ( + "os" +) + +func getFile4(temp string) string { + tmp := "./list1/images/" + x := 5 + if x != 5 { + tmp = "./etc/passwd" + } + DirTraversal6(tmp) + return "str" +} + +func DirTraversal6(folderPath string) { + f, _ := os.Create(folderPath) + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/pathMultParams.go b/test/testdata/vulnerablemodule/path_traversal/pathMultParams.go new file mode 100644 index 0000000..b5e61b1 --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/pathMultParams.go @@ -0,0 +1,32 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// zero vulns should be reported + +import ( + "os" +) + +func getFile6(temp string) string { + tmp := "./list1/images/" + DirTraversal8(tmp, "test") + return "str" +} + +func DirTraversal8(folderPath string, path2 string) { + f, _ := os.Create(folderPath + path2 + "so" + "many" + "params!") + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/pathPhi.go b/test/testdata/vulnerablemodule/path_traversal/pathPhi.go new file mode 100644 index 0000000..2e1545d --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/pathPhi.go @@ -0,0 +1,32 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// zero vulns should be reported + +import ( + "os" +) + +func getFilePhi(temp string) string { + return temp +} + +func DirTraversalPhi() { + safe := "hello" + folderPath := "./list1/images/" + f, _ := os.Create(folderPath + getFilePhi(safe)) + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/pathReg.go b/test/testdata/vulnerablemodule/path_traversal/pathReg.go new file mode 100644 index 0000000..979754b --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/pathReg.go @@ -0,0 +1,32 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// zero vulns should be reported + +import ( + "os" +) + +func getFile5(temp string) string { + tmp := "./list1/images/" + DirTraversal7(tmp) + return "str" +} + +func DirTraversal7(folderPath string) { + f, _ := os.Create(folderPath) + f.Close() +} diff --git a/test/testdata/vulnerablemodule/path_traversal/twitterTest.go b/test/testdata/vulnerablemodule/path_traversal/twitterTest.go new file mode 100644 index 0000000..3fd766c --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/twitterTest.go @@ -0,0 +1,36 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +import ( + "net/http" + "path/filepath" +) + +func twitterMain() { + http.HandleFunc("/", DirPathTraversalHandler) + http.ListenAndServe(":8080", nil) +} + +func DirPathTraversalHandler(w http.ResponseWriter, r *http.Request) { + var pwd string + params := r.URL.Query() + appName := params.Get(":app") + dirFile(filepath.Join(pwd, "/admin/builds/"+appName+"-build/static/"), "/admin/"+appName+"/static/", r.URL.Path) + //serveFile(w, r, dir, file) +} + +func dirFile(test string, test2 string, test3 string) { +} diff --git a/test/testdata/vulnerablemodule/path_traversal/unop_test.go b/test/testdata/vulnerablemodule/path_traversal/unop_test.go new file mode 100644 index 0000000..31a1b5d --- /dev/null +++ b/test/testdata/vulnerablemodule/path_traversal/unop_test.go @@ -0,0 +1,37 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 path_traversal + +// Should print 1 vulnerability + +import ( + "bufio" + "os" +) + +func unop_novuln() { + hiddenMessage := "constt" + tester := hiddenMessage + a := &tester + os.Open(*a) +} + +func unop_vuln() { + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + tester := hiddenMessage + a := &tester + os.Open(*a) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_bad.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_bad.go new file mode 100644 index 0000000..7290280 --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_bad.go @@ -0,0 +1,24 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +func rsa_main() { + rsa.GenerateKey(rand.Reader, 100) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_good.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_good.go new file mode 100644 index 0000000..013128b --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_direct_good.go @@ -0,0 +1,24 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +func rsa_main() { + rsa.GenerateKey(rand.Reader, 2048) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_bad.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_bad.go new file mode 100644 index 0000000..b998560 --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_bad.go @@ -0,0 +1,29 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +func key_len() int { + return 1024 +} + +func rsa_main() { + keylen := key_len() + rsa.GenerateKey(rand.Reader, keylen+1) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_good.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_good.go new file mode 100644 index 0000000..7351c75 --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_indirect_good.go @@ -0,0 +1,29 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +func key_len() int { + return 2048 +} + +func rsa_main() { + keylen := key_len() + rsa.GenerateKey(rand.Reader, keylen+1) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_issues.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_issues.go new file mode 100644 index 0000000..5c44d2d --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_issues.go @@ -0,0 +1,40 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be two insufficient key length in this file + +func big() int { + tmp2 := 2040 + tmp3 := 5 + keylenBig(tmp2, tmp3) + return tmp2 +} + +func keylenBig(tmp2 int, tmp3 int) { + keylenBig2(tmp2) + rsa.GenerateKey(rand.Reader, tmp2+100) + rsa.GenerateKey(rand.Reader, tmp3+tmp2) +} + +func keylenBig2(tmp2 int) { + + rsa.GenerateKey(rand.Reader, tmp2) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_bad.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_bad.go new file mode 100644 index 0000000..cd0c5be --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_bad.go @@ -0,0 +1,34 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file + +func multipleParams() int { + tmp2 := 500 + tmp3 := 1000 + keylenMultParam(tmp2, tmp3) + return tmp2 +} + +func keylenMultParam(tmp2 int, tmp3 int) { + + rsa.GenerateKey(rand.Reader, tmp2+tmp3) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_good.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_good.go new file mode 100644 index 0000000..62b3a69 --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_multiple_params_good.go @@ -0,0 +1,34 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file + +func multipleParams() int { + tmp2 := 1024 + tmp3 := 1024 + keylenMultParam(tmp2, tmp3) + return tmp2 +} + +func keylenMultParam(tmp2 int, tmp3 int) { + + rsa.GenerateKey(rand.Reader, tmp2+tmp3) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_bad.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_bad.go new file mode 100644 index 0000000..b4bbd9b --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_bad.go @@ -0,0 +1,36 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file +func phi() int { + tmp2 := 2040 + tmp3 := 3000 + if tmp2 == 2040 { + tmp3 = 50 + } + keylenPhi(tmp2, tmp3) + return tmp2 +} + +func keylenPhi(tmp2 int, tmp3 int) { + + rsa.GenerateKey(rand.Reader, tmp3) +} diff --git a/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_good.go b/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_good.go new file mode 100644 index 0000000..2983e2d --- /dev/null +++ b/test/testdata/vulnerablemodule/rsa_keylength/rsa_phi_good.go @@ -0,0 +1,36 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "crypto/rand" + "crypto/rsa" +) + +//There should be one insufficient key length in this file +func phi() int { + tmp2 := 2040 + tmp3 := 3000 + if tmp2 == 2040 { + tmp3 = 5000 + } + keylenPhi(tmp2, tmp3) + return tmp2 +} + +func keylenPhi(tmp2 int, tmp3 int) { + + rsa.GenerateKey(rand.Reader, tmp3) +} diff --git a/test/testdata/vulnerablemodule/sql_injection/SQLfp.go b/test/testdata/vulnerablemodule/sql_injection/SQLfp.go new file mode 100644 index 0000000..b5c03a6 --- /dev/null +++ b/test/testdata/vulnerablemodule/sql_injection/SQLfp.go @@ -0,0 +1,47 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "database/sql" + "net/http" +) + +var ( + db *sql.DB +) + +func executeQuery(query string) { + db.Query(query) +} + +func formatQuery(field string, table string, user string) string { + return "SELECT " + field + " FROM " + table + " WHERE username=" + user +} + +func HTTPHandler(w http.ResponseWriter, r *http.Request) { + FIELD := "name" + TABLE := "users" + + response := r.URL.Query()["users"] + user := response[0] + + executeQuery(formatQuery(FIELD, TABLE, user)) +} + +func main2() { + http.HandleFunc("/", HTTPHandler) + http.ListenAndServe(":8080", nil) +} diff --git a/test/testdata/vulnerablemodule/sql_injection/sql1.go b/test/testdata/vulnerablemodule/sql_injection/sql1.go new file mode 100644 index 0000000..3f65c61 --- /dev/null +++ b/test/testdata/vulnerablemodule/sql_injection/sql1.go @@ -0,0 +1,51 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 + +/* +* There should be 2 vulnerability + */ + +import ( + "bufio" + "context" + "database/sql" + "fmt" + "os" +) + +var ( + ctx context.Context + + db1 *sql.DB +) + +func temp() string { + return "a" +} +func query(x string) { + a := "asdf" + db1.Query("SELECT name FROM users WHERE username=" + a) + db1.Query("SELECT name FROM users WHERE username=" + temp()) + db1.Query("SELECT name FROM users WHERE username=" + x) + db1.Query(fmt.Sprintf("SELECT name FROM users WHERE username= %s", a)) + db1.Query(fmt.Sprintf("SELECT name FROM users WHERE username= %s", x)) +} + +func Sqlmain() { + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + query(hiddenMessage + "abc") +} diff --git a/test/testdata/vulnerablemodule/sql_injection/sql2.go b/test/testdata/vulnerablemodule/sql_injection/sql2.go new file mode 100644 index 0000000..336c31c --- /dev/null +++ b/test/testdata/vulnerablemodule/sql_injection/sql2.go @@ -0,0 +1,70 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 + +/* +* There should be no vulnerabilities + */ + +import ( + "context" + "database/sql" + "fmt" + "strconv" +) + +var ( + ctx2 context.Context + db2 *sql.DB +) + +func getSalt() string { + return "5678" +} + +func makequery(input string) { + db2.Query("SELECT name FROM users WHERE username=" + input) +} + +func makequery2(input string) { + db2.Query("SELECT name FROM users WHERE username=" + input) +} + +func makequery3(s string, i int) { + db2.Query(fmt.Sprintf("SELECT name FROM users WHERE username= %s", s)) + db2.Query(fmt.Sprintf("SELECT name FROM users WHERE username=admin OR 1=%d", i)) + converted := strconv.Itoa(i) + db2.Query("SELECT name FROM users WHERE username=admin OR 1=" + converted) +} + +func middleman(hiddenMessage string) { + salt := "1234" + makequery(hiddenMessage + salt) +} + +func middleman2(hiddenMessage string) { + makequery2(hiddenMessage + getSalt()) +} + +func middleman3(hiddenMessage string, secretMessage int) { + makequery3(hiddenMessage, secretMessage+5) +} + +func toplevel() { + hiddenMessage := "This is safe" + middleman(hiddenMessage) + middleman2(hiddenMessage) + middleman3(hiddenMessage, 5) +} diff --git a/test/testdata/vulnerablemodule/sql_injection/sql3.go b/test/testdata/vulnerablemodule/sql_injection/sql3.go new file mode 100644 index 0000000..a25954b --- /dev/null +++ b/test/testdata/vulnerablemodule/sql_injection/sql3.go @@ -0,0 +1,78 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 + +/* +* There should be 5 vulnerabilities + */ + +import ( + "bufio" + "database/sql" + "fmt" + "os" + "strconv" +) + +var ( + db4 *sql.DB +) + +func three_getSalt() string { + return "5678" +} + +func three_makequery(input string) { + db4.Query("SELECT name FROM users WHERE username=" + input) +} + +func three_makequery2(input string) { + db4.Query("SELECT name FROM users WHERE username=" + input) +} + +func three_makequery3(s string, i int) { + db4.Query(fmt.Sprintf("SELECT name FROM users WHERE username= %s", s)) + db4.Query(fmt.Sprintf("SELECT name FROM users WHERE username=admin OR 1=%d", i)) + converted := strconv.Itoa(i) + db4.Query("SELECT name FROM users WHERE username=admin OR 1=" + converted) +} + +func three_middleman(hiddenMessage string) { + salt := "1234" + three_makequery(hiddenMessage + salt) +} + +func three_middleman2(hiddenMessage string) { + three_makequery2(hiddenMessage + three_getSalt()) +} + +func three_middleman3(hiddenMessage string, secretMessage int) { + three_makequery3(hiddenMessage, secretMessage+5) +} + +func three_toplevel(unsafe int) { + reader := bufio.NewReader(os.Stdin) + hiddenMessage, _ := reader.ReadString('\n') + three_middleman(hiddenMessage) + three_middleman2(hiddenMessage) + three_middleman3(hiddenMessage, unsafe) +} + +func three_sqlmain() { + reader := bufio.NewReader(os.Stdin) + str, _ := reader.ReadString('\n') + hiddenInt, _ := strconv.Atoi(str) + three_toplevel(hiddenInt) +} diff --git a/test/testdata/vulnerablemodule/sql_injection/sql4_formatstr.go b/test/testdata/vulnerablemodule/sql_injection/sql4_formatstr.go new file mode 100644 index 0000000..5157807 --- /dev/null +++ b/test/testdata/vulnerablemodule/sql_injection/sql4_formatstr.go @@ -0,0 +1,51 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 + +/* +* There should be 2 vulnerabilities + */ + +import ( + "bufio" + "context" + "database/sql" + "fmt" + "os" + "strconv" +) + +var ( + ctx3 context.Context + db3 *sql.DB +) + +func four_makequery3(s string, i int) { + //db3.Query(fmt.Sprintf("SELECT name FROM users WHERE username= %s", s)) + //db3.Query(fmt.Sprintf("SELECT name FROM users WHERE username=admin OR 1=%d", i)) + //converted := strconv.Itoa(i) + //db3.Query("SELECT name FROM users WHERE username=admin OR 1=" + converted) + db3.Query(fmt.Sprintf("SELECT 1 = %s", s)) + db3.Query(fmt.Sprintf("SELECT 2=%d", i)) + converted := strconv.Itoa(i) + db3.Query("SELECT 3=" + converted) +} + +func four_sqlmain() { + reader := bufio.NewReader(os.Stdin) + str, _ := reader.ReadString('\n') + hiddenInt, _ := strconv.Atoi(str) + four_makequery3("string", hiddenInt) +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf.go b/test/testdata/vulnerablemodule/ssrf/ssrf.go new file mode 100644 index 0000000..4715358 --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf.go @@ -0,0 +1,27 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "net/http" + "os" +) + +func main() { + reader := bufio.NewReader(os.Stdin) + untrusted, _ := reader.ReadString('\n') + _, _ = http.Get(untrusted) +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_alloc_taint.go b/test/testdata/vulnerablemodule/ssrf/ssrf_alloc_taint.go new file mode 100644 index 0000000..d9de68f --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_alloc_taint.go @@ -0,0 +1,30 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "net/http" + "os" +) + +func main() { + buf := make([]byte, 20) + reader := bufio.NewReader(os.Stdin) + untrusted, _ := reader.ReadString('\n') + copy(buf, []byte(untrusted)) + //buf[0] = 3 + _, _ = http.Get(string(buf)) +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_client.go b/test/testdata/vulnerablemodule/ssrf/ssrf_client.go new file mode 100644 index 0000000..f34110c --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_client.go @@ -0,0 +1,50 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "net" + "net/http" + "os" + "time" +) + +func main() { + safeDialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + Control: nil, + } + + safeTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: safeDialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + safeClient := &http.Client{ + Transport: safeTransport, + } + + reader := bufio.NewReader(os.Stdin) + untrusted, _ := reader.ReadString('\n') + _, _ = safeClient.Get(untrusted) +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_client_control.go b/test/testdata/vulnerablemodule/ssrf/ssrf_client_control.go new file mode 100644 index 0000000..2120093 --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_client_control.go @@ -0,0 +1,55 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "net" + "net/http" + "os" + "syscall" + "time" +) + +func safeSocketControl(network string, address string, conn syscall.RawConn) error { + return nil +} + +func main() { + safeDialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + Control: safeSocketControl, + } + + safeTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: safeDialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + safeClient := &http.Client{ + Transport: safeTransport, + } + + reader := bufio.NewReader(os.Stdin) + untrusted, _ := reader.ReadString('\n') + _, _ = safeClient.Get(untrusted) +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_client_good.go b/test/testdata/vulnerablemodule/ssrf/ssrf_client_good.go new file mode 100644 index 0000000..b26ab56 --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_client_good.go @@ -0,0 +1,46 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "net" + "net/http" + "time" +) + +func main() { + safeDialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + Control: nil, + } + + safeTransport := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: safeDialer.DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + + safeClient := &http.Client{ + Transport: safeTransport, + } + + _, _ = safeClient.Get("google.com") +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_good.go b/test/testdata/vulnerablemodule/ssrf/ssrf_good.go new file mode 100644 index 0000000..9234637 --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_good.go @@ -0,0 +1,23 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "net/http" +) + +func main() { + _, _ = http.Get("google.com") +} diff --git a/test/testdata/vulnerablemodule/ssrf/ssrf_struct.go b/test/testdata/vulnerablemodule/ssrf/ssrf_struct.go new file mode 100644 index 0000000..668b57c --- /dev/null +++ b/test/testdata/vulnerablemodule/ssrf/ssrf_struct.go @@ -0,0 +1,34 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 ( + "bufio" + "net/http" + "os" +) + +type Wrapper struct { + Val string +} + +func main() { + reader := bufio.NewReader(os.Stdin) + untrusted, _ := reader.ReadString('\n') + wrapper := Wrapper{ + Val: untrusted, + } + _, _ = http.Get(wrapper.Val) +} diff --git a/test/testutil/run.go b/test/testutil/run.go new file mode 100644 index 0000000..971e5d2 --- /dev/null +++ b/test/testutil/run.go @@ -0,0 +1,42 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 testutil + +import ( + "path/filepath" + "runtime" + "testing" + + "github.com/praetorian-inc/gokart/run" + "golang.org/x/tools/go/analysis" +) + +func RunTest(file string, numResults int, resultsType string, analyzer *analysis.Analyzer, t *testing.T) { + _, b, _, _ := runtime.Caller(0) + basepath := filepath.Dir(b) + path := basepath + "/../testdata/vulnerablemodule/" + file + results, _, err := run.Run([]*analysis.Analyzer{analyzer}, "file="+path) + if err != nil { + t.Error(err) + } + if len(results) != numResults { + t.Errorf("Expected %d results, found %d", numResults, len(results)) + } + for _, result := range results { + if result.Type != resultsType { + t.Errorf("Expected type %s, found %s", resultsType, results[0].Type) + } + } +} diff --git a/util/analyzers.yml b/util/analyzers.yml new file mode 100644 index 0000000..b5be05d --- /dev/null +++ b/util/analyzers.yml @@ -0,0 +1,98 @@ +# Copyright 2021 Praetorian Security, Inc. + +# 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. + +# GoKart analyzers configuration + +# Uncomment analyzers section below to create a new vulnerability type + +# analyzers: +# # Each entry specifies a vulnerability type. + +# # Name of the vulnerability: +# "Test Sink": +# # Description of this vulnerability +# doc: "Writing data to Printf()" +# # Message displayed when this vulnerability is found +# message: "Test Sink reachable by user input" +# # List of vulnerable functions used to identify this vulnerability +# vuln_calls: +# # Package name +# "log": +# # Function name +# - "Printf" + + +sources: + # Each entry specifies a source that should be considered untrusted + # If the package already exists in the sources section, add the variable/function/type underneath + # Each package can contain multiple vulnerable sources. + sources: + # Sources that are defined in Go documentation as a "variable" go here (note: these variables will have an SSA type of "Global"). + variables: + "os": + - "Args" + # Sources that are defined in Go documentation as a "function" go here. + functions: + "flag": + - "Arg" + - "Args" + "os": + - "Environ" + - "File" + - "FileInfo" + - "FileMode" + - "Readdir" + - "Readdirnames" + - "OpenFile" + "crypto/tls": + - "LoadX509KeyPair" + - "X509KeyPair" + "os/user": + - "Lookup" + - "LookupId" + - "Current" + "crypto/x509": + - "Subjects" + "io": + - "ReadAtLeast" + - "ReadFull" + "database/sql": + - "Query" + - "QueryRow" + "bytes": + - "String" + - "ReadBytes" + - "ReadByte" + "bufio": + - "Text" + - "Bytes" + - "ReadString" + - "ReadSlice" + - "ReadRune" + - "ReadLine" + - "ReadBytes" + - "ReadByte" + "archive/tar": + - "Next" + - "FileInfo" + - "Header" + "net/url": + - "ParseQuery" + - "ParseUriRequest" + - "Parse" + - "Query" + # Sources that are defined in Go documentation as a "type" go here (note: adding types will consider all functions that use that type to be tainted). + types: + "net/http": + - "Request" diff --git a/util/config.go b/util/config.go new file mode 100644 index 0000000..bd69d75 --- /dev/null +++ b/util/config.go @@ -0,0 +1,159 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 util + +import ( + _ "embed" + "flag" + "fmt" + "io/ioutil" + "log" + "os" + "path" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +// ConfigType stores booleans for GoKart analysis configuration +type ConfigType struct { + GlobalsSafe bool + OutputSarif bool + Debug bool + Verbose bool + YMLPath string +} + +var ( + FilesFound = 0 + VulnGlobalVars map[string][]string + VulnGlobalFuncs map[string][]string + VulnTypes map[string][]string + //go:embed analyzers.yml + DefaultAnalyzersContent []byte +) + +var Config ConfigType + +func LoadVulnerableSources() { + // Load YAML + yamlPath := Config.YMLPath + // If not found in the working directory, use the one in the executable's directory + if _, err := os.Stat(yamlPath); os.IsNotExist(err) { + execPath, err := os.Executable() + if err != nil { + log.Fatal(err) + } + yamlPath = path.Join(path.Dir(execPath), yamlPath) + } + yfile, err := ioutil.ReadFile(yamlPath) + if err != nil { + log.Fatal(err) + } + + data := make(map[interface{}]map[interface{}]map[interface{}]interface{}) + err = yaml.Unmarshal(yfile, &data) + if err != nil { + log.Fatal(err) + } + + skeys := data["sources"] + if Config.Debug { + log.Println("Beginning list of sources defined in yml:") + } + for _, sdict := range skeys { + for stype, sTypeDict := range sdict { + callsmap := sTypeDict.(map[string]interface{}) + vulnmap := make(map[string][]string) + + for package_name, package_vuln_funcs := range callsmap { + // Initialize an empty array if the map key does not exist + if _, ok := vulnmap[package_name]; !ok { + var empty_array []string + vulnmap[package_name] = empty_array + } + package_vuln_funcs_arr := package_vuln_funcs.([]interface{}) + for i, val := range package_vuln_funcs_arr { + if Config.Debug { + log.Println("Function", package_vuln_funcs_arr[i], "in package", package_name) + } + vulnmap[package_name] = append(vulnmap[package_name], val.(string)) + } + } + + // Set the map of vulnerable sources of this type + if stype == "variables" { + VulnGlobalVars = vulnmap + } else if stype == "functions" { + VulnGlobalFuncs = vulnmap + } else if stype == "types" { + VulnTypes = vulnmap + + } + } + } + if Config.Debug { + log.Println("List of sources complete") + } +} + +// InitConfig() parses the flags and sets the corresponding Config variables +func InitConfig(globals bool, sarif bool, verbose bool, debug bool, yml string) { + + flag.Parse() + + // If the YAML path provided is a relative path, convert it to absolute + if yml != "" && !filepath.IsAbs(yml) { + yml, _ = filepath.Abs(yml) + } + + // If the YAML path provided is empty or doesn't exist, then load from the default of ~/.gokart/analyzers.yml + if _, err := os.Stat(yml); os.IsNotExist(err) { + if yml != "" { + fmt.Printf("Custom analyzers config file not found at %q. ", yml) + } + fmt.Println("Using default analyzers config found at \"~/.gokart/analyzers.yml\".") + + // Load YAML + config_path := os.ExpandEnv("$HOME/.gokart") + yaml_path := path.Join(config_path, "analyzers.yml") + yml = yaml_path + + // Create our config directory if it doesn't already exist + if _, err := os.Stat(config_path); os.IsNotExist(err) { + err = os.Mkdir(config_path, 0744) + if err != nil { + log.Fatal(err) + } + } + + // If not found in the working directory, use the one in the executable's directory + if _, err := os.Stat(yaml_path); os.IsNotExist(err) { + // default_analyzers_content is populated using the go:embed directive above + err := ioutil.WriteFile(yaml_path, DefaultAnalyzersContent, 0744) + if err != nil { + log.Fatal(err) + } + fmt.Println("No existing analyzers.yml file found - writing default to ~/.gokart/analyzers.yml") + } + } + + Config.GlobalsSafe = !globals + Config.OutputSarif = sarif + Config.Debug = debug + Config.Verbose = verbose + Config.YMLPath = yml + LoadVulnerableSources() +} diff --git a/util/finding.go b/util/finding.go new file mode 100644 index 0000000..e7adc63 --- /dev/null +++ b/util/finding.go @@ -0,0 +1,96 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 util + +import ( + "fmt" + "strings" + + "github.com/fatih/color" +) + +// Finding represents a single vulnerability +type Finding struct { + message string + Vulnerable_Function TaintedCode + Untrusted_Source []TaintedCode + Type string +} + +// Create a finding object +func MakeFinding(message string, vulnerable_function TaintedCode, untrusted_source []TaintedCode, finding_type string) Finding { + return Finding{ + message: message, + Vulnerable_Function: vulnerable_function, + Untrusted_Source: untrusted_source, + Type: finding_type, + } +} + +func StripArguments(parentFunction string) string { + functionName := strings.Split(parentFunction, "(")[0] + functionReturn := "" + if splitOnClose := strings.Split(parentFunction, ")"); len(splitOnClose) > 1 { + functionReturn = splitOnClose[1] + } + return strings.TrimSpace(functionName) + "(...)" + functionReturn +} + +// prints out a finding; returns true if the finding was valid and false if the finding had the same source and sink +func OutputFinding(finding Finding) bool { + // if the source and sink are the same, return false and do not print out the finding + if finding.Vulnerable_Function.SourceCode == finding.Untrusted_Source[0].SourceCode { + return false + } + if Config.OutputSarif { + SarifRecordFinding(finding.Type, finding.message, finding.Vulnerable_Function.SourceFilename, + finding.Vulnerable_Function.SourceLineNum) + } else { + yellow := color.New(color.FgYellow).SprintFunc() + cyan := color.New(color.FgCyan).SprintFunc() + green := color.New(color.FgGreen).SprintFunc() + + parentFunctionArgs := finding.Vulnerable_Function.ParentFunction + parentFunctionNoArgs := StripArguments(finding.Vulnerable_Function.ParentFunction) + + fmt.Printf("\n(%s) %s\n\n", cyan(finding.Type), yellow(finding.message)) + fmt.Printf("%s:%d\nVulnerable Function: [ %s ]\n", finding.Vulnerable_Function.SourceFilename, finding.Vulnerable_Function.SourceLineNum, parentFunctionNoArgs) + fmt.Printf(" %d:\t%s\n", finding.Vulnerable_Function.SourceLineNum-1, GrabSourceCode(finding.Vulnerable_Function.SourceFilename, finding.Vulnerable_Function.SourceLineNum-1)) + fmt.Printf(" > %d:\t%s\n", finding.Vulnerable_Function.SourceLineNum, finding.Vulnerable_Function.SourceCode) + fmt.Printf(" %d:\t%s\n", finding.Vulnerable_Function.SourceLineNum+1, GrabSourceCode(finding.Vulnerable_Function.SourceFilename, finding.Vulnerable_Function.SourceLineNum+1)) + + if finding.Untrusted_Source != nil { + + source := finding.Untrusted_Source[0] + fmt.Printf("\n%s:%d\n", source.SourceFilename, source.SourceLineNum) + fmt.Printf("Source of Untrusted Input: [ %s ]\n", parentFunctionNoArgs) + fmt.Printf(" %d:\t%s\n", source.SourceLineNum-1, GrabSourceCode(source.SourceFilename, source.SourceLineNum-1)) + fmt.Printf(" > %d:\t%s\n", source.SourceLineNum, source.SourceCode) + fmt.Printf(" %d:\t%s\n", source.SourceLineNum+1, GrabSourceCode(source.SourceFilename, source.SourceLineNum+1)) + + if Config.Verbose { + fmt.Print(green("\n############################### FULL TRACE ###############################\n")) + fmt.Printf("\nUntrusted Input Source:") + for _, source := range finding.Untrusted_Source { + fmt.Printf("%s:%d:\n[ %s ]\n>>>\t%s\n", source.SourceFilename, + source.SourceLineNum, parentFunctionArgs, strings.TrimLeft(source.SourceCode, " \t")) + } + } + + } + fmt.Printf("------------------------------------------------------------------------------\n") + } + return true +} diff --git a/util/sarif.go b/util/sarif.go new file mode 100644 index 0000000..3559e17 --- /dev/null +++ b/util/sarif.go @@ -0,0 +1,43 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 util + +import ( + "os" + + "github.com/owenrumney/go-sarif/sarif" +) + +var SarifReport *sarif.Report +var SarifRun *sarif.Run + +func InitSarifReporting() { + report, err := sarif.New(sarif.Version210) + SarifReport = report + if err != nil { + panic(err) + } + SarifRun = sarif.NewRun("GoKart", "https://github.com/praetorian-inc/gokart") +} + +func SarifRecordFinding(type_ string, message string, filename string, lineNumber int) { + vulnLoc := sarif.NewLocationWithPhysicalLocation(sarif.NewPhysicalLocation().WithArtifactLocation(sarif.NewSimpleArtifactLocation(filename)).WithRegion(sarif.NewSimpleRegion(lineNumber, lineNumber))) + SarifRun.AddResult(type_).WithMessage(sarif.NewTextMessage(message)).WithLocation(vulnLoc) +} + +func SarifPrintReport() { + SarifReport.AddRun(SarifRun) + SarifReport.PrettyWrite(os.Stdout) +} diff --git a/util/taint.go b/util/taint.go new file mode 100644 index 0000000..e9140ee --- /dev/null +++ b/util/taint.go @@ -0,0 +1,356 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 util + +import ( + "go/token" + "log" + "strings" + + "golang.org/x/tools/go/analysis" + + "golang.org/x/tools/go/ssa" +) + +// TaintedCode is a struct that contains information about the vulnerable line of code +type TaintedCode struct { + SourceCode string + SourceFilename string + SourceLineNum int + ParentFunction string +} + +//MapData is a struct that contains information about each hash +type MapData struct { + Mapped bool // whether a hash has already been mapped + Vulnerable bool // whether a hash has been found vulnerable + Count int // the number of times a hash has been visited +} + +// TaintAnalyzer is a struct that contains information about each taint analyzer +type TaintAnalyzer struct { + taint_map map[uint64]MapData + TaintSource []TaintedCode + pass *analysis.Pass + location token.Pos +} + +// CreateTaintAnalyzer returns a new TaintAnalyzer struct +func CreateTaintAnalyzer(pass *analysis.Pass, location token.Pos) TaintAnalyzer { + return TaintAnalyzer{ + make(map[uint64]MapData), + []TaintedCode{}, + pass, + location, + } +} + +// ContainsTaint analyzes the ssa.Value, recursively traces the value to all possible sources, and returns True if any of the sources are vulnerable. It returns False otherwise. +func (ta *TaintAnalyzer) ContainsTaint(startCall *ssa.CallCommon, val *ssa.Value, cg CallGraph) bool { + return ta.ContainsTaintRecurse(startCall, val, cg, 0, []ssa.Value{}) +} + +func (ta *TaintAnalyzer) ContainsTaintRecurse(startCall *ssa.CallCommon, val *ssa.Value, cg CallGraph, depth int, visitedMutable []ssa.Value) bool { + if *val == nil { + return false + } + if Config.Debug { + out := "" + for i := 0; i < depth; i++ { + out += " " + } + log.Printf("%s%s (%T)\n", out, *val, *val) + } + + call, isCall := (*val).(*ssa.Call) + if isCall { + //A function call cannot become tainted from itself This is due to a bug with how we handle referrers. Since we + //check all function calls, past and future, we need to make sure to ignore the starting function call + //This makes sure we dont duplicate findings by having one parameter infect other parameters + if startCall == &call.Call { + return false + } + } + + //We have already seen this buffer, assume its fine + for _, visitedVal := range visitedMutable { + if *val == visitedVal { + return false + } + } + + // Memoize the ssa.Value + map_status1 := ta.taint_map[SSAvalToHash(val)] + ta.Memoize(val, map_status1.Vulnerable) + // Store the memoization status in map_status + map_status := ta.taint_map[SSAvalToHash(val)] + + // if the ssa.Value hash has been seen over fifty times, return false because it is likely an infinite loop + if map_status.Count > 20 { + if Config.Debug { + log.Printf("Overflow detected, breaking the infinite loop") + } + + return false + } + // if the ssa.Value hash has already been mapped, return it's vulnerable status + if map_status.Mapped { + return map_status.Vulnerable + } + + //default set vulnerable to false, this may not be necessary anymore + vulnerable := false + + switch expr := (*val).(type) { + case *ssa.Const: + vulnerable = false + case *ssa.Parameter: + // Check if this function call is part of the tainted function source list + globalPkgName := (expr).Parent().Pkg.Pkg.Name() + if val, ok := VulnGlobalFuncs[globalPkgName]; ok { + for _, funcName := range val { + if (expr).Name() == funcName { + vulnerable = true + } + } + } + + for pkg, types_ := range VulnTypes { + for _, type_ := range types_ { + if strings.TrimPrefix(expr.Type().String(), "*") == pkg+"."+type_ { + vulnerable = true + } + } + } + + var values []*ssa.Value + values = cg.ResolveParam(expr) + if len(values) > 0 { + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, values[0], cg, depth+1, visitedMutable) //loop B + } + case *ssa.FreeVar: + vulnerable = false + case *ssa.Function: + // Assume that the user cannot create their own functions + vulnerable = false + case *ssa.Field: + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.Next: + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr.Iter, cg, depth+1, visitedMutable) + case *ssa.TypeAssert: + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.Range: + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.Phi: + mapping := MapData{Mapped: true, Vulnerable: false} + ta.taint_map[SSAvalToHash(val)] = mapping + for _, edge := range (*expr).Edges { + + // this if statement is to prevent infiinite loop + if edge != expr { + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &edge, cg, depth+1, visitedMutable) + } + } + case *ssa.UnOp: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.BinOp: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) || ta.ContainsTaintRecurse(startCall, &expr.Y, cg, depth+1, visitedMutable) + case *ssa.Extract: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.Tuple, cg, depth+1, visitedMutable) + case *ssa.Call: + callFunc, ok := (expr.Call.Value).(*ssa.Function) + if ok { + globalPkgName := callFunc.Pkg.Pkg.Name() + if val, ok := VulnGlobalFuncs[globalPkgName]; ok { + for _, funcName := range val { + if callFunc.Name() == funcName { + vulnerable = true + } + } + } + } + if dest := expr.Common().StaticCallee(); dest != nil { + returns := ReturnValues(dest) + + /* If return values of function can't be determined then we run under the assumption + * that if you can trust the arguments to the function, then you can trust the return value of the function. + */ + if len(returns) > 0 { + + for _, retval := range returns { + if len(retval) > 0 { + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &retval[0], cg, depth+1, visitedMutable) + } + } + } else { + for _, arg := range expr.Call.Args { + + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &arg, cg, depth+1, visitedMutable) //loop C + } + } + } else { + for _, arg := range expr.Call.Args { + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &arg, cg, depth+1, visitedMutable) //loop C + } + ta.pass.Reportf(ta.location, "Warning: Couldn't evaluate function statically") + } + case *ssa.Slice: + valSlice := ssa.Slice(*expr) + valSliceX := valSlice.X + vulnerable = ta.ContainsTaintRecurse(startCall, &valSliceX, cg, depth+1, visitedMutable) //loop D + refs := valSlice.Referrers() + for _, ref := range *refs { + expr, isVal := ref.(ssa.Value) + if isVal { + newMutable := make([]ssa.Value, len(visitedMutable)+1) + copy(newMutable, visitedMutable) + newMutable = append(newMutable, *val) + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr, cg, depth+1, newMutable) + } + } + case *ssa.MakeSlice: + // MakeSlice is only used for new allocations and, as such, is + // inherently safe. + vulnerable = false + case *ssa.Convert: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.ChangeType: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.MakeInterface: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.MakeMap: + vulnerable = false + case *ssa.MakeClosure: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.Fn, cg, depth+1, visitedMutable) + for _, val := range expr.Bindings { + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &val, cg, depth+1, visitedMutable) + } + case *ssa.Lookup: + // Traces not only the collection but also the source of the index + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) || ta.ContainsTaintRecurse(startCall, &expr.Index, cg, depth+1, visitedMutable) + case *ssa.Index: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) || ta.ContainsTaintRecurse(startCall, &expr.Index, cg, depth+1, visitedMutable) + case *ssa.ChangeInterface: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.IndexAddr: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.FieldAddr: + vulnerable = ta.ContainsTaintRecurse(startCall, &expr.X, cg, depth+1, visitedMutable) + case *ssa.Alloc: + // Check all the references to this memory + alloc_refs := expr.Referrers() + vulnerable = false + + mapping := MapData{Mapped: true, Vulnerable: false} + ta.taint_map[SSAvalToHash(val)] = mapping + + for alloc_item := range *alloc_refs { + alloc_ref := (*alloc_refs)[alloc_item] + + switch instr := (alloc_ref).(type) { + case *ssa.IndexAddr: + for indexaddr_ref_idx := range *instr.Referrers() { + indexaddr_ref := (*instr.Referrers())[indexaddr_ref_idx] + switch instr2 := (indexaddr_ref).(type) { + // If the variable is assigned to something else, check + // the new assignment + case *ssa.Store: + if ta.ContainsTaintRecurse(startCall, &instr2.Val, cg, depth+1, visitedMutable) { //loop A -- I think this might be causing the problem + vulnerable = true + } + } + } + + case *ssa.FieldAddr: + for _, ref := range *instr.Referrers() { + expr, isStore := (ref).(*ssa.Store) + if isStore { + newMutable := make([]ssa.Value, len(visitedMutable)+1) + copy(newMutable, visitedMutable) + newMutable = append(newMutable, *val) + vulnerable = vulnerable || ta.ContainsTaintRecurse(startCall, &expr.Val, cg, depth+1, newMutable) + } + } + } + + var items []*ssa.Value + operand_items := alloc_ref.Operands(items) + for operand_idx := range operand_items { + if ta.ContainsTaintRecurse(startCall, operand_items[operand_idx], cg, depth+1, visitedMutable) { + vulnerable = true + } + } + } + case *ssa.Global: + if Config.Debug { + test := GenerateTaintedCode(ta.pass, (*val).Parent(), (*val).Pos()) + log.Println("Global variable found: ", test.SourceCode, " in file ", test.SourceFilename) + } + vulnerable = !Config.GlobalsSafe + globalPkgName := (expr).Package().Pkg.Name() + if Config.Debug { + log.Println("expr", expr, expr.Package()) + log.Println("gloablPkgName", globalPkgName, *val) + log.Println(VulnGlobalVars) + } + + if val, ok := VulnGlobalVars[globalPkgName]; ok { + for _, funcName := range val { + if (expr).Name() == funcName { + if Config.Debug { + log.Println(expr.Name()) + log.Println(funcName) + } + + vulnerable = true + } + } + } + case nil: + vulnerable = false + default: + vulnerable = true + if Config.Debug { + log.Printf("Unknown SSA type found: %T\n", expr) + } + } + + // Memoize the ssa.Value along with whether or not it is vulnerable + ta.Memoize(val, vulnerable) + + /* If the taint analysis reaches a vulnerable ssa.Value, + * then store the information about the state to display to the analyst as untrusted input. + */ + if vulnerable { + tempTaintedCode := GenerateTaintedCode(ta.pass, (*val).Parent(), (*val).Pos()) + if tempTaintedCode.SourceLineNum > 0 { + + // Make sure that we don't output duplicate source code lines in Verbose Output + duplicateSourceCode := false + for _, current := range ta.TaintSource { + if tempTaintedCode.SourceLineNum == current.SourceLineNum { + duplicateSourceCode = true + break + } + } + + if !duplicateSourceCode { + ta.TaintSource = append(ta.TaintSource, tempTaintedCode) + } + } + } + + return vulnerable +} diff --git a/util/util.go b/util/util.go new file mode 100644 index 0000000..403efcb --- /dev/null +++ b/util/util.go @@ -0,0 +1,190 @@ +// Copyright 2021 Praetorian Security, Inc. +// +// 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 util implements underlying functionality for building and traversing call graphs, +configuraing and building analyzers and generating findings +*/ +package util + +import ( + "bufio" + "bytes" + "fmt" + "go/token" + "os" + "runtime" + "strings" + + "golang.org/x/tools/go/analysis" + "golang.org/x/tools/go/ssa" + + "github.com/segmentio/fasthash/fnv1a" +) + +type ReturnSet = []ssa.Value + +// ReturnValues returns a set of the return values of the function +func ReturnValues(fn *ssa.Function) []ReturnSet { + res := []ReturnSet{} + + for _, block := range fn.DomPreorder() { + // a returning block ends in a Return instruction and has no successors + if len(block.Succs) != 0 { + continue + } + + if ret, ok := block.Instrs[len(block.Instrs)-1].(*ssa.Return); ok { + res = append(res, ret.Results[:]) + } + } + + return res +} + +// CGRelation is a struct that contains information about an instruction and a function in the call graph +type CGRelation struct { + Instr *ssa.Call + Fn *ssa.Function +} +type CallGraph map[string][]CGRelation + +// AnalyzeFunction updates the CallGraph to contain relations between callee and caller functions. This should be called once on every function in a local package +func (cg CallGraph) AnalyzeFunction(fn *ssa.Function) { + for _, block := range fn.DomPreorder() { + for _, instr := range block.Instrs { + switch instr := instr.(type) { + case *ssa.Call: + if instr.Call.StaticCallee() != nil { + calleeName := instr.Call.StaticCallee().String() + // Update the callgraph + cg[calleeName] = append(cg[calleeName], CGRelation{instr, fn}) + } + } + } + } +} + +// ResolveParam returns the caller nodes of a parameter. This is used for tracing parameters back to their source. +func (cg CallGraph) ResolveParam(p *ssa.Parameter) []*ssa.Value { + // Determine which argument we are in the parent function + pFunc := p.Parent() + pIdx := -1 + for i, arg := range pFunc.Params { + if p.Pos() == arg.Pos() { + pIdx = i + } + } + // Find all the places the function is called + callerNodes := make([]*ssa.Value, len(cg[pFunc.String()])) + for i, rel := range cg[pFunc.String()] { + callerNodes[i] = &rel.Instr.Call.Args[pIdx] + } + return callerNodes +} + +// Memoize hashes an ssa.Value and then adds it to the Taint Map while updating the metadata +func (ta TaintAnalyzer) Memoize(val *ssa.Value, vulnerable bool) { + switch (*val).(type) { + case *ssa.Phi: + // Don't want to memoize Phi nodes as recursion will then not check all edges + default: + // hash the ssa.Value + hash := SSAvalToHash(val) + // get the current map status + map_status := ta.taint_map[hash] + // increment the count + new_count := map_status.Count + 1 + // create the new MapData struct + mapping := MapData{Mapped: map_status.Mapped, Vulnerable: map_status.Vulnerable, Count: new_count} + // update the Taint Map + ta.taint_map[hash] = mapping + } + +} + +// SSAvalToHash returns the hash of an ssa.Value to be used in the Taint Map +func SSAvalToHash(val *ssa.Value) uint64 { + // convert the de-referenced ssa.Value to a byte array + b_arrayPointer := []byte(fmt.Sprintf("%v", *val)) + // convert the byte array to a string + val_string := string(b_arrayPointer) + // if the ssa.Value has a parent, add that to the val_string to be used in the hash. Otherwise just hash the val_string + if (*val).Parent() != nil { + b_arrayParent := (*val).Parent().String() + val_string += b_arrayParent + } + + // hash the val_string + hash := fnv1a.HashString64(val_string) + return hash +} + +// GrabSourceCode retrieves the specified line of source code from the specified file +func GrabSourceCode(filename string, lineNumber int) string { + + fileHandle, _ := os.Open(filename) + defer fileHandle.Close() + + var buff bytes.Buffer + scanner := bufio.NewScanner(fileHandle) + scanner.Split(bufio.ScanLines) + + counter := 0 + + for scanner.Scan() { + counter++ + if lineNumber == counter { + buff.WriteString(scanner.Text()) + break + } + } + return buff.String() +} + +// GenerateTaintedCode returns a TaintedCode struct that stores information (source code, filename, linenumber) for a line of code +func GenerateTaintedCode(pass *analysis.Pass, parent *ssa.Function, position token.Pos) TaintedCode { + vulnerable_code := pass.Fset.Position(position) + + // Evaluate $GOROOT environment variable so correct filepath is generated. + expanded_filename := os.ExpandEnv(vulnerable_code.Filename) + if _, err := os.Stat(expanded_filename); os.IsNotExist(err) { + if strings.Contains(vulnerable_code.Filename, "$GOROOT") { + vulnerable_code.Filename = strings.Replace(vulnerable_code.Filename, "$GOROOT", runtime.GOROOT(), 1) + } else { + vulnerable_code.Filename = "WARNING: Could not find the file at path: " + vulnerable_code.Filename + } + } else { + vulnerable_code.Filename = expanded_filename + } + + vulnerable_source_code := GrabSourceCode(vulnerable_code.Filename, vulnerable_code.Line) + + var parent_function_name string + var parent_function_args string + if parent == nil { + parent_function_name = "" + parent_function_args = "" + } else { + parent_function_name = parent.Name() + parent_function_args = strings.Split(parent.Signature.String(), "func")[1] + } + tainted_code := TaintedCode{ + SourceCode: vulnerable_source_code, + SourceFilename: vulnerable_code.Filename, + SourceLineNum: vulnerable_code.Line, + ParentFunction: parent_function_name + " " + parent_function_args, + } + return tainted_code +}