yellow.git


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 }