diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..9db7326 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,14 @@ +blank_issues_enabled: false + +contact_links: + - name: Ask an question / advise on using cvemap + url: https://github.com/projectdiscovery/cvemap/discussions/categories/q-a + about: Ask a question or request support for using cvemap + + - name: Share idea / feature to discuss for cvemap + url: https://github.com/projectdiscovery/cvemap/discussions/categories/ideas + about: Share idea / feature to discuss for cvemap + + - name: Connect with PD Team (Discord) + url: https://discord.gg/projectdiscovery + about: Connect with PD Team for direct communication \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..a44c78a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature request +about: Request feature to implement in this project +labels: 'Type: Enhancement' +--- + + + +### Please describe your feature request: + + +### Describe the use case of this feature: + diff --git a/.github/ISSUE_TEMPLATE/issue-report.md b/.github/ISSUE_TEMPLATE/issue-report.md new file mode 100644 index 0000000..1cad67e --- /dev/null +++ b/.github/ISSUE_TEMPLATE/issue-report.md @@ -0,0 +1,36 @@ +--- +name: Issue report +about: Create a report to help us to improve the project +labels: 'Type: Bug' + +--- + + + + + +### cvemap version: + + + + +### Current Behavior: + + +### Expected Behavior: + + +### Steps To Reproduce: + + + +### Anything else: + \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f9ecf8d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,45 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + + # Maintain dependencies for go modules + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "weekly" + target-branch: "main" + commit-message: + prefix: "chore" + include: "scope" + labels: + - "Type: Maintenance" + allow: + - dependency-name: "github.com/projectdiscovery/*" + +# # Maintain dependencies for docker +# - package-ecosystem: "docker" +# directory: "/" +# schedule: +# interval: "weekly" +# target-branch: "dev" +# commit-message: +# prefix: "chore" +# include: "scope" +# labels: +# - "Type: Maintenance" +# +# # Maintain dependencies for GitHub Actions +# - package-ecosystem: "github-actions" +# directory: "/" +# schedule: +# interval: "weekly" +# target-branch: "dev" +# commit-message: +# prefix: "chore" +# include: "scope" +# labels: +# - "Type: Maintenance" \ No newline at end of file diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 0000000..596ba07 --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,17 @@ +changelog: + exclude: + authors: + - dependabot + categories: + - title: 🎉 New Features + labels: + - "Type: Enhancement" + - title: 🐞 Bugs Fixes + labels: + - "Type: Bug" + - title: 🔨 Maintenance + labels: + - "Type: Maintenance" + - title: Other Changes + labels: + - "*" \ No newline at end of file diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml new file mode 100644 index 0000000..8bd137d --- /dev/null +++ b/.github/workflows/build-test.yml @@ -0,0 +1,63 @@ +name: 🔨 Build Test + +on: + pull_request: + paths: + - '**.go' + - '**.mod' + workflow_dispatch: + +jobs: + build: + name: Test Builds + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macOS-latest] + go-version: [1.21.x] + steps: + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: ${{ matrix.go-version }} + + - name: Check out code + uses: actions/checkout@v3 + + - name: Build + run: go build . + working-directory: cmd/cvemap/ + + - name: Test + run: go test ./... + working-directory: . + + - name: Integration Tests Linux, macOS + if: runner.os == 'Linux' || runner.os == 'macOS' + env: + GH_ACTION: true + run: bash run.sh + working-directory: cmd/integration-test/ + + - name: Integration Tests Windows + if: runner.os == 'Windows' + env: + GH_ACTION: true + MSYS_NO_PATHCONV: true + run: bash run.sh + working-directory: cmd/integration-test/ + + - name: Race Condition Tests + if: github.actor != 'dependabot[bot]' + run: go run -race . -id CVE-1999-0027 + working-directory: cmd/cvemap/ + env: + PDCP_API_KEY: "${{ secrets.PDCP_API_KEY }}" + PDCP_API_SERVER: https://api.projectdiscovery.io + DEBUG: true + + # - name: Test Example Code + # run: go run . + # working-directory: examples/ + + diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..9f533f8 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,38 @@ +name: 🚨 CodeQL Analysis + +on: + workflow_dispatch: + pull_request: + branches: + - dev + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'go' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 \ No newline at end of file diff --git a/.github/workflows/dep-auto-merge.yml b/.github/workflows/dep-auto-merge.yml new file mode 100644 index 0000000..fc26472 --- /dev/null +++ b/.github/workflows/dep-auto-merge.yml @@ -0,0 +1,26 @@ +name: 🤖 dep auto merge + +on: + pull_request: + branches: + - main + workflow_dispatch: + +permissions: + pull-requests: write + issues: write + repository-projects: write + +jobs: + automerge: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - uses: actions/checkout@v3 + with: + token: ${{ secrets.DEPENDABOT_PAT }} + + - uses: ahmadnassri/action-dependabot-auto-merge@v2 + with: + github-token: ${{ secrets.DEPENDABOT_PAT }} + target: all \ No newline at end of file diff --git a/.github/workflows/dockerhub-push.yml b/.github/workflows/dockerhub-push.yml new file mode 100644 index 0000000..1e9f341 --- /dev/null +++ b/.github/workflows/dockerhub-push.yml @@ -0,0 +1,40 @@ +name: 🌥 Docker Push + +on: + workflow_run: + workflows: ["🎉 Release Binary"] + types: + - completed + workflow_dispatch: + +jobs: + docker: + runs-on: ubuntu-latest-16-cores + steps: + - name: Git Checkout + uses: actions/checkout@v3 + + - name: Get Github tag + id: meta + run: | + curl --silent "https://api.github.com/repos/projectdiscovery/cvemap/releases/latest" | jq -r .tag_name | xargs -I {} echo TAG={} >> $GITHUB_OUTPUT + + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Login to DockerHub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v4 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm + push: true + tags: projectdiscovery/cvemap:latest,projectdiscovery/cvemap:${{ steps.meta.outputs.TAG }} \ No newline at end of file diff --git a/.github/workflows/lint-test.yml b/.github/workflows/lint-test.yml new file mode 100644 index 0000000..57d31cf --- /dev/null +++ b/.github/workflows/lint-test.yml @@ -0,0 +1,28 @@ +name: 🙏🏻 Lint Test + +on: + pull_request: + paths: + - '**.go' + - '**.mod' + workflow_dispatch: + +jobs: + lint: + name: Lint Test + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: "Set up Go" + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v3.6.0 + with: + version: latest + args: --timeout 5m + working-directory: . \ No newline at end of file diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml new file mode 100644 index 0000000..b17ae06 --- /dev/null +++ b/.github/workflows/release-binary.yml @@ -0,0 +1,32 @@ +name: 🎉 Release Binary +on: + push: + tags: + - '*' + workflow_dispatch: + +jobs: + release: + runs-on: ubuntu-latest-16-cores + steps: + - name: "Check out code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: "Set up Go" + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: "Create release on GitHub" + uses: goreleaser/goreleaser-action@v4 + with: + args: "release --clean" + version: latest + workdir: . + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + SLACK_WEBHOOK: "${{ secrets.RELEASE_SLACK_WEBHOOK }}" + DISCORD_WEBHOOK_ID: "${{ secrets.DISCORD_WEBHOOK_ID }}" + DISCORD_WEBHOOK_TOKEN: "${{ secrets.DISCORD_WEBHOOK_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml new file mode 100644 index 0000000..3cc2909 --- /dev/null +++ b/.github/workflows/release-test.yml @@ -0,0 +1,30 @@ +name: 🔨 Release Test + +on: + pull_request: + paths: + - '**.go' + - '**.mod' + - '**.yml' + workflow_dispatch: + +jobs: + release-test: + runs-on: ubuntu-latest-16-cores + steps: + - name: "Check out code" + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: 1.21.x + + - name: release test + uses: goreleaser/goreleaser-action@v4 + with: + args: "release --clean --snapshot" + version: latest + workdir: . \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b735ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..a5a7834 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,45 @@ +before: + hooks: + - go mod tidy + +builds: +- env: + - CGO_ENABLED=0 + goos: + - windows + - linux + - darwin + goarch: + - amd64 + - 386 + - arm + - arm64 + + ignore: + - goos: darwin + goarch: '386' + - goos: windows + goarch: 'arm' + - goos: windows + goarch: 'arm64' + + binary: '{{ .ProjectName }}' + main: cmd/cvemap/main.go + +archives: +- format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ if eq .Os "darwin" }}macOS{{ else }}{{ .Os }}{{ end }}_{{ .Arch }}' + +checksum: + algorithm: sha256 + +announce: + slack: + enabled: true + channel: '#release' + username: GoReleaser + message_template: 'New Release: {{ .ProjectName }} {{.Tag}} is published! Check it out at {{ .ReleaseURL }}' + + discord: + enabled: true + message_template: '**New Release: {{ .ProjectName }} {{.Tag}}** is published! Check it out at {{ .ReleaseURL }}' \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..822f480 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 ProjectDiscovery + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2d33510 --- /dev/null +++ b/README.md @@ -0,0 +1,153 @@ +

CVEMap

+ +

+ + + + + + +

+

+ Features • + Installation • + Usage • + Example • + Join Discord +

