Skip to content

Instantly share code, notes, and snippets.

@tormath1
Last active May 2, 2021 11:39
Show Gist options
  • Save tormath1/224d7c89439f9ad8521e61ee90d13b9a to your computer and use it in GitHub Desktop.
Save tormath1/224d7c89439f9ad8521e61ee90d13b9a to your computer and use it in GitHub Desktop.
generateCVE summary from a CVE list

Usage example:

$ go run ./main.go -cvefile ./cves.txt | jq
[
  {
    "score": 5.9,
    "severity": "MEDIUM",
    "description": "A flaw was found in dnsmasq before version 2.83. A heap-based buffer overflow was discovered in dnsmasq when DNSSEC is enabled and before it validates the received DNS entries. A remote attacker, who can create valid DNS replies, could use this flaw to cause an overflow in a heap-allocated memory. This flaw is caused by the lack of length checks in rfc1035.c:extract_name(), which could be abused to make the code execute memcpy() with a negative size in get_rdata() and cause a crash in dnsmasq, resulting in a denial of service. The highest threat from this vulnerability is to system availability."
  },
  {
    "score": 7.8,
    "severity": "HIGH",
    "description": "An issue was discovered in the Linux kernel through 5.11.3. Certain iSCSI data structures do not have appropriate length constraints or checks, and can exceed the PAGE_SIZE value. An unprivileged user can send a Netlink message that is associated with iSCSI, and has a length up to the maximum length of a Netlink message."
  },
  {
    "score": 7.1,
    "severity": "HIGH",
    "description": "An issue was discovered in the Linux kernel through 5.11.3. drivers/scsi/scsi_transport_iscsi.c is adversely affected by the ability of an unprivileged user to craft Netlink messages."
  },
  {
    "score": 4.4,
    "severity": "MEDIUM",
    "description": "An issue was discovered in the Linux kernel through 5.11.3. A kernel pointer leak can be used to determine the address of the iscsi_transport structure. When an iSCSI transport is registered with the iSCSI subsystem, the transport's handle is available to unprivileged users via the sysfs file system, at /sys/class/iscsi_transport/$TRANSPORT_NAME/handle. When read, the show_transport_handle function (in drivers/scsi/scsi_transport_iscsi.c) is called, which leaks the handle. This handle is actually the pointer to an iscsi_transport struct in the kernel module's global variables."
  },
  {
    "score": 6.5,
    "severity": "MEDIUM",
    "description": "An issue was discovered in the Linux kernel through 5.11.3, as used with Xen PV. A certain part of the netback driver lacks necessary treatment of errors such as failed memory allocations (as a result of changes to the handling of grant mapping errors). A host OS denial of service may occur during misbehavior of a networking frontend driver. NOTE: this issue exists because of an incomplete fix for CVE-2021-26931."
  }
]
CVE-2020-25683
CVE-2021-27365
CVE-2021-27364
CVE-2021-27363
CVE-2021-28038
package main
import (
"bufio"
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"strconv"
"strings"
"golang.org/x/net/html"
)
const (
// baseURL is the base NIST URL to which we
// concatenate CVE id
baseURL = "https://nvd.nist.gov/vuln/detail"
)
var (
// CVELink is the URL links to the CVE
// on the NIST website
CVELink string
// CVEFile is the path of the file containing
// CVEs
CVEFile string
// walk is the function walking recursively into
// the HTML tree
walk func(*html.Node, *CVE) error
)
// CVE holds the CVE information
// we want to extract
type CVE struct {
// Score is the CVE score
Score float64 `json:"score"`
// Severity is the severity of the CVE (based on score)
Severity string `json:"severity"`
// Description is the current description of the CVE
Description string `json:"description"`
}
func init() {
flag.StringVar(&CVELink, "cvelink", "", "NIST URL of the CVE")
flag.StringVar(&CVEFile, "cvefile", "", "file containtaing CVEs NIST URL")
}
// fetchCVE download the NIST webpage of the CVE
func fetchCVE(id string) (*html.Node, error) {
resp, err := http.Get(id)
if err != nil {
return nil, fmt.Errorf("unable to get CVE webpage: %w", err)
}
defer resp.Body.Close()
page, err := html.Parse(resp.Body)
if err != nil {
return nil, fmt.Errorf("unable to parse CVE webpage: %w", err)
}
return page, nil
}
// extractCVE extracts CVE information from a NIST webpage
func extractCVE(n *html.Node) (*CVE, error) {
walk = func(n *html.Node, c *CVE) error {
if n.Type == html.ElementNode && n.Data == "a" {
for _, a := range n.Attr {
if a.Key == "id" && a.Val == "Cvss3NistCalculatorAnchor" {
child := n.FirstChild
if child.Type == html.TextNode {
// child.Data looks like something
// like "6.7 MEDIUM"
data := strings.Split(child.Data, " ")
if len(data) < 2 {
return fmt.Errorf("CVE score should be composed by score and severity (e.g 6.7 MEDIUM)")
}
score, err := strconv.ParseFloat(data[0], 64)
if err != nil {
return fmt.Errorf("unable to convert score to int: %w", err)
}
c.Score = score
c.Severity = data[1]
return nil
}
}
}
}
if n.Type == html.ElementNode && n.Data == "p" {
for _, a := range n.Attr {
if a.Key == "data-testid" && a.Val == "vuln-description" {
child := n.FirstChild
if child.Type == html.TextNode {
c.Description = child.Data
return nil
}
}
}
}
for child := n.FirstChild; child != nil; child = child.NextSibling {
if err := walk(child, c); err != nil {
return err
}
}
return nil
}
var c CVE
if err := walk(n, &c); err != nil {
return nil, fmt.Errorf("unable to walk into CVE parsed HTML: %w", err)
}
return &c, nil
}
// getCVE calls the fetch and the extract methods to get a CVE
// from a NIST URL
func getCVE(id string) (*CVE, error) {
u := fmt.Sprintf("%s/%s", baseURL, id)
if _, err := url.Parse(u); err != nil {
return nil, fmt.Errorf("unable to parse URL: %w", err)
}
page, err := fetchCVE(u)
if err != nil {
return nil, fmt.Errorf("unable to fetch CVE: %w", err)
}
cve, err := extractCVE(page)
if err != nil {
return nil, fmt.Errorf("unable to extract CVE: %w", err)
}
return cve, nil
}
func main() {
flag.Parse()
if len(CVELink) != 0 {
cve, err := getCVE(CVELink)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to get CVE: %v\n", err)
os.Exit(1)
}
res, err := json.Marshal(cve)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to convert CVE to JSON: %v\n", err)
os.Exit(1)
}
fmt.Println(string(res))
}
if len(CVEFile) != 0 {
if _, err := os.Stat(CVEFile); os.IsNotExist(err) {
fmt.Fprint(os.Stderr, "CVE file does not exist\n")
os.Exit(1)
}
file, err := os.Open(CVEFile)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to open CVE file: %v\n", err)
os.Exit(1)
}
defer file.Close()
cves := make([]*CVE, 0)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
url := scanner.Text()
cve, err := getCVE(url)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to get CVE: %v\n", err)
continue
}
cves = append(cves, cve)
}
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "unable to scan file: %v\n", err)
}
res, err := json.Marshal(cves)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to convert CVE list to JSON: %v\n", err)
os.Exit(1)
}
fmt.Println(string(res))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment