Skip to content
This repository has been archived by the owner on Mar 20, 2022. It is now read-only.

Commit

Permalink
Add cvssv3/base subpackage
Browse files Browse the repository at this point in the history
  • Loading branch information
spiegel-im-spiegel committed Mar 18, 2018
1 parent dbe2ae7 commit 90b3a72
Show file tree
Hide file tree
Showing 19 changed files with 1,210 additions and 0 deletions.
189 changes: 189 additions & 0 deletions cvssv3/base/base.go
@@ -0,0 +1,189 @@
package base

import (
"bytes"
"errors"
"fmt"
"math"
"strings"
)

//Error instances
var (
ErrNilData = errors.New("nil data")
ErrUndefinedMetric = errors.New("undefined metric")
ErrInvalidVector = errors.New("invalid vector")
ErrNotSupportVer = errors.New("not support version")
)

//Metrics is Base Metrics for CVSSv3
type Metrics struct {
AV AttackVector
AC AttackComplexity
PR PrivilegesRequired
UI UserInteraction
S Scope
C ConfidentialityImpact
I IntegrityImpact
A AvailabilityImpact
}

//NewMetrics returns Metrics instance
func NewMetrics() *Metrics {
return &Metrics{
AV: AttackVectorUnknown,
AC: AttackComplexityUnknown,
PR: PrivilegesRequiredUnknown,
UI: UserInteractionUnknown,
S: ScopeUnknown,
C: ConfidentialityImpactUnknown,
I: IntegrityImpactUnknown,
A: AvailabilityImpactUnknown,
}
}

//Decode returns Metrics instance by CVSSv3 vector
func Decode(vector string) (*Metrics, error) {
values := strings.Split(vector, "/")
if len(values) < 9 {
return nil, ErrInvalidVector
}
//CVSS version (CVSS 3.0 only)
if _, err := checkVersion(values[0]); err != nil {
return nil, err
}
//metrics
metrics := NewMetrics()
for _, value := range values[1:] {
metric := strings.Split(value, ":")
if len(metric) != 2 {
return nil, ErrInvalidVector
}
switch strings.ToUpper(metric[0]) {
case "AV": //Attack Vector
metrics.AV = GetAttackVector(metric[1])
case "AC": //Attack Complexity
metrics.AC = GetAttackComplexity(metric[1])
case "PR": //Privileges Required
metrics.PR = GetPrivilegesRequired(metric[1])
case "UI": //User Interaction
metrics.UI = GetUserInteraction(metric[1])
case "S": //Scope
metrics.S = GetScope(metric[1])
case "C": //Confidentiality Impact
metrics.C = GetConfidentialityImpact(metric[1])
case "I": //Integrity Impact
metrics.I = GetIntegrityImpact(metric[1])
case "A": //Availability Impact
metrics.A = GetAvailabilityImpact(metric[1])
default:
return nil, ErrInvalidVector
}
}
return metrics, metrics.GetError()
}
func checkVersion(ver string) (string, error) {
v := strings.Split(ver, ":")
if len(v) != 2 {
return "", ErrInvalidVector
}
if strings.ToUpper(v[0]) != "CVSS" {
return "", ErrInvalidVector
}
if v[1] != "3.0" {
return "", ErrNotSupportVer
}
return v[1], nil
}

//Encode returns CVSSv3 vector string
func (m *Metrics) Encode() (string, error) {
if err := m.GetError(); err != nil {
return "", err
}
r := &bytes.Buffer{}
r.WriteString("CVSS:3.0") //CVSS Version
r.WriteString(fmt.Sprintf("/AV:%v", m.AV)) //Attack Vector
r.WriteString(fmt.Sprintf("/AC:%v", m.AC)) //Attack Complexity
r.WriteString(fmt.Sprintf("/PR:%v", m.PR)) //Privileges Required
r.WriteString(fmt.Sprintf("/UI:%v", m.UI)) //User Interaction
r.WriteString(fmt.Sprintf("/S:%v", m.S)) //Scope
r.WriteString(fmt.Sprintf("/C:%v", m.C)) //Confidentiality Impact
r.WriteString(fmt.Sprintf("/I:%v", m.I)) //Integrity Impact
r.WriteString(fmt.Sprintf("/A:%v", m.A)) //Availability Impact
return r.String(), nil
}

//GetError returns error instance if undefined metric
func (m *Metrics) GetError() error {
if m == nil {
return ErrNilData
}
switch true {
case !m.AV.IsDefined(), !m.AC.IsDefined(), !m.PR.IsDefined(), !m.UI.IsDefined(), !m.S.IsDefined(), !m.C.IsDefined(), !m.I.IsDefined(), !m.A.IsDefined():
return ErrUndefinedMetric
default:
return nil
}
}

//Score returns score of Base metrics
func (m *Metrics) Score() float64 {
if err := m.GetError(); err != nil {
return 0.0
}

impact := 1.0 - (1-m.C.Value())*(1-m.I.Value())*(1-m.A.Value())
if m.S == ScopeUnchanged {
impact *= 6.42
} else {
impact = 7.52*(impact-0.029) - 3.25*math.Pow(impact-0.02, 15.0)
}
ease := 8.22 * m.AV.Value() * m.AC.Value() * m.PR.Value(m.S) * m.UI.Value()

var score float64
if impact <= 0 {
score = 0.0
} else if m.S == ScopeUnchanged {
score = math.Min(impact+ease, 10.0)
score = math.Ceil(score*10.0) / 10.0
} else {
score = math.Min(1.08*(impact+ease), 10.0)
score = math.Ceil(score*10.0) / 10.0
}
return score
}

//GetSeverity returns severity by score of Base metrics
func (m *Metrics) GetSeverity() Severity {
score := m.Score()
switch true {
case score == 0:
return SeverityNone
case score > 0 && score < 4.0:
return SeverityLow
case score >= 4.0 && score < 7.0:
return SeverityMedium
case score >= 7.0 && score < 9.0:
return SeverityHigh
case score >= 9.0:
return SeverityCritical
default:
return SeverityUnknown
}
}

/* Copyright 2018 Spiegel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
120 changes: 120 additions & 0 deletions cvssv3/base/base_test.go
@@ -0,0 +1,120 @@
package base

import "testing"

func TestDecodeError(t *testing.T) {
testCases := []struct {
vector string
err error
}{
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: nil},
{vector: "XXX:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: ErrInvalidVector},
{vector: "CVSS:2.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: ErrNotSupportVer},
{vector: "CVSS:3.0", err: ErrInvalidVector},
{vector: "CVSS3.0/AV:X/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: ErrInvalidVector},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A-N", err: ErrInvalidVector},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/X:N", err: ErrInvalidVector},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:X", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:X/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:X/I:N/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:X/C:N/I:N/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:X/S:U/C:N/I:N/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:X/UI:R/S:U/C:N/I:N/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:X/PR:H/UI:R/S:U/C:N/I:N/A:N", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:X/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: ErrUndefinedMetric},
}

for _, tc := range testCases {
_, err := Decode(tc.vector)
if err != tc.err {
t.Errorf("NewMetrics(%s) = \"%v\", want \"%v\".", tc.vector, err, tc.err)
}
}
}

func TestDecodeEncode(t *testing.T) {
testCases := []struct {
vector string
err error
}{
{vector: "CVSS:3.0/AV:X/AC:X/PR:X/UI:X/S:X/C:X/I:X/A:X", err: ErrUndefinedMetric},
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", err: nil},
{vector: "CVSS:3.0/AV:L/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:L", err: nil},
{vector: "CVSS:3.0/AV:A/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", err: nil},
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H", err: nil},
}

for _, tc := range testCases {
m, err := Decode(tc.vector)
if err != tc.err {
t.Errorf("Decode(%s) = \"%v\", want \"%v\".", tc.vector, err, tc.err)
}
v, err := m.Encode()
if err != tc.err {
t.Errorf("Encode() = \"%v\", want \"%v\".", err, tc.err)
}
if err == nil {
if v != tc.vector {
t.Errorf("Encode() = \"%v\", want \"%v\".", v, tc.vector)
}
}
}
}

func TestScore(t *testing.T) {
testCases := []struct {
vector string
score float64
severity Severity
}{
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:X", score: 0.0, severity: SeverityNone}, //error
{vector: "CVSS:3.0/AV:P/AC:H/PR:H/UI:R/S:U/C:N/I:N/A:N", score: 0.0, severity: SeverityNone}, //Zero metrics
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", score: 7.5, severity: SeverityHigh}, //CVE-2015-8252
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N", score: 6.1, severity: SeverityMedium}, //CVE-2013-1937
{vector: "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:L/I:L/A:N", score: 6.4, severity: SeverityMedium}, //CVE-2013-0375
{vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N", score: 3.1, severity: SeverityLow}, //CVE-2014-3566
{vector: "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H", score: 9.9, severity: SeverityCritical}, //CVE-2012-1516
{vector: "CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", score: 8.8, severity: SeverityHigh}, //CVE-2012-0384
{vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", score: 7.8, severity: SeverityHigh}, //CVE-2015-1098
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N", score: 7.5, severity: SeverityHigh}, //CVE-2014-0160
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", score: 9.8, severity: SeverityCritical}, //CVE-2014-6271
{vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:N", score: 6.8, severity: SeverityMedium}, //CVE-2008-1447
{vector: "CVSS:3.0/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", score: 6.8, severity: SeverityMedium}, //CVE-2014-2005
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:N/A:N", score: 5.8, severity: SeverityMedium}, //CVE-2010-0467
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N", score: 5.8, severity: SeverityMedium}, //CVE-2012-1342
{vector: "CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N", score: 5.4, severity: SeverityMedium}, //CVE-2014-9253
{vector: "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", score: 7.8, severity: SeverityHigh}, //CVE-2009-0658
{vector: "CVSS:3.0/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", score: 8.8, severity: SeverityHigh}, //CVE-2011-1265
{vector: "CVSS:3.0/AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", score: 4.6, severity: SeverityMedium}, //CVE-2014-2019
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", score: 8.8, severity: SeverityHigh}, //CVE-2015-0970
{vector: "CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N", score: 7.4, severity: SeverityHigh}, //CVE-2014-0224
{vector: "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:H", score: 9.6, severity: SeverityCritical}, //CVE-2012-5376
}

for _, tc := range testCases {
m, _ := Decode(tc.vector)
score := m.Score()
if score != tc.score {
t.Errorf("Score(%s) = %v, want %v.", tc.vector, score, tc.score)
}
severity := m.GetSeverity()
if severity.String() != tc.severity.String() {
t.Errorf("Score(%s) = %v, want %v.", tc.vector, severity, tc.severity)
}
}
}

/* Copyright 2018 Spiegel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
72 changes: 72 additions & 0 deletions cvssv3/base/metric-a.go
@@ -0,0 +1,72 @@
package base

import "strings"

//AvailabilityImpact is metric type for Base Metrics
type AvailabilityImpact int

//Constant of AvailabilityImpact result
const (
AvailabilityImpactUnknown AvailabilityImpact = iota
AvailabilityImpactNone
AvailabilityImpactLow
AvailabilityImpactHigh
)

var availabilityImpactMap = map[AvailabilityImpact]string{
AvailabilityImpactNone: "N",
AvailabilityImpactLow: "L",
AvailabilityImpactHigh: "H",
}

var availabilityImpactValueMap = map[AvailabilityImpact]float64{
AvailabilityImpactNone: 0.00,
AvailabilityImpactLow: 0.22,
AvailabilityImpactHigh: 0.56,
}

//GetAvailabilityImpact returns result of AvailabilityImpact metric
func GetAvailabilityImpact(s string) AvailabilityImpact {
s = strings.ToUpper(s)
for k, v := range availabilityImpactMap {
if s == v {
return k
}
}
return AvailabilityImpactUnknown
}

func (ai AvailabilityImpact) String() string {
if s, ok := availabilityImpactMap[ai]; ok {
return s
}
return ""
}

//Value returns value of AvailabilityImpact metric
func (ai AvailabilityImpact) Value() float64 {
if v, ok := availabilityImpactValueMap[ai]; ok {
return v
}
return 0.0
}

//IsDefined returns false if undefined result value of metric
func (ai AvailabilityImpact) IsDefined() bool {
return ai != AvailabilityImpactUnknown
}

/* Copyright 2018 Spiegel
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

0 comments on commit 90b3a72

Please sign in to comment.