Skip to content

Commit

Permalink
Merge pull request #16 from ninoseki/use-cvss
Browse files Browse the repository at this point in the history
feat: use cvss lib to calculate rating
  • Loading branch information
ninoseki committed Sep 9, 2022
2 parents 5efb98b + aa2c2bb commit ea0e09c
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 14 deletions.
14 changes: 13 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions pycomponents/cve_search/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ def severity(self) -> Optional[str]:
if self.cvss is None:
return None

# since it is impossible to determine whether the score based on CVSS v2 or v3,
# use CVSS v2's scale
if self.cvss >= 7.0:
return "high"
return "High"

if self.cvss >= 4.0:
return "medium"
return "Medium"

return "low"
return "Low"
6 changes: 6 additions & 0 deletions pycomponents/osv/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class AffectedItem(BaseModel):
database_specific: DatabaseSpecific


class Severity(BaseModel):
type: str
score: str


class Vuln(BaseModel):
id: str
details: str
Expand All @@ -45,6 +50,7 @@ class Vuln(BaseModel):
references: List[Reference] = Field(default_factory=list)
affected: List[AffectedItem] = Field(default_factory=list)
schema_version: str
severity: List[Severity] = Field(default_factory=list)

@property
def cve_id(self) -> Optional[str]:
Expand Down
47 changes: 47 additions & 0 deletions pycomponents/rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Optional, Union, cast

from cvss import CVSS2, CVSS3
from cyclonedx.model.vulnerability import VulnerabilityRating

from .cve_search import get_cve_search
from .exceptions import CVESearchQueryException
from .osv.schemas import Severity, Vuln


def severity_to_rating(severity: Severity) -> Optional[VulnerabilityRating]:
severity_type_to_cvss = {
"CVSS_V3": CVSS3,
"CVSS_V2": CVSS2,
}

klass = severity_type_to_cvss.get(severity.type)
if klass is None:
return None

c = cast(Union[CVSS2, CVSS3], klass(severity.score))
score, _, _ = c.scores()
severity_str, _, _ = c.severities()
return VulnerabilityRating(score=score, severity=severity_str)


class RatingFactory:
@staticmethod
def from_osv_vuln(vuln: Vuln) -> Optional[VulnerabilityRating]:
rating: Optional[VulnerabilityRating] = None

if len(vuln.severity) > 0:
# TODO: consider a case when there are multi severities
severity = vuln.severity[0]
rating = severity_to_rating(severity)
if rating is not None:
return rating

if vuln.cve_id is not None:
try:
cve_search = get_cve_search()
res = cve_search.query(vuln.cve_id)
rating = VulnerabilityRating(score=res.cvss, severity=res.severity)
except CVESearchQueryException:
pass

return rating
13 changes: 3 additions & 10 deletions pycomponents/vulnerability.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
VulnerabilitySource,
)

from .cve_search import get_cve_search
from .exceptions import CVESearchQueryException, OSVQueryException
from .exceptions import OSVQueryException
from .osv import get_osv
from .osv.schemas import Vuln
from .rating import RatingFactory


def to_datetime(s: str) -> datetime:
Expand All @@ -35,14 +35,7 @@ def from_osv_vuln(vuln: Vuln) -> Vulnerability:
if source is not None:
references.append(VulnerabilityReference(source=source))

rating: Optional[VulnerabilityRating] = None
if vuln.cve_id is not None:
try:
cve_search = get_cve_search()
res = cve_search.query(vuln.cve_id)
rating = VulnerabilityRating(score=res.cvss, severity=res.severity)
except CVESearchQueryException:
pass
rating = RatingFactory.from_osv_vuln(vuln)

vulnerability = Vulnerability(
id=vuln.id,
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ readme = "README.md"
python = "^3.8"
arrow = "^1.2.2"
cachetools = "^5.2.0"
cvss = "^2.5"
cyclonedx-python-lib = "^2.7.1"
environs = "^9.5.0"
httpx = "^0.23.0"
Expand Down
17 changes: 17 additions & 0 deletions tests/test_rating.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import pytest

from pycomponents.osv.schemas import Severity
from pycomponents.rating import severity_to_rating


@pytest.fixture
def severity():
return Severity(
type="CVSS_V3", score="CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N"
)


def test_severity_to_rating(severity: Severity):
rating = severity_to_rating(severity)
assert rating.score == 8.6
assert rating.severity == "High"

0 comments on commit ea0e09c

Please sign in to comment.