yellow~ opinionated web scanning pipeline for pentesters in a hurry |
| git clone https://github.com/djnnvx/yellow.git |
| log | files | refs | archive | README | LICENSE |
cvemap.go (4854B)
1 package scan 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "net/url" 8 "os" 9 "strings" 10 "time" 11 12 "evil.djnn.sh/djnn/yellow/core" 13 helper "evil.djnn.sh/djnn/yellow/helpers" 14 ) 15 16 // NVD rate limits: 5 req/30s without key, 50 req/30s with key. 17 // We sleep conservatively between queries to avoid 403s. 18 const ( 19 nvdDelayNoKey = 7 * time.Second 20 nvdDelayWithKey = 1 * time.Second 21 ) 22 23 const nvdBaseURL = "https://services.nvd.nist.gov/rest/json/cves/2.0" 24 25 type Cvemap struct { 26 HTTPProxy string 27 scanPath string 28 Limit int 29 Offset int 30 } 31 32 type nvdResponse struct { 33 TotalResults int `json:"totalResults"` 34 Vulnerabilities []nvdVuln `json:"vulnerabilities"` 35 } 36 37 type nvdVuln struct { 38 CVE nvdCVE `json:"cve"` 39 } 40 41 type nvdCVE struct { 42 ID string `json:"id"` 43 Published string `json:"published"` 44 LastModified string `json:"lastModified"` 45 Descriptions []nvdDesc `json:"descriptions"` 46 Metrics nvdMetrics `json:"metrics"` 47 Weaknesses []nvdWeak `json:"weaknesses"` 48 References []nvdRef `json:"references"` 49 } 50 51 type nvdDesc struct { 52 Lang string `json:"lang"` 53 Value string `json:"value"` 54 } 55 56 type nvdMetrics struct { 57 CVSSv31 []nvdCVSS `json:"cvssMetricV31,omitempty"` 58 CVSSv30 []nvdCVSS `json:"cvssMetricV30,omitempty"` 59 CVSSv2 []nvdCVSS `json:"cvssMetricV2,omitempty"` 60 } 61 62 type nvdCVSS struct { 63 Source string `json:"source"` 64 Type string `json:"type"` 65 CVSSData nvdCVSSData `json:"cvssData"` 66 } 67 68 type nvdCVSSData struct { 69 Version string `json:"version"` 70 BaseScore float64 `json:"baseScore"` 71 BaseSeverity string `json:"baseSeverity"` 72 } 73 74 type nvdWeak struct { 75 Description []nvdDesc `json:"description"` 76 } 77 78 type nvdRef struct { 79 URL string `json:"url"` 80 Source string `json:"source"` 81 } 82 83 func (*Cvemap) Name() string { return "cvemap" } 84 85 func (c *Cvemap) Run(ctx *core.Context) error { 86 fmt.Println("[+] Running cvemap for", ctx.Domain) 87 if ctx.DryRun { 88 return nil 89 } 90 91 c.HTTPProxy = ctx.Proxy 92 c.scanPath = ctx.ScanPath 93 94 techs := ctx.Techs 95 if len(techs) == 0 { 96 fmt.Println("[+] no technologies provided") 97 return nil 98 } 99 100 client := &http.Client{Timeout: 30 * time.Second} 101 if c.HTTPProxy != "" { 102 proxyURL, err := url.Parse(c.HTTPProxy) 103 if err != nil { 104 fmt.Printf("[ERROR] invalid proxy URL: %v\n", err) 105 return nil 106 } 107 client.Transport = &http.Transport{Proxy: http.ProxyURL(proxyURL)} 108 } 109 110 apiKey := os.Getenv("NVD_API_KEY") 111 delay := nvdDelayNoKey 112 if apiKey != "" { 113 delay = nvdDelayWithKey 114 } 115 116 limit := c.Limit 117 if limit <= 0 { 118 limit = 50 119 } 120 121 var allCVEs []nvdCVE 122 123 queried := 0 124 for _, tech := range techs { 125 tech = strings.TrimSpace(tech) 126 if tech == "" { 127 continue 128 } 129 130 // wappalyzer formats versioned results as "TechName:version" — skip unversioned ones 131 // since NVD results without a version are too noisy to be actionable 132 colonIdx := strings.Index(tech, ":") 133 if colonIdx == -1 { 134 fmt.Printf("[+] skipping %s (no version detected)\n", tech) 135 continue 136 } 137 name := tech[:colonIdx] 138 version := tech[colonIdx+1:] 139 query := name + " " + version 140 141 if queried > 0 { 142 time.Sleep(delay) 143 } 144 queried++ 145 146 fmt.Println("[+] Running cvemap for", query) 147 148 cves, total, err := c.queryCVEs(client, apiKey, query, limit, c.Offset) 149 if err != nil { 150 fmt.Printf("[ERROR] query for '%s' failed: %v\n", tech, err) 151 continue 152 } 153 154 fmt.Printf("[SCAN %s] total results: %d (returned: %d)\n\n", query, total, len(cves)) 155 allCVEs = append(allCVEs, cves...) 156 } 157 158 c.saveResults(allCVEs) 159 return nil 160 } 161 162 func (c *Cvemap) queryCVEs(client *http.Client, apiKey, keyword string, limit, offset int) ([]nvdCVE, int, error) { 163 params := url.Values{} 164 params.Set("keywordSearch", keyword) 165 params.Set("resultsPerPage", fmt.Sprintf("%d", limit)) 166 params.Set("startIndex", fmt.Sprintf("%d", offset)) 167 168 reqURL := fmt.Sprintf("%s?%s", nvdBaseURL, params.Encode()) 169 170 req, err := http.NewRequest("GET", reqURL, nil) 171 if err != nil { 172 return nil, 0, err 173 } 174 if apiKey != "" { 175 req.Header.Set("apiKey", apiKey) 176 } 177 178 resp, err := client.Do(req) 179 if err != nil { 180 return nil, 0, err 181 } 182 defer resp.Body.Close() 183 184 if resp.StatusCode != http.StatusOK { 185 body, _, _ := helper.ReadCappedBody(resp.Body) 186 return nil, 0, fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) 187 } 188 189 var nvdResp nvdResponse 190 if err := json.NewDecoder(resp.Body).Decode(&nvdResp); err != nil { 191 return nil, 0, err 192 } 193 194 cves := make([]nvdCVE, len(nvdResp.Vulnerabilities)) 195 for i, v := range nvdResp.Vulnerabilities { 196 cves[i] = v.CVE 197 } 198 199 return cves, nvdResp.TotalResults, nil 200 } 201 202 func (c *Cvemap) saveResults(cves []nvdCVE) { 203 data, err := json.MarshalIndent(cves, "", "\t") 204 if err != nil { 205 return 206 } 207 208 filename := c.scanPath + "/cves.json" 209 if err := os.WriteFile(filename, data, 0644); err != nil { 210 fmt.Printf("[!] cvemap: could not write %s: %v\n", filename, err) 211 } 212 }