diff --git a/CHANGELOG.txt b/CHANGELOG.txt
@@ -1,5 +1,7 @@
## v0.0.5
+- fix(scan): replace dead cvemap/vulnx API with NVD API v2 ~ @djnnvx
+- chore(deps): drop github.com/projectdiscovery/cvemap dependency ~ @djnnvx
- fix(osint): only IPs should be passed to shodan ~ @djnnvx
- feat(osint): adding support for leaker ~ @djnnvx
- feat(scan): adding port-scanning ~ @djnnvx
diff --git a/README.md b/README.md
@@ -156,11 +156,17 @@ some domains that are not reachable anymore. To filter them out, you can run:
#### Retrieving CVEs automatically:
-We use vulnx( https://github.com/projectdiscovery/cvemap) under the hood to query CVEs based on
-initial fingerprinting. To get this feature working, you will need to create an account
-on ProjectDiscovery (https://cloud.projectdiscovery.io/) and retrieve an API key.
+CVE lookups use the [NVD API v2](https://nvd.nist.gov/developers/vulnerabilities) (NIST National
+Vulnerability Database) — no account required. Results are queried by detected technology name and
+saved to `cves.json` in your scan path.
-Then you should set this key in your .bashrc (or equivalent) to VULNX_API_KEY.
+Without an API key, NVD allows 5 requests per 30 seconds (yellow sleeps 7s between queries to stay
+safe). For faster scans, grab a free key at https://nvd.nist.gov/developers/request-an-api-key
+and set it:
+
+```bash
+export NVD_API_KEY=your-key-here
+```
#### Credential Leak Checking (Leaker)
diff --git a/go.mod b/go.mod
@@ -6,7 +6,6 @@ require (
github.com/OJ/gobuster/v3 v3.8.2
github.com/bogdzn/gork v0.0.5
github.com/praetorian-inc/nerva v1.0.0
- github.com/projectdiscovery/cvemap v1.0.0
github.com/projectdiscovery/dnsx v1.2.3
github.com/projectdiscovery/goflags v0.1.74
github.com/projectdiscovery/httpx v1.9.0
@@ -38,6 +37,7 @@ require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/bits-and-blooms/bitset v1.24.4 // indirect
+ github.com/bits-and-blooms/bloom/v3 v3.6.0 // indirect
github.com/bodgit/plumbing v1.3.0 // indirect
github.com/bodgit/sevenzip v1.6.1 // indirect
github.com/bodgit/windows v1.0.1 // indirect
@@ -64,7 +64,6 @@ require (
github.com/docker/go-units v0.5.0 // indirect
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
- github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/gaissmai/bart v0.26.0 // indirect
github.com/go-faker/faker/v4 v4.7.0 // indirect
@@ -94,7 +93,6 @@ require (
github.com/iangcarroll/cookiemonster v1.6.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 // indirect
- github.com/jedib0t/go-pretty/v6 v6.7.8 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kataras/jwt v0.1.17 // indirect
github.com/kennygrant/sanitize v1.2.4 // indirect
diff --git a/go.sum b/go.sum
@@ -54,6 +54,7 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp
github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA=
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.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
github.com/bits-and-blooms/bitset v1.24.4/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
@@ -133,8 +134,6 @@ github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj6
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
-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.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@@ -227,8 +226,6 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2 h1:36qep4gxKs+JgeHGWeQ040RyZdt9kQlLglL1rFVn/oQ=
github.com/ishidawataru/sctp v0.0.0-20251114114122-19ddcbc6aae2/go.mod h1:co9pwDoBCm1kGxawmb4sPq0cSIOOWNPT4KnHotMP1Zg=
-github.com/jedib0t/go-pretty/v6 v6.7.8 h1:BVYrDy5DPBA3Qn9ICT+PokP9cvCv1KaHv2i+Hc8sr5o=
-github.com/jedib0t/go-pretty/v6 v6.7.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
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=
@@ -347,8 +344,6 @@ github.com/projectdiscovery/chaos-client v0.5.2 h1:dN+7GXEypsJAbCD//dBcUxzAEAEH1
github.com/projectdiscovery/chaos-client v0.5.2/go.mod h1:KnoJ/NJPhll42uaqlDga6oafFfNw5l2XI2ajRijtDuU=
github.com/projectdiscovery/clistats v0.1.1 h1:8mwbdbwTU4aT88TJvwIzTpiNeow3XnAB72JIg66c8wE=
github.com/projectdiscovery/clistats v0.1.1/go.mod h1:4LtTC9Oy//RiuT1+76MfTg8Hqs7FQp1JIGBM3nHK6a0=
-github.com/projectdiscovery/cvemap v1.0.0 h1:0PVbaWozUNM4oislukPdeKrmeQKbdGwhWGpRNBQ5tpg=
-github.com/projectdiscovery/cvemap v1.0.0/go.mod h1:QQ/tnIoUewT+l9h5yaJyEgwmKEPjjY+0de/RJOGyDis=
github.com/projectdiscovery/dnsx v1.2.3 h1:S87U9kYuuqqvMFyen8mZQy1FMuR5EGCsXHqfHPQAeuc=
github.com/projectdiscovery/dnsx v1.2.3/go.mod h1:NjAEyJt6+meNqZqnYHL4ZPxXfysuva+et56Eq/e1cVE=
github.com/projectdiscovery/dsl v0.8.13 h1:HjjHta7c02saH2tUGs8CN5vDeE2MyWvCV32koT8ZCWs=
@@ -489,6 +484,7 @@ github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9R
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e h1:tD38/4xg4nuQCASJ/JxcvCHNb46w0cdAaJfkzQOO1bA=
github.com/tomnomnom/linkheader v0.0.0-20250811210735-e5fe3b51442e/go.mod h1:krvJ5AY/MjdPkTeRgMYbIDhbbbVvnPQPzsIsDJO8xrY=
+github.com/twmb/murmur3 v1.1.6/go.mod h1:Qq/R7NUyOfr65zD+6Q5IHKsJLwP7exErjN6lyyq3OSQ=
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
diff --git a/inventory/quick-run.sh b/inventory/quick-run.sh
@@ -11,4 +11,4 @@ set -euo
./yellow prune -f ${1}/scans/domains.txt -o ${1}/scans/active-web-panels.txt
-./yellow scan -d ${1}/scans/infra --file ${1}/scans/active-web-panels.txt
+./yellow scan --port-scan -d ${1}/scans/infra --file ${1}/scans/active-web-panels.txt
diff --git a/scan/cvemap.go b/scan/cvemap.go
@@ -3,13 +3,23 @@ package scan
import (
"encoding/json"
"fmt"
+ "io"
+ "net/http"
+ "net/url"
"os"
"strings"
+ "time"
+)
- "github.com/projectdiscovery/cvemap/pkg/runner"
- "github.com/projectdiscovery/cvemap/pkg/types"
+// NVD rate limits: 5 req/30s without key, 50 req/30s with key.
+// We sleep conservatively between queries to avoid 403s.
+const (
+ nvdDelayNoKey = 7 * time.Second
+ nvdDelayWithKey = 1 * time.Second
)
+const nvdBaseURL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
+
type Cvemap struct {
HTTPProxy string
scanPath string
@@ -19,6 +29,57 @@ type Cvemap struct {
Debug bool
}
+type nvdResponse struct {
+ TotalResults int `json:"totalResults"`
+ Vulnerabilities []nvdVuln `json:"vulnerabilities"`
+}
+
+type nvdVuln struct {
+ CVE nvdCVE `json:"cve"`
+}
+
+type nvdCVE struct {
+ ID string `json:"id"`
+ Published string `json:"published"`
+ LastModified string `json:"lastModified"`
+ Descriptions []nvdDesc `json:"descriptions"`
+ Metrics nvdMetrics `json:"metrics"`
+ Weaknesses []nvdWeak `json:"weaknesses"`
+ References []nvdRef `json:"references"`
+}
+
+type nvdDesc struct {
+ Lang string `json:"lang"`
+ Value string `json:"value"`
+}
+
+type nvdMetrics struct {
+ CVSSv31 []nvdCVSS `json:"cvssMetricV31,omitempty"`
+ CVSSv30 []nvdCVSS `json:"cvssMetricV30,omitempty"`
+ CVSSv2 []nvdCVSS `json:"cvssMetricV2,omitempty"`
+}
+
+type nvdCVSS struct {
+ Source string `json:"source"`
+ Type string `json:"type"`
+ CVSSData nvdCVSSData `json:"cvssData"`
+}
+
+type nvdCVSSData struct {
+ Version string `json:"version"`
+ BaseScore float64 `json:"baseScore"`
+ BaseSeverity string `json:"baseSeverity"`
+}
+
+type nvdWeak struct {
+ Description []nvdDesc `json:"description"`
+}
+
+type nvdRef struct {
+ URL string `json:"url"`
+ Source string `json:"source"`
+}
+
func (c *Cvemap) Info(target string) {
fmt.Println("[+] Running cvemap for", target)
}
@@ -50,64 +111,114 @@ func (c *Cvemap) Configure(cfg any) {
}
func (c *Cvemap) Run(techs []string) {
- runner.PDCPApiKey = os.Getenv("VULNX_API_KEY")
- if runner.PDCPApiKey == "" {
- println("[!] Automatic CVE research requires VULNX_API_KEY to be set in env...skipping")
+ if len(techs) == 0 {
+ fmt.Println("[+] no technologies provided")
return
}
- options := runner.Options{
- HTTPProxy: c.HTTPProxy,
- Limit: c.Limit,
- Offset: c.Offset,
- Verbose: c.Verbose,
- Debug: c.Debug,
+ client := &http.Client{}
+ if c.HTTPProxy != "" {
+ proxyURL, err := url.Parse(c.HTTPProxy)
+ if err != nil {
+ fmt.Printf("[ERROR] invalid proxy URL: %v\n", err)
+ return
+ }
+ client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)}
}
- rn, err := runner.New(&options)
- if err != nil {
- panic(err)
+ apiKey := os.Getenv("NVD_API_KEY")
+ delay := nvdDelayNoKey
+ if apiKey != "" {
+ delay = nvdDelayWithKey
}
- if len(techs) == 0 {
- fmt.Println("[+] no technologies provided")
- return
+ limit := c.Limit
+ if limit <= 0 {
+ limit = 50
}
+ var allCVEs []nvdCVE
+
+ queried := 0
for _, tech := range techs {
tech = strings.TrimSpace(tech)
if tech == "" {
continue
}
- c.Info(tech)
+ // wappalyzer formats versioned results as "TechName:version" — skip unversioned ones
+ // since NVD results without a version are too noisy to be actionable
+ colonIdx := strings.Index(tech, ":")
+ if colonIdx == -1 {
+ fmt.Printf("[+] skipping %s (no version detected)\n", tech)
+ continue
+ }
+ name := tech[:colonIdx]
+ version := tech[colonIdx+1:]
+ query := name + " " + version
+
+ if queried > 0 {
+ time.Sleep(delay)
+ }
+ queried++
- rn.Options.Search = tech
+ c.Info(query)
- cvesResp, err := rn.GetCves()
+ cves, total, err := c.queryCVEs(client, apiKey, query, limit, c.Offset)
if err != nil {
fmt.Printf("[ERROR] query for '%s' failed: %v\n", tech, err)
continue
}
- if cvesResp == nil {
- fmt.Printf("[SCAN %s] no results\n\n", tech)
- continue
- }
- total := 0
- if cvesResp.TotalResults > 0 {
- total = cvesResp.TotalResults
- } else if cvesResp.Cves != nil {
- total = len(cvesResp.Cves)
- }
+ fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", query, total, len(cves))
+ allCVEs = append(allCVEs, cves...)
+ }
+
+ c.saveResults(allCVEs)
+}
+
+func (c *Cvemap) queryCVEs(client *http.Client, apiKey, keyword string, limit, offset int) ([]nvdCVE, int, error) {
+ params := url.Values{}
+ params.Set("keywordSearch", keyword)
+ params.Set("resultsPerPage", fmt.Sprintf("%d", limit))
+ params.Set("startIndex", fmt.Sprintf("%d", offset))
- c.saveResults(cvesResp.Cves)
- fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", tech, total, len(cvesResp.Cves))
+ reqURL := fmt.Sprintf("%s?%s", nvdBaseURL, params.Encode())
+
+ req, err := http.NewRequest("GET", reqURL, nil)
+ if err != nil {
+ return nil, 0, err
}
+ if apiKey != "" {
+ req.Header.Set("apiKey", apiKey)
+ }
+
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, 0, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ body, _ := io.ReadAll(resp.Body)
+ return nil, 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
+ }
+
+ var nvdResp nvdResponse
+ if err := json.NewDecoder(resp.Body).Decode(&nvdResp); err != nil {
+ return nil, 0, err
+ }
+
+ cves := make([]nvdCVE, len(nvdResp.Vulnerabilities))
+ for i, v := range nvdResp.Vulnerabilities {
+ cves[i] = v.CVE
+ }
+
+ return cves, nvdResp.TotalResults, nil
}
-func (c *Cvemap) saveResults(names []types.CVEData) {
- data, err := json.MarshalIndent(names, "", "\t")
+func (c *Cvemap) saveResults(cves []nvdCVE) {
+ data, err := json.MarshalIndent(cves, "", "\t")
if err != nil {
return
}
| | | | | |