Added false positive heuristics.

This commit is contained in:
Šesták Vít
2017-03-31 09:05:45 +02:00
parent 10b3a3b6f1
commit 6044947481
5 changed files with 70 additions and 3 deletions

View File

@@ -235,3 +235,9 @@ h3.library-identification{
.vulnerability-expandable .more{
margin: 10px;
}
.warning-expandable:before{
padding-left: 1em;
font-family: 'Glyphicons Halflings';
content: "\e209";
}

View File

@@ -121,7 +121,10 @@ object Confidence extends Enumeration {
final case class Reference(source: String, url: String, name: String)
final case class VulnerableSoftware(allPreviousVersion: Boolean, name: String)
final case class VulnerableSoftware(allPreviousVersion: Boolean, name: String){
def containsVersion = name.count(_==':') >= 4
def isVersionless = !containsVersion
}
final case class CvssRating(score: Option[Double], authenticationr: Option[String], availabilityImpact: Option[String], accessVector: Option[String], integrityImpact: Option[String], accessComplexity: Option[String], confidentialImpact: Option[String])
@@ -140,9 +143,24 @@ object CWE{
def forIdentifierWithDescription(name: String) = cwePool(new CWE(name))
}
final class RichBoolean(val value: Boolean) extends AnyVal{
@inline def ==> (right: => Boolean): Boolean = !value || right
}
object RichBoolean{
@inline implicit def toRichBoolean(value: Boolean) = new RichBoolean(value)
}
final case class Vulnerability(name: String, cweOption: Option[CWE], cvss: CvssRating, description: String, vulnerableSoftware: Seq[VulnerableSoftware], references: Seq[Reference]){
import RichBoolean.toRichBoolean
def cvssScore = cvss.score
def ysvssScore(affectedDeps: Set[GroupedDependency]) = cvssScore.map(_ * affectedDeps.flatMap(_.projects).toSet.size)
def likelyMatchesOnlyWithoutVersion(dependencyIdentifiers: Set[Identifier]) = dependencyIdentifiers.forall { id =>
// Rather a quick hack. Maybe it would be better to do this check in ODC.
val versionlessCpeIdentifierOption = id.toCpeIdentifierOption.map(_.split(':').take(4).mkString(":"))
versionlessCpeIdentifierOption.fold(true){ versionlessCpeIdentifier =>
vulnerableSoftware.forall(vs => vs.name.startsWith(versionlessCpeIdentifier) ==> vs.isVersionless)
}
}
}
final case class Identifier(name: String, confidence: Confidence.Confidence, url: String, identifierType: String) {

View File

@@ -68,7 +68,11 @@
<ul id="@depPrefix-vulnerabilities-details" class="collapse in vulnerabilities-details">
@for(vuln <- dep.vulnerabilities.toSeq.sortBy(_.cvssScore.map(-_)); vulnPrefix = s"$depPrefix-vulnerabilities-details-${vuln.name}"){
<li>
<h5 data-toggle="collapse" class="expandable collapsed" data-target="#@vulnPrefix-details">@vuln.name <a href="@routes.Statistics.vulnerability(vuln.name, selectorOption)" target="_blank" onclick="event.stopPropagation();"><span class="glyphicon glyphicon-new-window"></span></a></h5>
<h5 data-toggle="collapse" class="expandable collapsed" data-target="#@vulnPrefix-details">
@vuln.name
<a href="@routes.Statistics.vulnerability(vuln.name, selectorOption)" target="_blank" onclick="event.stopPropagation();"><span class="glyphicon glyphicon-new-window"></span></a>
@if(vuln.likelyMatchesOnlyWithoutVersion(dep.identifiers)){<span class="warning-expandable" title="Heuristics suspect false positive. Double check <b>what version</b> does this vulnerability apply to, please. It seems that the vulnerability database does not provide enough information to check it automatically." onmouseover="$(this).tooltip({placement: 'right', html:true}).tooltip('show');"></span>}
</h5>
<div id="@vulnPrefix-details" class="collapse vulnerability-expandable">
@vulnerability("h6", depPrefix+"-"+vuln.name, vuln)
<p><a class="btn btn-primary more" target="_blank" href="@routes.Statistics.vulnerability(vuln.name, selectorOption)">Full details about this vulnerability</a></p>

View File

@@ -31,7 +31,7 @@
@section("vuln-sw", "Vulnerable software"){
<ul id="@idPrefix-details">
@for(sw <- vuln.vulnerableSoftware){
<li>@sw.name@if(sw.allPreviousVersion){ and all previous versions}</li>
<li>@sw.name@if(sw.allPreviousVersion){ and all previous versions}@if(sw.isVersionless){<span class="warning-expandable" title="This identifier does not contain version. It will match <b>any</b> version, which might cause false positives." onmouseover="$(this).tooltip({placement: 'right', html:true}).tooltip('show');"></span>}</li>
}
</ul>
}

View File

@@ -0,0 +1,39 @@
import com.ysoft.odc._
import org.specs2.mutable.Specification
//noinspection ScalaUnnecessaryParentheses
class VulnerabilitySpec extends Specification {
val vuln = Vulnerability("some-vuln", None, CvssRating(None, None, None, None, None, None, None), "descr", Seq(
VulnerableSoftware(allPreviousVersion = false, "cpe:/a:ftp:ftp"),
VulnerableSoftware(allPreviousVersion = false, "cpe:/a:ssh:ssh:1.0"),
VulnerableSoftware(allPreviousVersion = false, "cpe:/a:asd:asd:1.0")
), Seq())
def id(name: String) = Identifier(name = name, confidence = Confidence.Highest, url = "", identifierType = "cpe")
"matchesOnlyWithoutVersion should" >> {
"return true" >> {
"when it contains just one match and it is without version" >> {
vuln.likelyMatchesOnlyWithoutVersion(Set(id("cpe:/a:ftp:ftp:1.0"))) should beTrue
}
}
"return false" >> {
"when it contains just one match and it contains version" >> {
vuln.likelyMatchesOnlyWithoutVersion(Set(id("cpe:/a:ssh:ssh:1.0"))) should beFalse
}
"when it contains just one match by older version" >> {
vuln.likelyMatchesOnlyWithoutVersion(Set(id("cpe:/a:ssh:ssh:0.9"))) should beFalse
}
"when it matches without version, but it also matches with version" >> {
vuln.likelyMatchesOnlyWithoutVersion(Set(id("cpe:/a:ftp:ftp:1.0"), id("cpe:/a:ssh:ssh:1.0"))) should beFalse
}
"when it matches without version, but it also matches with version and everything matches" >> {
vuln.likelyMatchesOnlyWithoutVersion(Set(id("cpe:/a:ftp:ftp:1.0"), id("cpe:/a:ssh:ssh:1.0"), id("cpe:/a:asd:asd:1.0"))) should beFalse
}
}
// TODO: Add tests for version matching; They would not pass now, though.
}
}