Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions analyzers/OpenCVE/OpenCVE.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "OpenCVE",
"version": "1.0",
"author": "Ali Bhutto",
"url": "https://github.com/TheHive-Project/Cortex-Analyzers",
"license": "AGPL-V3",
"description": "Enrich a CVE with OpenCVE data: CVSS metrics, CISA KEV status, EPSS score, CWE weaknesses and affected vendors/products.",
"dataTypeList": ["cve"],
"command": "OpenCVE/opencve_analyzer.py",
"baseConfig": "OpenCVE",
"configurationItems": [
{
"name": "token",
"description": "OpenCVE organization API token (Bearer). Create one for free at app.opencve.io under your organization settings.",
"type": "string",
"multi": false,
"required": true
},
{
"name": "base_url",
"description": "OpenCVE API base URL. Keep the default for the hosted instance, or set it to your self-hosted OpenCVE API.",
"type": "string",
"multi": false,
"required": false,
"defaultValue": "https://app.opencve.io/api"
}
],
"registration_required": true,
"subscription_required": false,
"free_subscription": true,
"service_homepage": "https://www.opencve.io"
}
31 changes: 31 additions & 0 deletions analyzers/OpenCVE/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# OpenCVE Analyzer

Enrich a `cve` observable with vulnerability data from [OpenCVE](https://www.opencve.io).

OpenCVE aggregates CVE information from several providers (NVD, Red Hat, CISA, FIRST, ...)
and exposes it through a REST API. This analyzer queries that API for a given CVE and
reports its CVSS metrics, CISA KEV status, EPSS score, CWE weaknesses and the affected
vendors and products. Unlike the existing Vulners analyzer, OpenCVE is free to use and can
also be self-hosted.

### Supported observable

- `cve` (for example `CVE-2021-44228`)

### Requirements

You need an OpenCVE account and an organization API token:

1. Create a free account at [app.opencve.io](https://app.opencve.io). The Free plan includes API access (100 calls/hour).
2. Open your organization settings and generate an API token.
3. Provide it to the analyzer through the `token` configuration option.

If you run your own OpenCVE instance, set `base_url` to its API endpoint. The default is
`https://app.opencve.io/api`.

### Configuration

| Name | Description | Required | Default |
|------|-------------|----------|---------|
| `token` | OpenCVE organization API token (Bearer). | yes | |
| `base_url` | OpenCVE API base URL. | no | `https://app.opencve.io/api` |
98 changes: 98 additions & 0 deletions analyzers/OpenCVE/opencve_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
import requests
from cortexutils.analyzer import Analyzer


class OpenCVEAnalyzer(Analyzer):
"""Enrich a CVE observable with data from OpenCVE (https://www.opencve.io)."""

def __init__(self):
Analyzer.__init__(self)
self.token = self.get_param('config.token', None, 'Missing OpenCVE API token')
base_url = self.get_param('config.base_url', 'https://app.opencve.io/api')
self.base_url = base_url.rstrip('/')
self.proxies = self.get_param('config.proxy', None)

@staticmethod
def _best_cvss(metrics):
"""Return (score, vector, version) from the most recent CVSS metric available."""
for version, key in (('3.1', 'cvssV3_1'), ('4.0', 'cvssV4_0'),
('3.0', 'cvssV3_0'), ('2.0', 'cvssV2_0')):
data = (metrics.get(key) or {}).get('data') or {}
score = data.get('score')
if isinstance(score, (int, float)):
return score, data.get('vector'), version
return None, None, None

def summary(self, raw):
taxonomies = []
namespace = 'OpenCVE'

if not raw.get('found', False):
taxonomies.append(self.build_taxonomy('info', namespace, 'CVE', 'Not found'))
return {'taxonomies': taxonomies}

metrics = raw.get('metrics') or {}
score, _, _ = self._best_cvss(metrics)
kev = bool((metrics.get('kev') or {}).get('data'))

if kev or (score is not None and score >= 9.0):
level = 'malicious'
elif score is not None and score >= 4.0:
level = 'suspicious'
else:
level = 'info'

taxonomies.append(self.build_taxonomy(
level, namespace, 'CVSS', score if score is not None else 'N/A'))
if kev:
taxonomies.append(self.build_taxonomy('malicious', namespace, 'KEV', 'CISA'))
return {'taxonomies': taxonomies}

def run(self):
if self.data_type != 'cve':
self.error('OpenCVE analyzer only supports the cve data type')

cve_id = self.get_param('data', None, 'Data is missing').strip().upper()
url = '{}/cve/{}'.format(self.base_url, cve_id)
headers = {
'Authorization': 'Bearer {}'.format(self.token),
'Accept': 'application/json',
}

try:
response = requests.get(url, headers=headers, proxies=self.proxies, timeout=30)
except requests.exceptions.RequestException as e:
self.error('Error while contacting OpenCVE: {}'.format(e))

if response.status_code == 401:
self.error('OpenCVE authentication failed: check the API token')
if response.status_code == 404:
self.report({'found': False, 'cve_id': cve_id})
return
if response.status_code != 200:
self.error('Unexpected OpenCVE response ({}): {}'.format(
response.status_code, response.text[:200]))

data = response.json()
data['found'] = True

# OpenCVE returns vendors as a flat list mixing "vendor" and
# "vendor$PRODUCT$product" entries; split them for a readable report.
vendors = set()
products = []
for entry in data.get('vendors', []) or []:
if '$PRODUCT$' in entry:
vendor, product = entry.split('$PRODUCT$', 1)
vendors.add(vendor)
products.append({'vendor': vendor, 'product': product.replace('\\', '')})
else:
vendors.add(entry)
data['vendors_list'] = sorted(vendors)
data['products'] = products

self.report(data)


if __name__ == '__main__':
OpenCVEAnalyzer().run()
2 changes: 2 additions & 0 deletions analyzers/OpenCVE/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cortexutils
requests
88 changes: 88 additions & 0 deletions thehive-templates/OpenCVE_1_0/long.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<!-- Error -->
<div class="panel panel-danger" ng-if="!success">
<div class="panel-heading">
<strong>{{artifact.data | fang}}</strong>
</div>
<div class="panel-body">
<dl class="dl-horizontal">
<dt><i class="fa fa-warning"></i> Error: </dt>
<dd>{{content.errorMessage}}</dd>
</dl>
</div>
</div>

<!-- CVE not found in OpenCVE -->
<div class="panel panel-warning" ng-if="success && !content.found">
<div class="panel-heading">
OpenCVE: <strong>{{artifact.data | fang}}</strong>
</div>
<div class="panel-body">
No record for <strong>{{content.cve_id}}</strong> was found in OpenCVE.
</div>
</div>

<!-- Success -->
<div class="panel panel-info" ng-if="success && content.found">
<div class="panel-heading">
OpenCVE information for <strong>{{content.cve_id | fang}}</strong>
</div>
<div class="panel-body">
<h3 align="center"><b>{{content.cve_id}}</b></h3>
<p ng-if="content.title" align="center">{{content.title}}</p>
<dl class="dl-horizontal">
<dt>Published: </dt><dd>{{content.created_at | limitTo : 10}}</dd>
<dt>Updated: </dt><dd>{{content.updated_at | limitTo : 10}}</dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.cvssV3_1.data.score">
<dt>CVSS v3.1: </dt>
<dd>{{content.metrics.cvssV3_1.data.score}}
<small>{{content.metrics.cvssV3_1.data.vector}}</small></dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.cvssV4_0.data.score">
<dt>CVSS v4.0: </dt>
<dd>{{content.metrics.cvssV4_0.data.score}}
<small>{{content.metrics.cvssV4_0.data.vector}}</small></dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.cvssV2_0.data.score">
<dt>CVSS v2.0: </dt>
<dd>{{content.metrics.cvssV2_0.data.score}}
<small>{{content.metrics.cvssV2_0.data.vector}}</small></dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.threat_severity.data">
<dt>Severity: </dt><dd>{{content.metrics.threat_severity.data}}</dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.epss.data.score">
<dt>EPSS: </dt><dd>{{content.metrics.epss.data.score}}</dd>
</dl>
<dl class="dl-horizontal" ng-if="content.metrics.kev.data">
<dt>CISA KEV: </dt>
<dd style="color:#ff0000">Known Exploited
<span ng-if="content.metrics.kev.data.dateAdded">(added {{content.metrics.kev.data.dateAdded | limitTo : 10}})</span>
</dd>
</dl>
<dl class="dl-horizontal" ng-if="content.weaknesses.length">
<dt>CWE: </dt>
<dd><span class="label label-default" ng-repeat="cwe in content.weaknesses">{{cwe}} </span></dd>
</dl>
<hr>
<h3>Description</h3>
<p>{{content.description}}</p>
<div ng-if="content.vendors_list.length">
<hr>
<h3>Affected vendors</h3>
<p><span class="label label-primary" ng-repeat="v in content.vendors_list">{{v}} </span></p>
</div>
<div ng-if="content.products.length">
<hr>
<h3>Affected products</h3>
<table class="table table-striped">
<thead><tr><th>Vendor</th><th>Product</th></tr></thead>
<tbody>
<tr ng-repeat="p in content.products">
<td>{{p.vendor}}</td><td>{{p.product}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
6 changes: 6 additions & 0 deletions thehive-templates/OpenCVE_1_0/short.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<span class="label" ng-repeat="t in content.taxonomies"
ng-class="{'info': 'label-info', 'safe': 'label-success',
'suspicious': 'label-warning',
'malicious':'label-danger'}[t.level]">
{{t.namespace}}:{{t.predicate}}={{t.value}}
</span>