mirror of
https://github.com/ysoftdevs/odc-analyzer.git
synced 2026-03-23 09:31:54 +01:00
Added false positive heuristics.
This commit is contained in:
@@ -235,3 +235,9 @@ h3.library-identification{
|
|||||||
.vulnerability-expandable .more{
|
.vulnerability-expandable .more{
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.warning-expandable:before{
|
||||||
|
padding-left: 1em;
|
||||||
|
font-family: 'Glyphicons Halflings';
|
||||||
|
content: "\e209";
|
||||||
|
}
|
||||||
@@ -121,7 +121,10 @@ object Confidence extends Enumeration {
|
|||||||
|
|
||||||
final case class Reference(source: String, url: String, name: String)
|
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])
|
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))
|
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]){
|
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 cvssScore = cvss.score
|
||||||
def ysvssScore(affectedDeps: Set[GroupedDependency]) = cvssScore.map(_ * affectedDeps.flatMap(_.projects).toSet.size)
|
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) {
|
final case class Identifier(name: String, confidence: Confidence.Confidence, url: String, identifierType: String) {
|
||||||
|
|||||||
@@ -68,7 +68,11 @@
|
|||||||
<ul id="@depPrefix-vulnerabilities-details" class="collapse in vulnerabilities-details">
|
<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}"){
|
@for(vuln <- dep.vulnerabilities.toSeq.sortBy(_.cvssScore.map(-_)); vulnPrefix = s"$depPrefix-vulnerabilities-details-${vuln.name}"){
|
||||||
<li>
|
<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">
|
<div id="@vulnPrefix-details" class="collapse vulnerability-expandable">
|
||||||
@vulnerability("h6", depPrefix+"-"+vuln.name, vuln)
|
@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>
|
<p><a class="btn btn-primary more" target="_blank" href="@routes.Statistics.vulnerability(vuln.name, selectorOption)">Full details about this vulnerability</a></p>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
@section("vuln-sw", "Vulnerable software"){
|
@section("vuln-sw", "Vulnerable software"){
|
||||||
<ul id="@idPrefix-details">
|
<ul id="@idPrefix-details">
|
||||||
@for(sw <- vuln.vulnerableSoftware){
|
@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>
|
</ul>
|
||||||
}
|
}
|
||||||
|
|||||||
39
test/VulnerabilitySpec.scala
Normal file
39
test/VulnerabilitySpec.scala
Normal 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.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user