+ +Navigate the Common Vulnerabilities and Exposures (CVE) jungle with ease using CVEMAP, a command-line interface (CLI) tool designed to provide a structured and easily navigable interface to various vulnerability databases. + + +# Features + +![image](static/cvemap.png) + + - **CVE Dataset Search & Query** + - **CVE to EPSS Mapping** + - **CVE to KEV Mapping** + - **CVE to CPE Mapping** + - **CVE to GitHub POCs Mapping** + - **CVE to Nuclei Template Mapping** + - **CVE to HackerOne report Mapping** + - Customizable Filters on CVE data + - STDIN Input / JSONL Output + + +## Installation + +cvemap requires **Go 1.21** to install successfully. To install, just run the below command or download pre-compiled binary from [release page](https://github.com/projectdiscovery/cvemap/releases). + +```console +go install github.com/projectdiscovery/cvemap/cmd/cvemap@latest +``` + +## Usage +```console +cvemap -h +``` +This will display help for the tool. Here are all the switches it supports. + +```console +Usage: + cvemap [flags] + +Flags: +CONFIG: + -auth configure projectdiscovery cloud (pdcp) api key + +OPTIONS: + -id string[] cve to list for given id + -v, -vendor string[] cve to list for given vendor + -p, -product string[] cve to list for given product + -eproduct string[] cves to exclude based on products + -s, -severity string[] cve to list for given severity + -cs, -cvss-score string[] cve to list for given cvss score + -c, -cpe string cve to list for given cpe + -es, -epss-score string cve to list for given epss score + -ep, -epss-percentile string[] cve to list for given epss percentile + -age string cve to list published by given age in days + -a, -assignee string[] cve to list for given publisher assignee + -vs, -vstatus value cve to list for given vulnerability status in cli output. supported: unknown, new, confirmed, unconfirmed, modified, rejected + +UPDATE: + -up, -update update cvemap to latest version + -duc, -disable-update-check disable automatic cvemap update check + +FILTER: + -q, -search string search in cve data + -k, -kev display cves marked as exploitable vulnerabilities by cisa (default true) + -t, -template display cves that has public nuclei templates (default true) + -poc display cves that has public published poc (default true) + -h1, -hackerone display cves reported on hackerone (default true) + +OUTPUT: + -f, -field value fields to display in cli output. supported: age, kev, template, poc, cwe, epss, assignee, product, vendor, vstatus + -fe, -exclude value fields to exclude from cli output. supported: age, kev, template, poc, cwe, epss, assignee, product, vendor, vstatus + -lsi, -list-id list only the cve ids in the output + -l, -limit int limit the number of results to display (default 50) + -offset int offset the results to display + -j, -json return output in json format + -epk, -enable-page-keys enable page keys to navigate results + +DEBUG: + -version Version + -silent Silent + -verbose Verbose +``` + +## Configuring CVEMap CLI + +CVEMap CLI is built on top of the CVEMap API that requires API Token from [ProjectDiscovery Cloud Platform](https://cloud.projectdiscovery.io/?ref=api_key) that can be configured using environment variable named `PDCP_API_KEY` or using interactive `-auth` option as shown below. + +### Using environment variable + +```console +export PDCP_API_KEY=************* +``` + +### Using auth option + +```console +cvemap -auth + + + ______ _____ ____ ___ ____ ____ + / ___/ | / / _ \/ __ \__ \/ __ \/ __ \ + / /__ | |/ / __/ / / / / / /_/ / /_/ / + \___/ |___/\___/_/ /_/ /_/\__,_/ .___/ + /_/ + + + projectdiscovery.io + +[INF] Get your free api key by signing up at https://cloud.projectdiscovery.io +[*] Enter PDCP API Key (exit to abort): ************* +[INF] Successfully logged in as (@user) +``` + +## Running CVEMap + +For details about running cvemap, see https://docs.projectdiscovery.io/tools/cvemap/running. + + +## Note + +- CVE dataset gets updated in every 6 hours. + +## References + +- **[National Vulnerability Database (NVD)](https://nvd.nist.gov/developers)**: Comprehensive CVE vulnerability data. +- **[Known Exploited Vulnerabilities Catalog (KEV)](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)**: Exploited vulnerabilities catalog. +- **[Exploit Prediction Scoring System (EPSS)](https://www.first.org/epss/data_stats)**: Exploit prediction scores. +- **[HackerOne](https://hackerone.com/hacktivity/cve_discovery)**: CVE discoveries disclosure. +- **[Nuclei Templates](https://github.com/projectdiscovery/nuclei-templates)**: Vulnerability validation templates. +- **[Live-Hack-CVE](https://github.com/Live-Hack-CVE/) / [PoC-in-GitHub](https://github.com/nomi-sec/PoC-in-GitHub/)** GitHub Repository: Vulnerability PoCs references. +-------- + +
+ +**cvemap** is made with ❤️ by the [projectdiscovery](https://projectdiscovery.io) team and distributed under [MIT License](LICENSE). + + +Join Discord \ No newline at end of file diff --git a/cmd/cvemap/main.go b/cmd/cvemap/main.go new file mode 100644 index 0000000..44f71ed --- /dev/null +++ b/cmd/cvemap/main.go @@ -0,0 +1,8 @@ +package main + +import "github.com/projectdiscovery/cvemap/runner" + +func main() { + options := runner.ParseOptions() + runner.Run(*options) +} diff --git a/cmd/integration-test/main.go b/cmd/integration-test/main.go new file mode 100644 index 0000000..61831a5 --- /dev/null +++ b/cmd/integration-test/main.go @@ -0,0 +1,62 @@ +package main + +import ( + "flag" + "fmt" + "os" + "strings" + + "github.com/logrusorgru/aurora" + "github.com/pkg/errors" + "github.com/projectdiscovery/cvemap/runner/testutils" +) + +var ( + xPDCPHeaderTestKey = "test-67291d9a-0aa6-49b1-b249-2b9d4b45bcea" + debug = os.Getenv("DEBUG") == "true" + success = aurora.Green("[✓]").String() + failed = aurora.Red("[✘]").String() + currentCvemapBinary = flag.String("current", "", "Current Branch Cvemap Binary") +) + +func main() { + flag.Parse() + SetupMockServer() + os.Setenv("CVEMAP_API_URL", "http://localhost:8080/api/v1") + os.Setenv("PDCP_API_KEY", xPDCPHeaderTestKey) + if err := runIntegrationTests(); err != nil { + fmt.Println("Error running integration tests:", err) + } +} + +var testCases = map[string]testutils.TestCase{ + "Get By cve_id": &CveIDTestCase{}, +} + +type CveIDTestCase struct{} + +func (c *CveIDTestCase) Execute() error { + currentOutput, err := testutils.RunCvemapBinaryAndGetResults(*currentCvemapBinary, debug, []string{"-id", "CVE-1999-0027", "-j", "-silent"}) + if err != nil { + return errors.Wrap(err, "could not run cvemap test") + } + if len(currentOutput) == 0 { + return errors.New("no output from cvemap") + } + if strings.Contains(strings.Join(currentOutput, ""), `"cve_id": "CVE-1999-0027"`) { + return nil + } + return errors.New("cve_id not found in output") +} + +func runIntegrationTests() error { + + for testName, testcase := range testCases { + if err := testcase.Execute(); err != nil { + fmt.Fprintf(os.Stderr, "%s Test \"%s\" failed: %s\n", failed, testName, err) + } else { + fmt.Printf("%s Test \"%s\" passed!\n", success, testName) + } + } + return nil +} diff --git a/cmd/integration-test/run.sh b/cmd/integration-test/run.sh new file mode 100644 index 0000000..488deca --- /dev/null +++ b/cmd/integration-test/run.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +# reading os type from arguments +CURRENT_OS=$1 + +if [ "${CURRENT_OS}" == "windows-latest" ];then + extension=.exe +fi + +echo "::group::Building integration-test binary" +go build -o integration-test$extension +echo "::endgroup::" + +echo "::group::Building cvemap binary from current branch" +go build -o cvemap$extension ../cvemap +echo "::endgroup::" + + +echo 'Starting cvemap integration test' +./integration-test$extension -current cvemap$extension diff --git a/cmd/integration-test/server.go b/cmd/integration-test/server.go new file mode 100644 index 0000000..f09c025 --- /dev/null +++ b/cmd/integration-test/server.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "os" + + "github.com/projectdiscovery/cvemap/runner" +) + +var cveData *runner.CVEBulkData + +func SetupMockServer() { + var err error + // Load data from the JSON file + cveData, err = loadData("test-data.json") + if err != nil { + fmt.Println("Error loading data:", err) + return + } + // Setup HTTP server + http.HandleFunc("/api/v1/cves", RequireAPIKey(http.HandlerFunc(handleRequest))) + + go func() { + // Start the server on port 8080 + fmt.Println("Cvemap test server listening on 8080...") + if err := http.ListenAndServe(":8080", nil); err != nil { + fmt.Println("Error starting server:", err) + } + }() +} + +// RequireAPIKey is a middleware that checks for the X-PDCP-Key header. +func RequireAPIKey(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + apiKey := r.Header.Get("X-PDCP-Key") + if apiKey != xPDCPHeaderTestKey { + http.Error(w, "Unauthorized: X-PDCP-Key header is required", http.StatusUnauthorized) + return + } + next(w, r) + } +} + +// handleRequest handles HTTP requests. +func handleRequest(w http.ResponseWriter, r *http.Request) { + // Handle the case where "cve_id" is a query parameter + cveID := r.URL.Query().Get("cve_id") + if cveID == "" { + http.NotFound(w, r) + } + for _, data := range cveData.Cves { + if data.CveID == cveID { + // Return the data corresponding to the given CVE ID + if err := json.NewEncoder(w).Encode(cveData); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + return + } + } + http.NotFound(w, r) +} + +// LoadData loads data from a JSON file into a slice of CVEData. +func loadData(filename string) (*runner.CVEBulkData, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + var data runner.CVEBulkData + decoder := json.NewDecoder(file) + if err := decoder.Decode(&data); err != nil { + return nil, err + } + + return &data, nil +} diff --git a/cmd/integration-test/test-data.json b/cmd/integration-test/test-data.json new file mode 100644 index 0000000..056693b --- /dev/null +++ b/cmd/integration-test/test-data.json @@ -0,0 +1,56 @@ +{ + "result_count": 1, + "total_results": 1, + "cves": [ + { + "cpe": { + "cpe": "cpe:2.3:o:sgi:irix:*:*:*:*:*:*:*:*", + "vendor": "sgi", + "product": "irix" + }, + "epss": { + "epss_score": 0.00064, + "epss_percentile": 0.26181 + }, + "cve_id": "CVE-1999-0027", + "is_oss": false, + "is_poc": false, + "assignee": "cve@mitre.org", + "severity": "high", + "hackerone": { + "rank": 6320, + "count": 0 + }, + "is_remote": false, + "reference": [ + "https://exchange.xforce.ibmcloud.com/vulnerabilities/CVE-1999-0027", + "https://github.com/Kuromesi/Py4CSKG" + ], + "cvss_score": 7.2, + "updated_at": "2022-08-17T07:15:08.580", + "weaknesses": [ + { + "cwe_id": "CWE-119", + "cwe_name": "Improper Restriction of Operations within the Bounds of a Memory Buffer" + } + ], + "age_in_days": 9682, + "is_template": false, + "vuln_status": "modified", + "cvss_metrics": { + "cvss2": { + "score": 7.2, + "vector": "CVSS:2.0/AV:L/AC:L/Au:N/C:C/I:C/A:C", + "severity": "high" + } + }, + "is_exploited": false, + "published_at": "1997-07-16T04:00:00.000", + "vulnerable_cpe": [ + "cpe:2.3:o:sgi:irix:*:*:*:*:*:*:*:*" + ], + "cve_description": "root privileges via buffer overflow in eject command on SGI IRIX systems.", + "vendor_advisory": "" + } + ] +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2e044c9 --- /dev/null +++ b/go.mod @@ -0,0 +1,104 @@ +module github.com/projectdiscovery/cvemap + +go 1.21 + +require ( + github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 + github.com/jedib0t/go-pretty/v6 v6.4.7 + github.com/logrusorgru/aurora v2.0.3+incompatible + github.com/pkg/errors v0.9.1 + github.com/projectdiscovery/goflags v0.1.36 + github.com/projectdiscovery/gologger v1.1.12 + github.com/projectdiscovery/utils v0.0.74 +) + +require ( + aead.dev/minisign v0.2.0 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 // indirect + github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 // indirect + github.com/VividCortex/ewma v1.2.0 // indirect + github.com/akrylysov/pogreb v0.10.1 // indirect + github.com/alecthomas/chroma v0.10.0 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/aymerick/douceur v0.2.0 // indirect + github.com/charmbracelet/glamour v0.6.0 // indirect + github.com/cheggaaa/pb/v3 v3.1.4 // indirect + github.com/cloudflare/circl v1.3.7 // indirect + github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 // indirect + github.com/denisbrodbeck/machineid v1.0.1 // indirect + github.com/dimchansky/utfbom v1.1.1 // indirect + github.com/dlclark/regexp2 v1.8.1 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect + github.com/fatih/color v1.15.0 // indirect + github.com/gaukas/godicttls v0.0.4 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-github/v30 v30.1.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/css v1.0.1 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/lucasb-eyer/go-colorful v1.2.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mholt/archiver/v3 v3.5.1 // indirect + github.com/microcosm-cc/bluemonday v1.0.26 // indirect + github.com/miekg/dns v1.1.57 // indirect + github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/muesli/reflow v0.3.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect + github.com/nwaples/rardecode v1.1.3 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pierrec/lz4/v4 v4.1.2 // indirect + github.com/projectdiscovery/blackrock v0.0.1 // indirect + github.com/projectdiscovery/fastdialer v0.0.55 // indirect + github.com/projectdiscovery/hmap v0.0.35 // indirect + github.com/projectdiscovery/networkpolicy v0.0.7 // indirect + github.com/projectdiscovery/retryabledns v1.0.52 // indirect + github.com/projectdiscovery/retryablehttp-go v1.0.44 // indirect + github.com/quic-go/quic-go v0.37.7 // indirect + github.com/refraction-networking/utls v1.5.4 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect + github.com/syndtr/goleveldb v1.0.0 // indirect + github.com/tidwall/btree v1.4.3 // indirect + github.com/tidwall/buntdb v1.3.0 // indirect + github.com/tidwall/gjson v1.14.3 // indirect + github.com/tidwall/grect v0.1.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/tidwall/rtred v0.1.2 // indirect + github.com/tidwall/tinyqueue v0.1.1 // indirect + github.com/ulikunitz/xz v0.5.11 // indirect + github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 // indirect + github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db // indirect + github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/yl2chen/cidranger v1.0.2 // indirect + github.com/yuin/goldmark v1.5.4 // indirect + github.com/yuin/goldmark-emoji v1.0.1 // indirect + github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 // indirect + github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 // indirect + go.etcd.io/bbolt v1.3.7 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.11.0 // indirect + golang.org/x/sys v0.16.0 // indirect + golang.org/x/term v0.16.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.16.1 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.31.0 // indirect + gopkg.in/djherbis/times.v1 v1.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7c4ccd1 --- /dev/null +++ b/go.sum @@ -0,0 +1,388 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= +cloud.google.com/go/compute/metadata v0.2.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057 h1:KFac3SiGbId8ub47e7kd2PLZeACxc1LkiiNoDOFRClE= +github.com/Mzack9999/gcache v0.0.0-20230410081825-519e28eab057/go.mod h1:iLB2pivrPICvLOuROKmlqURtFIEsoJZaMidQfCG1+D4= +github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809 h1:ZbFL+BDfBqegi+/Ssh7im5+aQfBRx6it+kHnC7jaDU8= +github.com/Mzack9999/go-http-digest-auth-client v0.6.1-0.20220414142836-eb8883508809/go.mod h1:upgc3Zs45jBDnBT4tVRgRcgm26ABpaP7MoTSdgysca4= +github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow= +github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= +github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= +github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c= +github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/bits-and-blooms/bloom/v3 v3.5.0 h1:AKDvi1V3xJCmSR6QhcBfHbCN4Vf8FfxeWkMNQfmAGhY= +github.com/bits-and-blooms/bloom/v3 v3.5.0/go.mod h1:Y8vrn7nk1tPIlmLtW2ZPV+W7StdVMor6bC1xgpjMZFs= +github.com/charmbracelet/glamour v0.6.0 h1:wi8fse3Y7nfcabbbDuwolqTqMQPMnVPeZhDM273bISc= +github.com/charmbracelet/glamour v0.6.0/go.mod h1:taqWV4swIMMbWALc0m7AfE9JkPSU8om2538k9ITBxOc= +github.com/cheggaaa/pb/v3 v3.1.4 h1:DN8j4TVVdKu3WxVwcRKu0sG00IIU6FewoABZzXbRQeo= +github.com/cheggaaa/pb/v3 v3.1.4/go.mod h1:6wVjILNBaXMs8c21qRiaUM8BR82erfgau1DQ4iUXmSA= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08 h1:ox2F0PSMlrAAiAdknSRMDrAr8mfxPCfSZolH+/qQnyQ= +github.com/cnf/structhash v0.0.0-20201127153200-e1b16c1ebc08/go.mod h1:pCxVEbcm3AMg7ejXyorUXi6HQCzOIBf7zEDVPtw0/U4= +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/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMSRhl4D7AQ= +github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI= +github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.8.1 h1:6Lcdwya6GjPUNsBct8Lg/yRPwMhABj269AAzdGSiR+0= +github.com/dlclark/regexp2 v1.8.1/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= +github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg= +github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gaukas/godicttls v0.0.4 h1:NlRaXb3J6hAnTmWdsEKb9bcSBD6BvcIjdGdeb0zfXbk= +github.com/gaukas/godicttls v0.0.4/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= +github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v30 v30.1.0 h1:VLDx+UolQICEOKu2m4uAoMti1SxuEBAl7RSEG16L+Oo= +github.com/google/go-github/v30 v30.1.0/go.mod h1:n8jBpHl45a/rlBUtRJMOG4GhNADUQFEufcolZ95JfU8= +github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= +github.com/hashicorp/golang-lru/v2 v2.0.6 h1:3xi/Cafd1NaoEnS/yDssIiuVeDVywU0QdFGl3aQaQHM= +github.com/hashicorp/golang-lru/v2 v2.0.6/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jedib0t/go-pretty/v6 v6.4.7 h1:lwiTJr1DEkAgzljsUsORmWsVn5MQjt1BPJdPCtJ6KXE= +github.com/jedib0t/go-pretty/v6 v6.4.7/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= +github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= +github.com/microcosm-cc/bluemonday v1.0.21/go.mod h1:ytNkv4RrDrLJ2pqlsSI46O6IVXmZOBBD4SaJyDwwTkM= +github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58= +github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs= +github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= +github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7 h1:yRZGarbxsRytL6EGgbqK2mCY+Lk5MWKQYKJT2gEglhc= +github.com/minio/selfupdate v0.6.1-0.20230907112617-f11e74f84ca7/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/mreiferson/go-httpclient v0.0.0-20201222173833-5e475fde3a4d/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= +github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= +github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= +github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= +github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= +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/projectdiscovery/blackrock v0.0.1 h1:lHQqhaaEFjgf5WkuItbpeCZv2DUIE45k0VbGJyft6LQ= +github.com/projectdiscovery/blackrock v0.0.1/go.mod h1:ANUtjDfaVrqB453bzToU+YB4cUbvBRpLvEwoWIwlTss= +github.com/projectdiscovery/fastdialer v0.0.55 h1:dcD3La9MsImgQMrBnG0/w5Mu8PRJu2TU1STycKSSodc= +github.com/projectdiscovery/fastdialer v0.0.55/go.mod h1:DNP62sWCLp0YHXwhlo73iyZODpSZE7dVstt2GNAC7+A= +github.com/projectdiscovery/goflags v0.1.36 h1:gElwVU9BJsUbxjyHqDTmlGsB8Br2DDxbfMQMXLYvYhg= +github.com/projectdiscovery/goflags v0.1.36/go.mod h1:A+MLWJgGKZ2WUED0ZlW5EQ4mmJ/s71VnvY6KF5ThLaM= +github.com/projectdiscovery/gologger v1.1.12 h1:uX/QkQdip4PubJjjG0+uk5DtyAi1ANPJUvpmimXqv4A= +github.com/projectdiscovery/gologger v1.1.12/go.mod h1:DI8nywPLERS5mo8QEA9E7gd5HZ3Je14SjJBH3F5/kLw= +github.com/projectdiscovery/hmap v0.0.35 h1:JkadBpuB/GttuS+O72E26y6RrC8Ox90iFunrI2/zvrc= +github.com/projectdiscovery/hmap v0.0.35/go.mod h1:EXm6Z/e10GS0uK7qNLH2OcT0bIKq+T4ZDWxSzK0ho5U= +github.com/projectdiscovery/networkpolicy v0.0.7 h1:AwHqBRXBqDQgnWzBMuoJtHBNEYBw+NFp/4qIK688x7o= +github.com/projectdiscovery/networkpolicy v0.0.7/go.mod h1:CK0CnFoLF1Nou6mY7P4WODSAxhPN8g8g7XpapgEP8tI= +github.com/projectdiscovery/retryabledns v1.0.52 h1:jJRIT5y7KYZvaZAAvlkxvkKkQzst6LvEeLDqRc3LeOM= +github.com/projectdiscovery/retryabledns v1.0.52/go.mod h1:Ea478e6XNVAmfH4KwqtLNjkwdgkpVH1O3+FL2dKLNb8= +github.com/projectdiscovery/retryablehttp-go v1.0.44 h1:hicCe2h6daHt4muPovmffZE3YKBqGioreO6EpIGZ87g= +github.com/projectdiscovery/retryablehttp-go v1.0.44/go.mod h1:7ECXK2cH2/G4sstf8hacyrMdPPJ/3wCAO5tFPZ4iO4s= +github.com/projectdiscovery/utils v0.0.74 h1:mrjRm1eF3ZLIikIpWF3Nzbar/twHHqhz9WSi9fqGQeA= +github.com/projectdiscovery/utils v0.0.74/go.mod h1:4MBUFfZ9Mm96PiWUj2zJ99sx2AVOpZkGukC6O16+p+o= +github.com/quic-go/quic-go v0.37.7 h1:AgKsQLZ1+YCwZd2GYhBUsJDYZwEkA5gENtAjb+MxONU= +github.com/quic-go/quic-go v0.37.7/go.mod h1:YsbH1r4mSHPJcLF4k4zruUkLBqctEMBDR6VPvcYjIsU= +github.com/refraction-networking/utls v1.5.4 h1:9k6EO2b8TaOGsQ7Pl7p9w6PUhx18/ZCeT0WNTZ7Uw4o= +github.com/refraction-networking/utls v1.5.4/go.mod h1:SPuDbBmgLGp8s+HLNc83FuavwZCFoMmExj+ltUHiHUw= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA= +github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= +github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= +github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI= +github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8= +github.com/tidwall/btree v1.4.3 h1:Lf5U/66bk0ftNppOBjVoy/AIPBrLMkheBp4NnSNiYOo= +github.com/tidwall/btree v1.4.3/go.mod h1:LGm8L/DZjPLmeWGjv5kFrY8dL4uVhMmzmmLYmsObdKE= +github.com/tidwall/buntdb v1.3.0 h1:gdhWO+/YwoB2qZMeAU9JcWWsHSYU3OvcieYgFRS0zwA= +github.com/tidwall/buntdb v1.3.0/go.mod h1:lZZrZUWzlyDJKlLQ6DKAy53LnG7m5kHyrEHvvcDmBpU= +github.com/tidwall/gjson v1.12.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= +github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/grect v0.1.4 h1:dA3oIgNgWdSspFzn1kS4S/RDpZFLrIxAZOdJKjYapOg= +github.com/tidwall/grect v0.1.4/go.mod h1:9FBsaYRaR0Tcy4UwefBX/UDcDcDy9V5jUcxHzv2jd5Q= +github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8= +github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8= +github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ= +github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE= +github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw= +github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= +github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6 h1:TtyC78WMafNW8QFfv3TeP3yWNDG+uxNkk9vOrnDu6JA= +github.com/ulule/deepcopier v0.0.0-20200430083143-45decc6639b6/go.mod h1:h8272+G2omSmi30fBXiZDMkmHuOgonplfKIKjQWzlfs= +github.com/weppos/publicsuffix-go v0.13.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k= +github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db h1:/WcxBne+5CbtbgWd/sV2wbravmr4sT7y52ifQaCgoLs= +github.com/weppos/publicsuffix-go v0.30.1-0.20230422193905-8fecedd899db/go.mod h1:aiQaH1XpzIfgrJq3S1iw7w+3EDbRP7mF5fmwUhWyRUs= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= +github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU= +github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.2/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU= +github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= +github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248 h1:Nzukz5fNOBIHOsnP+6I79kPx3QhLv8nBy2mfFhBRq30= +github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= +github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is= +github.com/zmap/zcertificate v0.0.1/go.mod h1:q0dlN54Jm4NVSSuzisusQY0hqDWvu92C+TWveAxiVWk= +github.com/zmap/zcrypto v0.0.0-20201128221613-3719af1573cf/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-20201211161100-e54a5822fb7e/go.mod h1:aPM7r+JOkfL+9qSB4KbYjtoEzJqUK50EXkkJabeNJDQ= +github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968 h1:YOQ1vXEwE4Rnj+uQ/3oCuJk5wgVsvUyW+glsndwYuyA= +github.com/zmap/zcrypto v0.0.0-20230422215203-9a665e1e9968/go.mod h1:xIuOvYCZX21S5Z9bK1BMrertTGX/F8hgAPw7ERJRNS0= +github.com/zmap/zlint/v3 v3.0.0/go.mod h1:paGwFySdHIBEMJ61YjoqT4h7Ge+fdYG4sUQhnTb1lJ8= +go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= +go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/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-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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.11.0 h1:vPL4xzxBM4niKCW6g9whtaWVXTJf1U5e4aZxxFx/gbU= +golang.org/x/oauth2 v0.11.0/go.mod h1:LdF7O/8bLR/qWK9DrpXmbHLTouvRHK0SgJl0GmDBchk= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/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-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.16.0 h1:m+B6fahuftsE9qjo0VWp2FW0mB3MTJvR0BaMQrq0pmE= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/text v0.3.0/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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= +golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/djherbis/times.v1 v1.3.0 h1:uxMS4iMtH6Pwsxog094W0FYldiNnfY/xba00vq6C2+o= +gopkg.in/djherbis/times.v1 v1.3.0/go.mod h1:AQlg6unIsrsCEdQYhTzERy542dz6SFdQFZFv6mUY0P8= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/runner/banner.go b/runner/banner.go new file mode 100644 index 0000000..ce02bd4 --- /dev/null +++ b/runner/banner.go @@ -0,0 +1,40 @@ +package runner + +import ( + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/utils/auth/pdcp" + updateutils "github.com/projectdiscovery/utils/update" +) + +const banner = ` + + ______ _____ ____ ___ ____ ____ + / ___/ | / / _ \/ __ \__ \/ __ \/ __ \ + / /__ | |/ / __/ / / / / / /_/ / /_/ / + \___/ |___/\___/_/ /_/ /_/\__,_/ .___/ + /_/ + +` + +// Version is the current version +const Version = `v0.0.1` + +// showBanner is used to show the banner to the user +func showBanner() { + gologger.Print().Msgf("%s\n", banner) + gologger.Print().Msgf("\t\tprojectdiscovery.io\n\n") +} + +// GetUpdateCallback returns a callback function that updates proxify +func GetUpdateCallback() func() { + return func() { + showBanner() + updateutils.GetUpdateToolCallback("cvemap", Version)() + } +} + +// AuthWithPDCP is used to authenticate with PDCP +func AuthWithPDCP() { + showBanner() + pdcp.CheckNValidateCredentials("cvemap") +} diff --git a/runner/options.go b/runner/options.go new file mode 100644 index 0000000..b777b62 --- /dev/null +++ b/runner/options.go @@ -0,0 +1,43 @@ +package runner + +import "github.com/projectdiscovery/goflags" + +type Options struct { + PdcpAuth bool + CveIds goflags.StringSlice + CweIds goflags.StringSlice + Vendor goflags.StringSlice + Product goflags.StringSlice + Eproduct goflags.StringSlice + Severity goflags.StringSlice + CvssScore goflags.StringSlice + //cvssMetrics goflags.StringSlice + EpssPercentile goflags.StringSlice + //year goflags.StringSlice + Assignees goflags.StringSlice + Reference goflags.StringSlice + //vulnType goflags.StringSlice + IncludeColumns []string + ExcludeColumns []string + TableHeaders []string + ListId bool + EpssScore string + Cpe string + VulnStatus string + Age string + Kev string + //trending bool + Hackerone string + HasNucleiTemplate string + HasPoc string + Search string + RemotlyExploitable string + EnablePageKeys bool + Json bool + Limit int + Offset int + Version bool + DisableUpdateCheck bool + Silent bool + Verbose bool +} diff --git a/runner/runner.go b/runner/runner.go new file mode 100644 index 0000000..1b4aff0 --- /dev/null +++ b/runner/runner.go @@ -0,0 +1,865 @@ +package runner + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strconv" + "strings" + "sync" + + "github.com/eiannone/keyboard" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/projectdiscovery/goflags" + "github.com/projectdiscovery/gologger" + "github.com/projectdiscovery/gologger/levels" + "github.com/projectdiscovery/utils/auth/pdcp" + errorutil "github.com/projectdiscovery/utils/errors" + fileutil "github.com/projectdiscovery/utils/file" + sliceutil "github.com/projectdiscovery/utils/slice" + updateutils "github.com/projectdiscovery/utils/update" +) + +const xPDCPKeyHeader = "X-PDCP-Key" + +var ( + baseUrl = "https://cve.projectdiscovery.io/api/v1" + httpCleint = &http.Client{} + pdcpApiKey = "" + DEFAULT_FEILD_CHAR_LIMIT = 20 +) + +func init() { + if os.Getenv("CVEMAP_API_URL") != "" { + baseUrl = os.Getenv("CVEMAP_API_URL") + } + pch := pdcp.PDCPCredHandler{} + if os.Getenv("PDCP_API_KEY") != "" { + pdcpApiKey = os.Getenv("PDCP_API_KEY") + } else if creds, err := pch.GetCreds(); err == nil { + pdcpApiKey = creds.APIKey + } + if os.Getenv("DEFAULT_FEILD_CHAR_LIMIT") != "" { + DEFAULT_FEILD_CHAR_LIMIT, _ = strconv.Atoi(os.Getenv("DEFAULT_FEILD_CHAR_LIMIT")) + } +} + +var ( + defaultHeaders = []string{"ID", "CVSS", "Severity", "EPSS", "Product", "Age", "Template"} + + headerMap = map[string]string{ + "id": "id", + "cwe": "cwe", + "epss": "epss", + "cvss": "cvss", + "severity": "severity", + "vendor": "vendor", + "product": "product", + "vstatus": "vstatus", + "assignee": "assignee", + "age": "age", + "kev": "kev", + "template": "template", + "poc": "poc", + "rank": "rank", + "reports": "reports", + } + + allowedHeader = goflags.AllowdTypes{ + "": goflags.EnumVariable(-1), + "cwe": goflags.EnumVariable(0), + "epss": goflags.EnumVariable(1), + "product": goflags.EnumVariable(2), + "vendor": goflags.EnumVariable(3), + "vstatus": goflags.EnumVariable(4), + "assignee": goflags.EnumVariable(5), + "age": goflags.EnumVariable(6), + "kev": goflags.EnumVariable(7), + "template": goflags.EnumVariable(8), + "poc": goflags.EnumVariable(9), + } + allowedHeaderString = allowedHeader.String() + + allowedVstatus = goflags.AllowdTypes{ + "": goflags.EnumVariable(-1), + "new": goflags.EnumVariable(0), + "confirmed": goflags.EnumVariable(1), + "unconfirmed": goflags.EnumVariable(2), + "modified": goflags.EnumVariable(3), + "rejected": goflags.EnumVariable(4), + "unknown": goflags.EnumVariable(5), + } + + maxLimit = 300 +) + +func ParseOptions() *Options { + var options Options + + flagset := goflags.NewFlagSet() + flagset.SetDescription(`Navigate the CVE jungle with ease.`) + + flagset.CreateGroup("config", "Config", + flagset.BoolVar(&options.PdcpAuth, "auth", false, "configure projectdiscovery cloud (pdcp) api key"), + ) + + flagset.CreateGroup("OPTIONS", "options", + // currently only one cve id is supported + flagset.StringSliceVar(&options.CveIds, "id", nil, "cve to list for given id", goflags.FileCommaSeparatedStringSliceOptions), + // flagset.StringSliceVarP(&options.cweIds, "cwe-id", "cwe", nil, "cve to list for given cwe id", goflags.CommaSeparatedStringSliceOptions), + flagset.StringSliceVarP(&options.Vendor, "vendor", "v", nil, "cve to list for given vendor", goflags.CommaSeparatedStringSliceOptions), + flagset.StringSliceVarP(&options.Product, "product", "p", nil, "cve to list for given product", goflags.CommaSeparatedStringSliceOptions), + flagset.StringSliceVar(&options.Eproduct, "eproduct", nil, "cves to exclude based on products", goflags.CommaSeparatedStringSliceOptions), + flagset.StringSliceVarP(&options.Severity, "severity", "s", nil, "cve to list for given severity", goflags.CommaSeparatedStringSliceOptions), + flagset.StringSliceVarP(&options.CvssScore, "cvss-score", "cs", nil, "cve to list for given cvss score", goflags.CommaSeparatedStringSliceOptions), + flagset.StringVarP(&options.Cpe, "cpe", "c", "", "cve to list for given cpe"), + flagset.StringVarP(&options.EpssScore, "epss-score", "es", "", "cve to list for given epss score"), + flagset.StringSliceVarP(&options.EpssPercentile, "epss-percentile", "ep", nil, "cve to list for given epss percentile", goflags.CommaSeparatedStringSliceOptions), + flagset.StringVar(&options.Age, "age", "", "cve to list published by given age in days"), + flagset.StringSliceVarP(&options.Assignees, "assignee", "a", nil, "cve to list for given publisher assignee", goflags.CommaSeparatedStringSliceOptions), + //flagset.StringSliceVarP(&options.vulnType, "type", "vt", nil, "cve to list for given vulnerability type", goflags.CommaSeparatedStringSliceOptions), + flagset.EnumVarP(&options.VulnStatus, "vstatus", "vs", goflags.EnumVariable(-1), strings.Replace(fmt.Sprintf("cve to list for given vulnerability status in cli output. supported: %s", allowedVstatus.String()), " ,", "", -1), allowedVstatus), + ) + + flagset.CreateGroup("update", "Update", + flagset.CallbackVarP(GetUpdateCallback(), "update", "up", "update cvemap to latest version"), + flagset.BoolVarP(&options.DisableUpdateCheck, "disable-update-check", "duc", false, "disable automatic cvemap update check"), + ) + + flagset.CreateGroup("FILTER", "filter", + flagset.StringVarP(&options.Search, "search", "q", "", "search in cve data"), + flagset.DynamicVarP(&options.Kev, "kev", "k", "true", "display cves marked as exploitable vulnerabilities by cisa"), + flagset.DynamicVarP(&options.HasNucleiTemplate, "template", "t", "true", "display cves that has public nuclei templates"), + flagset.DynamicVar(&options.HasPoc, "poc", "true", "display cves that has public published poc"), + flagset.DynamicVarP(&options.Hackerone, "hackerone", "h1", "true", "display cves reported on hackerone"), + flagset.DynamicVarP(&options.RemotlyExploitable, "remote", "re", "true", "display remotely exploitable cves (AV:N & PR:N | PR:L)"), + ) + + flagset.CreateGroup("OUTPUT", "output", + flagset.EnumSliceVarP(&options.IncludeColumns, "field", "f", []goflags.EnumVariable{goflags.EnumVariable(-1)}, strings.Replace(fmt.Sprintf("fields to display in cli output. supported: %s", allowedHeaderString), " ,", "", -1), allowedHeader), + flagset.EnumSliceVarP(&options.ExcludeColumns, "exclude", "fe", []goflags.EnumVariable{goflags.EnumVariable(-1)}, strings.Replace(fmt.Sprintf("fields to exclude from cli output. supported: %s", allowedHeaderString), " ,", "", -1), allowedHeader), + flagset.BoolVarP(&options.ListId, "list-id", "lsi", false, "list only the cve ids in the output"), + flagset.IntVarP(&options.Limit, "limit", "l", 50, "limit the number of results to display"), + flagset.IntVar(&options.Offset, "offset", 0, "offset the results to display"), + flagset.BoolVarP(&options.Json, "json", "j", false, "return output in json format"), + // experimental + flagset.BoolVarP(&options.EnablePageKeys, "enable-page-keys", "epk", false, "enable page keys to navigate results"), + ) + + flagset.CreateGroup("DEBUG", "debug", + flagset.BoolVar(&options.Version, "version", false, "Version"), + flagset.BoolVar(&options.Silent, "silent", false, "Silent"), + flagset.BoolVar(&options.Verbose, "verbose", false, "Verbose"), + ) + + if err := flagset.Parse(); err != nil { + gologger.Fatal().Msgf("Error parsing flags: %s\n", err) + } + + if options.Limit > maxLimit { + options.Limit = maxLimit + } + + if fileutil.HasStdin() { + // Read from stdin + bin, err := io.ReadAll(os.Stdin) + if err != nil { + gologger.Fatal().Msgf("couldn't read stdin: %s\n", err) + } + options.CveIds = append(options.CveIds, strings.Split(strings.TrimSpace(string(bin)), "\n")...) + } + + return &options +} + +func Run(options Options) { + + if options.Version { + gologger.Info().Msgf("Current Version: %s\n", Version) + os.Exit(0) + } + + if options.PdcpAuth { + AuthWithPDCP() + } + + if options.Silent { + gologger.DefaultLogger.SetMaxLevel(levels.LevelSilent) + } else if options.Verbose { + gologger.DefaultLogger.SetMaxLevel(levels.LevelVerbose) + } + + // Show the user the banner + showBanner() + + if !options.DisableUpdateCheck { + latestVersion, err := updateutils.GetToolVersionCallback("cvemap", Version)() + if err != nil { + if options.Verbose { + gologger.Error().Msgf("cvemap version check failed: %v", err.Error()) + } + } else { + gologger.Info().Msgf("Current cvemap version %v %v", Version, updateutils.GetVersionDescription(Version, latestVersion)) + } + } + + // on default, enable kev + if isDefaultRun(options) { + options.Kev = "true" + } + + parseHeaders(&options) + + if options.EnablePageKeys && len(options.CveIds) == 0 { + processWithPageKeyEvents(options) + } else { + _ = process(options) + } +} + +func process(options Options) *CVEBulkData { + var cvesResp *CVEBulkData + var err error + cvesResp, err = getCves(options) + if err != nil { + gologger.Fatal().Msgf("Error getting CVEs: %s\n", err) + return nil + } + + if options.Json { + outputJson(cvesResp.Cves) + return cvesResp + } + + nPages := cvesResp.TotalResults / options.Limit + if cvesResp.TotalResults%options.Limit > 0 { + nPages++ + } + currentPage := (options.Offset / options.Limit) + 1 + if len(options.CveIds) == 0 && (options.Verbose || options.EnablePageKeys) { + gologger.Print().Msgf("\n Limit: %v Page: %v TotalPages: %v TotalResults: %v\n", options.Limit, currentPage, nPages, cvesResp.TotalResults) + } + + if options.ListId { + for _, cve := range cvesResp.Cves { + fmt.Println(cve.CveID) + } + return cvesResp + } + + // limit headers to 10, otherwise it will be too wide + if len(options.TableHeaders) > 10 { + options.TableHeaders = options.TableHeaders[:10] + } + + headers, rows := generateTableData(cvesResp.Cves, options.TableHeaders) + + renderTable(headers, rows) + + if options.EnablePageKeys { + pageString := "" + if currentPage > 1 { + pageString += " ◀ " + } + if currentPage < nPages { + pageString += " ▶" + } + fmt.Print(pageString) + } + return cvesResp +} + +func processWithPageKeyEvents(options Options) { + cveResp := process(options) + + // wait for user input + err := keyboard.Open() + if err != nil { + panic(err) + } + defer keyboard.Close() + waitGroup := sync.WaitGroup{} + waitGroup.Add(1) + + go func() { + for { + _, key, err := keyboard.GetKey() + if err != nil { + panic(err) + } + + if key == keyboard.KeyEsc || key == keyboard.KeyCtrlC { + waitGroup.Done() + break + } + + switch key { + case keyboard.KeyArrowRight: + if options.Offset+options.Limit < cveResp.TotalResults { + options.Offset += options.Limit + clearScreen() + cveResp = process(options) + } + case keyboard.KeyArrowLeft: + if options.Offset-options.Limit >= 0 { + options.Offset -= options.Limit + clearScreen() + cveResp = process(options) + } + } + } + }() + + waitGroup.Wait() +} + +func parseHeaders(options *Options) { + // construct headers + headers := make([]string, 0) + + if options.Hackerone == "true" { + defaultHeaders = []string{"ID", "CVSS", "Severity", "Rank", "Reports", "Product", "Age", "Template"} + } + + options.IncludeColumns = getValidHeaders(options.IncludeColumns) + options.ExcludeColumns = getValidHeaders(options.ExcludeColumns) + + options.IncludeColumns = append(defaultHeaders, options.IncludeColumns...) + // case insensitive contains check + contains := func(array []string, element string) bool { + for _, e := range array { + if strings.EqualFold(e, element) { + return true + } + } + return false + } + // add headers to display + for _, header := range options.IncludeColumns { + if !contains(options.ExcludeColumns, header) && !contains(headers, header) { + headers = append(headers, header) + } + } + options.TableHeaders = headers +} + +func renderTable(headers []string, rows [][]interface{}) { + // Create a table for displaying CVE data with the specified headers + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + + // Append the specified headers to the table + headerRow := make([]interface{}, len(headers)) + for i, header := range headers { + headerRow[i] = header + } + t.AppendHeader(headerRow) + // Loop through the retrieved CVE data and add rows to the table + for _, row := range rows { + t.AppendRow(row) + } + // t.SetColumnConfigs([]table.ColumnConfig{ + // {Number: 5, WidthMax: 20}, + // }) + // Set table options and render it + t.SetStyle(table.StyleRounded) + t.Render() +} + +func generateTableData(cves []CVEData, headers []string) ([]string, [][]interface{}) { + dataRows := make([][]interface{}, len(cves)) + for r, cve := range cves { + dataRows[r] = getRow(headers, cve) + } + return headers, dataRows +} + +func getRow(headers []string, cve CVEData) []interface{} { + row := make([]interface{}, len(headers)) + for i, header := range headers { + switch strings.ToLower(header) { + case "id": + row[i] = getCellValueByLimit(cve.CveID) + case "epss": + row[i] = getCellValueByLimit(cve.Epss.Score) + case "cvss": + row[i] = "" + if cve.CvssMetrics != nil { + row[i] = getCellValueByLimit(getLatestVersionCVSSScore(*cve.CvssMetrics)) + } + case "severity": + row[i] = getCellValueByLimit(strings.ToTitle(cve.Severity)) + case "cwe": + row[i] = "" + if len(cve.Weaknesses) > 0 { + row[i] = getCellValueByLimit(cve.Weaknesses[0].CWEID) + } + case "vendor": + row[i] = "" + if cve.Cpe != nil { + row[i] = getCellValueByLimit(*cve.Cpe.Vendor) + } + case "product": + row[i] = "" + if cve.Cpe != nil { + row[i] = getCellValueByLimit(*cve.Cpe.Product) + } + case "vstatus": + row[i] = getCellValueByLimit(strings.ToUpper(cve.VulnStatus)) + case "assignee": + row[i] = "" + if len(cve.Assignee) > 0 { + row[i] = getCellValueByLimit(cve.Assignee) + } + case "age": + row[i] = "" + if cve.AgeInDays > 0 { + row[i] = getCellValueByLimit(cve.AgeInDays) + } + case "kev": + row[i] = getCellValueByLimit(strings.ToUpper(strconv.FormatBool(cve.IsKev))) + case "template": + if cve.IsTemplate { + row[i] = "✅" + } else { + row[i] = "❌" + } + case "poc": + row[i] = getCellValueByLimit(strings.ToUpper(strconv.FormatBool(cve.IsPoc))) + case "rank": + row[i] = "" + if cve.Hackerone.Rank > 0 { + row[i] = getCellValueByLimit(cve.Hackerone.Rank) + } + case "reports": + row[i] = getCellValueByLimit(cve.Hackerone.Count) + + default: + row[i] = "" + } + } + return row +} + +func getCellValueByLimit(cell interface{}) string { + if cell == nil { + return "" + } + cellValue := fmt.Sprintf("%v", cell) + if len(cellValue) > DEFAULT_FEILD_CHAR_LIMIT { + cellValue = cellValue[:DEFAULT_FEILD_CHAR_LIMIT] + "..." + } + return cellValue +} + +func getCves(options Options) (*CVEBulkData, error) { + if len(options.CveIds) > 0 { + return getCvesByIds(options.CveIds) + } + if options.Search != "" { + query := constructQueryByOptions(options) + return getCvesBySearchString(query, options.Limit, options.Offset) + } + return getCvesByFilters(constructQueryParams(options)) +} + +func getCvesByFilters(encodedParams string) (*CVEBulkData, error) { + url := fmt.Sprintf("%s/cves?%s", baseUrl, encodedParams) + // Send an HTTP GET request + response, err := makeGetRequest(url) + if err != nil { + return nil, err + } + defer response.Body.Close() + // Check the response status code + if response.StatusCode != http.StatusOK { + return nil, errorutil.New("unexpected status code: %d", response.StatusCode) + } + // Create a variable to store the response data + var cvesInBulk CVEBulkData + // Decode the JSON response into an array of CVEData structs + err = json.NewDecoder(response.Body).Decode(&cvesInBulk) + if err != nil { + return nil, err + } + return &cvesInBulk, nil +} + +func getCvesByIds(cveIds []string) (*CVEBulkData, error) { + url := fmt.Sprintf("%s/cves", baseUrl) + // send only 100 cve ids max + if len(cveIds) > 100 { + cveIds = cveIds[:100] + } + var cveIdList CVEIdList + cveIdList.Cves = append(cveIdList.Cves, cveIds...) + reqData, err := json.Marshal(cveIdList) + if err != nil { + return nil, err + } + // Send an HTTP POST request + req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqData)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/json") + req.Header.Set(xPDCPKeyHeader, pdcpApiKey) + + response, err := doRequest(req) + if err != nil { + return nil, err + } + defer response.Body.Close() + // Check the response status code + if response.StatusCode != http.StatusOK { + return nil, errorutil.New("unexpected status code: %d", response.StatusCode) + } + var cvesInBulk CVEBulkData + // Decode the JSON response into an array of CVEData structs + err = json.NewDecoder(response.Body).Decode(&cvesInBulk) + if err != nil { + return nil, err + } + return &cvesInBulk, nil +} + +func getCvesBySearchString(query string, limit, offset int) (*CVEBulkData, error) { + u, err := url.Parse(fmt.Sprintf("%s/cves/search", baseUrl)) + if err != nil { + return nil, err + } + // Construct query parameters + q := u.Query() + q.Set("q", query) + q.Set("limit", fmt.Sprintf("%v", limit)) + q.Set("offset", fmt.Sprintf("%v", offset)) + u.RawQuery = q.Encode() + response, err := makeGetRequest(u.String()) + if err != nil { + return nil, err + } + defer response.Body.Close() + // Check the response status code + if response.StatusCode != http.StatusOK { + return nil, errorutil.New("unexpected status code: %d", response.StatusCode) + } + // Create a variable to store the response data + var cvesInBulk CVEBulkData + // Decode the JSON response into an array of CVEData structs + err = json.NewDecoder(response.Body).Decode(&cvesInBulk) + if err != nil { + return nil, err + } + return &cvesInBulk, nil +} + +// all the root level fields are supported +// func getCvesForSpecificFields(fields []string, encodedParams string, limit, offset int) (*CVEBulkData, error) { +// url := fmt.Sprintf("%s/cves?fields=%s&%s&limit=%v&offset=%v", baseUrl, strings.Join(fields, ","), encodedParams, limit, offset) +// // Send an HTTP GET request +// response, err := makeGetRequest(url) +// if err != nil { +// return nil, err +// } +// defer response.Body.Close() +// // Check the response status code +// if response.StatusCode != http.StatusOK { +// return nil, errorutil.New("unexpected status code: %d", response.StatusCode) +// } +// // Create a variable to store the response data +// var cvesInBulk CVEBulkData +// // Decode the JSON response into an array of CVEData structs +// err = json.NewDecoder(response.Body).Decode(&cvesInBulk) +// if err != nil { +// return nil, err +// } +// return &cvesInBulk, nil +// } + +var UNAUTHORIZEDERR = errorutil.New(`unexpected status code: 401 (get your free api key from https://cloud.projectdiscovery.io)`) + +func makeGetRequest(url string) (*http.Response, error) { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + gologger.Fatal().Msgf("Error creating request: %s\n", err) + } + req.Header.Set(xPDCPKeyHeader, pdcpApiKey) + return doRequest(req) +} + +func doRequest(req *http.Request) (*http.Response, error) { + if os.Getenv("DEBUG") == "true" { + // dump request + dump, err := httputil.DumpRequest(req, true) + if err != nil { + gologger.Fatal().Msgf("Error dumping request: %s\n", err) + } + fmt.Println(string(dump)) + } + resp, err := httpCleint.Do(req) + if err == nil && resp.StatusCode == http.StatusUnauthorized { + var errResp ErrorMessage + _ = json.NewDecoder(resp.Body).Decode(&errResp) + if os.Getenv("DEBUG") == "true" { + gologger.Error().Msgf("unauthorized: %s\n", errResp.Message) + } + return nil, UNAUTHORIZEDERR + } + return resp, err +} + +func outputJson(cve []CVEData) { + json, err := json.MarshalIndent(cve, "", " ") + if err != nil { + gologger.Error().Msgf("Error marshalling json: %s\n", err) + return + } + fmt.Println(string(json)) +} + +func constructQueryParams(opts Options) string { + queryParams := &url.Values{} + if len(opts.Severity) > 0 { + addQueryParams(queryParams, "severity", opts.Severity) + } + if len(opts.Assignees) > 0 { + addQueryParams(queryParams, "assignee", opts.Assignees) + } + if len(opts.CvssScore) > 0 { + var cvsKey string + for _, cvssScore := range opts.CvssScore { + cvsKey = "cvss_score" + if cvssScore[0] == '>' { + cvsKey = "cvss_score_gte" + cvssScore = strings.TrimSpace(cvssScore[1:]) + } else if cvssScore[0] == '<' { + cvsKey = "cvss_score_lte" + cvssScore = strings.TrimSpace(cvssScore[1:]) + } + queryParams.Add(cvsKey, cvssScore) + } + } + + if len(opts.Age) > 0 { + ageKey := "age_in_days" + if opts.Age[0] == '>' { + ageKey = "age_in_days_gte" + opts.Age = strings.TrimSpace(opts.Age[1:]) + } else if opts.Age[0] == '<' { + ageKey = "age_in_days_lte" + opts.Age = strings.TrimSpace(opts.Age[1:]) + } + queryParams.Add(ageKey, opts.Age) + } + if len(opts.VulnStatus) > 0 { + queryParams.Add("vuln_status", strings.ToLower(opts.VulnStatus)) + } + if len(opts.Reference) > 0 { + addQueryParams(queryParams, "reference", opts.Reference) + } + if len(opts.EpssScore) > 0 { + epssKey := "epss.epss_score" + if opts.EpssScore[0] == '>' { + epssKey = "epss.epss_score_gte" + opts.EpssScore = strings.TrimSpace(opts.EpssScore[1:]) + } else if opts.EpssScore[0] == '<' { + epssKey = "epss.epss_score_lte" + opts.EpssScore = strings.TrimSpace(opts.EpssScore[1:]) + } + queryParams.Add(epssKey, opts.EpssScore) + } + if len(opts.EpssPercentile) > 0 { + var epKey string + for _, ep := range opts.EpssPercentile { + epKey = "epss.epss_percentile" + if ep[0] == '>' { + epKey = "epss.epss_percentile_gte" + ep = strings.TrimSpace(ep[1:]) + } else if ep[0] == '<' { + epKey = "epss.epss_percentile_lte" + ep = strings.TrimSpace(ep[1:]) + } + queryParams.Add(epKey, ep) + } + } + if len(opts.CweIds) > 0 { + addQueryParams(queryParams, "cwe_id", opts.CweIds) + } + if len(opts.Cpe) > 0 { + queryParams.Add("cpe.cpe", opts.Cpe) + } + if len(opts.Product) > 0 { + addQueryParams(queryParams, "cpe.product", opts.Product) + } + if len(opts.Eproduct) > 0 { + addQueryParams(queryParams, "cpe.product_ne", opts.Eproduct) + } + if len(opts.Vendor) > 0 { + addQueryParams(queryParams, "cpe.vendor", opts.Vendor) + } + if opts.Kev == "true" { + queryParams.Add("is_exploited", "true") + } else if opts.Kev == "false" { + queryParams.Add("is_exploited", "false") + } + if opts.HasNucleiTemplate == "true" { + queryParams.Add("is_template", "true") + } else if opts.HasNucleiTemplate == "false" { + queryParams.Add("is_template", "false") + } + if opts.HasPoc == "true" { + queryParams.Add("is_poc", "true") + } else if opts.HasPoc == "false" { + queryParams.Add("is_poc", "false") + } + if opts.Hackerone == "true" { + queryParams.Add("hackerone.rank_gte", "1") + queryParams.Add("sort_asc", "hackerone.rank") + } else { + queryParams.Add("sort_desc", "cve_id") + } + if opts.RemotlyExploitable == "true" { + queryParams.Add("is_remote", "true") + } + if opts.Limit > 0 { + queryParams.Add("limit", strconv.Itoa(opts.Limit)) + } + if opts.Offset >= 0 { + queryParams.Add("offset", strconv.Itoa(opts.Offset)) + } + return queryParams.Encode() +} + +func constructQueryByOptions(opts Options) string { + query := opts.Search + if len(opts.Vendor) > 0 { + query = fmt.Sprintf("%s cpe.vendor:%s", query, strings.Join(opts.Vendor, ",")) + } + if len(opts.Product) > 0 { + query = fmt.Sprintf("%s cpe.product:%s", query, strings.Join(opts.Product, ",")) + } + if len(opts.Eproduct) > 0 { + query = fmt.Sprintf("%s cpe.product_ne:%s", query, strings.Join(opts.Eproduct, ",")) + } + if len(opts.Severity) > 0 { + query = fmt.Sprintf("%s severity:%s", query, strings.Join(opts.Severity, ",")) + } + if len(opts.CvssScore) > 0 { + var cvsKey string + for _, cvssScore := range opts.CvssScore { + cvsKey = "cvss_score" + if cvssScore[0] == '>' { + cvsKey = "cvss_score_gte" + cvssScore = strings.TrimSpace(cvssScore[1:]) + } else if cvssScore[0] == '<' { + cvsKey = "cvss_score_lte" + cvssScore = strings.TrimSpace(cvssScore[1:]) + } + query = fmt.Sprintf("%s %s:%s", query, cvsKey, cvssScore) + } + } + if len(opts.EpssScore) > 0 { + epssKey := "epss.epss_score" + if opts.EpssScore[0] == '>' { + epssKey = "epss.epss_score_gte" + opts.EpssScore = strings.TrimSpace(opts.EpssScore[1:]) + } else if opts.EpssScore[0] == '<' { + epssKey = "epss.epss_score_lte" + opts.EpssScore = strings.TrimSpace(opts.EpssScore[1:]) + } + query = fmt.Sprintf("%s %s:%s", query, epssKey, opts.EpssScore) + } + if len(opts.EpssPercentile) > 0 { + var epKey string + for _, ep := range opts.EpssPercentile { + epKey = "epss.epss_percentile" + if ep[0] == '>' { + epKey = "epss.epss_percentile_gte" + ep = strings.TrimSpace(ep[1:]) + } else if ep[0] == '<' { + epKey = "epss.epss_percentile_lte" + ep = strings.TrimSpace(ep[1:]) + } + query = fmt.Sprintf("%s %s:%s", query, epKey, ep) + } + } + if len(opts.Cpe) > 0 { + query = fmt.Sprintf(`%s cpe.cpe:"%s"`, query, opts.Cpe) + } + if len(opts.CweIds) > 0 { + query = fmt.Sprintf("%s cwe_id:%s", query, strings.Join(opts.CweIds, ",")) + } + if len(opts.Age) > 0 { + ageKey := "age_in_days" + if opts.Age[0] == '>' { + ageKey = "age_in_days_gte" + opts.Age = strings.TrimSpace(opts.Age[1:]) + } else if opts.Age[0] == '<' { + ageKey = "age_in_days_lte" + opts.Age = strings.TrimSpace(opts.Age[1:]) + } + query = fmt.Sprintf("%s %s:%s", query, ageKey, opts.Age) + } + if len(opts.Assignees) > 0 { + query = fmt.Sprintf("%s assignee:%s", query, strings.Join(opts.Assignees, ",")) + } + if len(opts.VulnStatus) > 0 { + query = fmt.Sprintf("%s vuln_status:%s", query, strings.ToLower(opts.VulnStatus)) + } + if opts.Kev == "true" { + query = fmt.Sprintf("%s is_exploited:true", query) + } else if opts.Kev == "false" { + query = fmt.Sprintf("%s is_exploited:false", query) + } + if opts.HasNucleiTemplate == "true" { + query = fmt.Sprintf("%s is_template:true", query) + } else if opts.HasNucleiTemplate == "false" { + query = fmt.Sprintf("%s is_template:false", query) + } + if opts.HasPoc == "true" { + query = fmt.Sprintf("%s is_poc:true", query) + } else if opts.HasPoc == "false" { + query = fmt.Sprintf("%s is_poc:false", query) + } + if opts.Hackerone == "true" { + query = fmt.Sprintf("%s hackerone.rank_gte:1 sort_asc:hackerone.rank", query) + } else { + query = fmt.Sprintf("%s sort_desc:cve_id", query) + } + if opts.RemotlyExploitable == "true" { + query = fmt.Sprintf("%s is_remote:true", query) + } + + parts := strings.Split(query, " ") + parts = sliceutil.PruneEmptyStrings(parts) + parts = sliceutil.Dedupe(parts) + query = strings.Join(parts, " ") + if os.Getenv("DEBUG") == "true" { + fmt.Println("constructed query: ", query) + } + return query +} + +func addQueryParams(queryParams *url.Values, key string, values []string) *url.Values { + if len(values) > 0 { + for _, value := range values { + queryParams.Add(key, value) + } + } + return queryParams +} + +func getValidHeaders(keys []string) []string { + headers := []string{} + for _, hk := range keys { + if v, ok := headerMap[hk]; ok { + headers = append(headers, v) + } + } + return headers +} diff --git a/runner/testutils/util.go b/runner/testutils/util.go new file mode 100644 index 0000000..0765d22 --- /dev/null +++ b/runner/testutils/util.go @@ -0,0 +1,38 @@ +package testutils + +import ( + "fmt" + "os" + "os/exec" + "strings" +) + +// RunCvemapßBinaryAndGetResults returns a list of the results +func RunCvemapBinaryAndGetResults(cvemapBinary string, debug bool, args []string) ([]string, error) { + cmd := exec.Command("bash", "-c") + cmdLine := fmt.Sprintf(`./%s `, cvemapBinary) + cmdLine += strings.Join(args, " ") + if debug { + os.Setenv("DEBUG", "1") + cmd.Stderr = os.Stderr + } + cmd.Args = append(cmd.Args, cmdLine) + data, err := cmd.Output() + if err != nil { + return nil, err + } + parts := []string{} + items := strings.Split(string(data), "\n") + for _, i := range items { + if i != "" { + parts = append(parts, i) + } + } + return parts, nil +} + +// TestCase is a single integration test case +type TestCase interface { + // Execute executes a test case and returns any errors if occurred + Execute() error +} diff --git a/runner/types.go b/runner/types.go new file mode 100644 index 0000000..89b05cf --- /dev/null +++ b/runner/types.go @@ -0,0 +1,143 @@ +package runner + +import "time" + +type CVEBulkData struct { + ResultCount int `json:"result_count"` + TotalResults int `json:"total_results"` + Cves []CVEData `json:"cves"` +} + +type CVEData struct { + CveID string `json:"cve_id,omitempty"` + CveDescription string `json:"cve_description,omitempty"` + Severity string `json:"severity,omitempty"` + CvssScore float64 `json:"cvss_score,omitempty"` + CvssMetrics *CvssMetrics `json:"cvss_metrics,omitempty"` + Weaknesses []struct { + CWEID string `json:"cwe_id"` + CWEName string `json:"cwe_name,omitempty"` + } `json:"weaknesses,omitempty"` + Epss struct { + Score float64 `json:"epss_score"` + Percentile float64 `json:"epss_percentile"` + } `json:"epss,omitempty"` + Cpe *OutputCpe `json:"cpe,omitempty"` + Reference []string `json:"reference,omitempty"` + Poc []struct { + URL string `json:"url"` + Source string `json:"source"` + AddedAt string `json:"added_at"` + } `json:"poc,omitempty"` + VendorAdvisory *string `json:"vendor_advisory,omitempty"` + Patch []string `json:"patch_url,omitempty"` + IsTemplate bool `json:"is_template"` + NucleiTemplates *NucleiTemplates `json:"nuclei_templates,omitempty"` + IsKev bool `json:"is_exploited"` + Kev *KevObject `json:"kev,omitempty"` + Assignee string `json:"assignee,omitempty"` + PublishedAt string `json:"published_at,omitempty"` + UpdatedAt string `json:"updated_at,omitempty"` + Hackerone struct { + Rank int `json:"rank"` + Count int `json:"count"` + } `json:"hackerone,omitempty"` + AgeInDays int `json:"age_in_days,omitempty"` + VulnStatus string `json:"vuln_status,omitempty"` + IsPoc bool `json:"is_poc"` + IsRemote bool `json:"is_remote"` + IsOss bool `json:"is_oss"` + VulnerableCPE []string `json:"vulnerable_cpe,omitempty"` + Shodan *OutputShodanData `json:"shodan,omitempty"` + OSS *OSS `json:"oss,omitempty"` +} + +type CvssMetrics struct { + Cvss2 *Cvss2 `json:"cvss2,omitempty"` + Cvss30 *Cvss30 `json:"cvss30,omitempty"` + Cvss31 *Cvss31 `json:"cvss31,omitempty"` +} + +type Cvss2 struct { + Score float64 `json:"score"` + Vector string `json:"vector"` + Severity string `json:"severity"` +} + +type Cvss30 struct { + Score float64 `json:"score"` + Vector string `json:"vector"` + Severity string `json:"severity"` +} + +type Cvss31 struct { + Score float64 `json:"score"` + Vector string `json:"vector"` + Severity string `json:"severity"` +} + +type NucleiTemplates struct { + TemplateIssue string `json:"template_issue,omitempty"` + TemplateIssueType string `json:"template_issue_type,omitempty"` + TemplatePath string `json:"template_path,omitempty"` + TemplatePR string `json:"template_pr,omitempty"` + TemplateURL string `json:"template_url,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` +} + +type OSS struct { + AllLanguages map[string]int `json:"all_languages,omitempty"` + Description string `json:"description,omitempty"` + Forks int `json:"forks,omitempty"` + Language string `json:"language,omitempty"` + Stars int `json:"stars,omitempty"` + Subscribers int `json:"subscribers,omitempty"` + Topics []string `json:"topics,omitempty"` + PushedAt CustomTime `json:"pushed_at,omitempty"` + CreatedAt CustomTime `json:"created_at,omitempty"` + UpdatedAt CustomTime `json:"updated_at,omitempty"` + URL string `json:"url,omitempty"` +} + +type CustomTime struct { + time.Time +} + +func (ct *CustomTime) UnmarshalJSON(b []byte) error { + s := string(b) + if s == "null" { + return nil + } + t, err := time.Parse(`"2006-01-02 15:04:05 -0700 MST"`, s) + if err != nil { + return err + } + ct.Time = t + return nil +} + +type OutputCpe struct { + Cpe *string `json:"cpe,omitempty"` + Vendor *string `json:"vendor,omitempty"` + Product *string `json:"product,omitempty"` + Platform *string `json:"framework,omitempty"` +} + +type KevObject struct { + AddedDate string `json:"added_date"` + DueDate string `json:"due_date"` +} + +type OutputShodanData struct { + Count int `json:"count"` + Query []string `json:"query"` +} + +type ErrorMessage struct { + Message string `json:"message"` +} + +type CVEIdList struct { + Cves []string `json:"cves"` +} diff --git a/runner/util.go b/runner/util.go new file mode 100644 index 0000000..642a67c --- /dev/null +++ b/runner/util.go @@ -0,0 +1,41 @@ +package runner + +import ( + "os" + "os/exec" + "runtime" + + fileutil "github.com/projectdiscovery/utils/file" +) + +func getLatestVersionCVSSScore(cvss CvssMetrics) float64 { + var highestScore float64 + if cvss.Cvss2 != nil { + highestScore = cvss.Cvss2.Score + } + if cvss.Cvss30 != nil { + highestScore = cvss.Cvss30.Score + } + if cvss.Cvss31 != nil { + highestScore = cvss.Cvss31.Score + } + return highestScore +} + +func isDefaultRun(opts Options) bool { + options := len(opts.CveIds) == 0 && len(opts.CweIds) == 0 && len(opts.Vendor) == 0 && len(opts.Product) == 0 && len(opts.Severity) == 0 && len(opts.CvssScore) == 0 && len(opts.EpssPercentile) == 0 && len(opts.Assignees) == 0 && len(opts.Reference) == 0 && opts.EpssScore == "" && opts.Cpe == "" && opts.VulnStatus == "" && opts.Age == "" + filters := opts.Kev == "" && opts.Hackerone == "" && opts.HasNucleiTemplate == "" && opts.HasPoc == "" && opts.RemotlyExploitable == "" + return options && filters && !fileutil.HasStdin() +} + +// clearScreen clears the terminal screen +func clearScreen() { + var cmd *exec.Cmd + if runtime.GOOS == "windows" { + cmd = exec.Command("cls") + } else { + cmd = exec.Command("clear") + } + cmd.Stdout = os.Stdout + _ = cmd.Run() +} diff --git a/static/cvemap.png b/static/cvemap.png new file mode 100644 index 0000000..edf8e92 Binary files /dev/null and b/static/cvemap.png differ