github.com/khulnasoft-lab/tunnel-db@v0.0.0-20231117205118-74e1113bd007/pkg/vulnsrc/node/node.go (about)

     1  package node
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	bolt "go.etcd.io/bbolt"
    12  	"golang.org/x/xerrors"
    13  
    14  	"github.com/khulnasoft-lab/tunnel-db/pkg/db"
    15  	"github.com/khulnasoft-lab/tunnel-db/pkg/types"
    16  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/bucket"
    17  	"github.com/khulnasoft-lab/tunnel-db/pkg/vulnsrc/vulnerability"
    18  )
    19  
    20  const (
    21  	nodeDir = "nodejs-security-wg"
    22  )
    23  
    24  var (
    25  	source = types.DataSource{
    26  		ID:   vulnerability.NodejsSecurityWg,
    27  		Name: "Node.js Ecosystem Security Working Group",
    28  		URL:  "https://github.com/nodejs/security-wg",
    29  	}
    30  
    31  	bucketName = bucket.Name(vulnerability.Npm, source.Name)
    32  )
    33  
    34  type Number struct {
    35  	Value float64
    36  }
    37  
    38  // This is for Go 1.14+ compat, to support mixed strings of CVSSScores
    39  // In Node core CVSSScore is like: "4.8 (Medium)", Type string
    40  // In NPM package CVSSScore is like: 4.8, Type float64
    41  // Details: https://github.com/golang/go/issues/37308
    42  func (n *Number) UnmarshalJSON(b []byte) error {
    43  	var data interface{}
    44  	if err := json.Unmarshal(b, &data); err != nil {
    45  		return err
    46  	}
    47  
    48  	switch v := data.(type) {
    49  	case float64:
    50  		n.Value = v
    51  	case string:
    52  		f, err := strconv.ParseFloat(strings.Split(v, " ")[0], 64)
    53  		if err != nil {
    54  			return err
    55  		}
    56  		n.Value = f
    57  	default: // it can be null: https://github.com/nodejs/security-wg/blob/master/vuln/npm/334.json
    58  		n.Value = -1
    59  	}
    60  	return nil
    61  }
    62  
    63  type RawAdvisory struct {
    64  	ID                 int
    65  	Title              string
    66  	ModuleName         string `json:"module_name"`
    67  	Cves               []string
    68  	VulnerableVersions string `json:"vulnerable_versions"`
    69  	PatchedVersions    string `json:"patched_versions"`
    70  	Overview           string
    71  	Recommendation     string
    72  	References         []string
    73  	CvssScoreNumber    Number `json:"cvss_score"`
    74  	CvssScore          float64
    75  }
    76  
    77  type VulnSrc struct {
    78  	dbc db.Operation
    79  }
    80  
    81  func NewVulnSrc() VulnSrc {
    82  	return VulnSrc{
    83  		dbc: db.Config{},
    84  	}
    85  }
    86  
    87  func (vs VulnSrc) Name() types.SourceID {
    88  	return source.ID
    89  }
    90  
    91  func (vs VulnSrc) Update(dir string) error {
    92  	repoPath := filepath.Join(dir, nodeDir)
    93  	if err := vs.update(repoPath); err != nil {
    94  		return xerrors.Errorf("failed to update node vulnerabilities: %w", err)
    95  	}
    96  	return nil
    97  }
    98  
    99  func (vs VulnSrc) update(repoPath string) error {
   100  	root := filepath.Join(repoPath, "vuln")
   101  
   102  	err := vs.dbc.BatchUpdate(func(tx *bolt.Tx) error {
   103  		if err := vs.dbc.PutDataSource(tx, bucketName, source); err != nil {
   104  			return xerrors.Errorf("failed to put data source: %w", err)
   105  		}
   106  		if err := vs.walk(tx, root); err != nil {
   107  			return xerrors.Errorf("failed to walk node advisories: %w", err)
   108  		}
   109  		return nil
   110  	})
   111  	if err != nil {
   112  		return xerrors.Errorf("batch update failed: %w", err)
   113  	}
   114  	return nil
   115  }
   116  
   117  func (vs VulnSrc) walk(tx *bolt.Tx, root string) error {
   118  	return filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
   119  		if err != nil {
   120  			return err
   121  		}
   122  		if info.IsDir() || !strings.HasSuffix(info.Name(), ".json") {
   123  			return nil
   124  		}
   125  
   126  		f, err := os.Open(path)
   127  		if err != nil {
   128  			return err
   129  		}
   130  		defer f.Close()
   131  
   132  		return vs.commit(tx, f)
   133  	})
   134  }
   135  
   136  func (vs VulnSrc) commit(tx *bolt.Tx, f *os.File) error {
   137  	advisory := RawAdvisory{}
   138  	var err error
   139  	if err = json.NewDecoder(f).Decode(&advisory); err != nil {
   140  		return err
   141  	}
   142  
   143  	// Node.js itself
   144  	if advisory.ModuleName == "" {
   145  		return nil
   146  	}
   147  	advisory.ModuleName = strings.ToLower(advisory.ModuleName)
   148  
   149  	vulnerabilityIDs := advisory.Cves
   150  	if len(vulnerabilityIDs) == 0 {
   151  		vulnerabilityIDs = []string{fmt.Sprintf("NSWG-ECO-%d", advisory.ID)}
   152  	}
   153  
   154  	adv := convertToGenericAdvisory(advisory)
   155  	for _, vulnID := range vulnerabilityIDs {
   156  		// for detecting vulnerabilities
   157  		err = vs.dbc.PutAdvisoryDetail(tx, vulnID, advisory.ModuleName, []string{bucketName}, adv)
   158  		if err != nil {
   159  			return xerrors.Errorf("failed to save node advisory: %w", err)
   160  		}
   161  
   162  		// If an advisory is 0 override with -1
   163  		// https://github.com/nodejs/security-wg/pull/91/files
   164  		if advisory.CvssScoreNumber.Value <= 0 {
   165  			advisory.CvssScoreNumber.Value = -1
   166  		}
   167  
   168  		// for displaying vulnerability detail
   169  		vuln := types.VulnerabilityDetail{
   170  			ID:          vulnID,
   171  			CvssScore:   advisory.CvssScoreNumber.Value,
   172  			References:  advisory.References,
   173  			Title:       advisory.Title,
   174  			Description: advisory.Overview,
   175  		}
   176  		if err = vs.dbc.PutVulnerabilityDetail(tx, vulnID, source.ID, vuln); err != nil {
   177  			return xerrors.Errorf("failed to save node vulnerability detail: %w", err)
   178  		}
   179  
   180  		// for optimization
   181  		if err = vs.dbc.PutVulnerabilityID(tx, vulnID); err != nil {
   182  			return xerrors.Errorf("failed to save the vulnerability ID: %w", err)
   183  		}
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  func convertToGenericAdvisory(advisory RawAdvisory) types.Advisory {
   190  	var vulnerable, patched []string
   191  	if advisory.VulnerableVersions != "" {
   192  		for _, ver := range strings.Split(advisory.VulnerableVersions, "||") {
   193  			vulnerable = append(vulnerable, strings.TrimSpace(ver))
   194  		}
   195  	}
   196  	if advisory.PatchedVersions != "" {
   197  		for _, ver := range strings.Split(advisory.PatchedVersions, "||") {
   198  			patched = append(patched, strings.TrimSpace(ver))
   199  		}
   200  	}
   201  
   202  	return types.Advisory{
   203  		VulnerableVersions: vulnerable,
   204  		PatchedVersions:    patched,
   205  	}
   206  }