Skip to content

Commit c513713

Browse files
committed
Implement the appropriate LoopProgress progress bar.
Refactor the error handling logic in the code. Signed-off-by: ziadhany <ziadhany2016@gmail.com>
1 parent 7f3c168 commit c513713

File tree

9 files changed

+337
-245
lines changed

9 files changed

+337
-245
lines changed
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import csv
2+
import io
3+
import logging
4+
5+
import requests
6+
from aboutcode.pipeline import LoopProgress
7+
from dateutil import parser as dateparser
8+
from django.db import DataError
9+
10+
from vulnerabilities.models import Alias
11+
from vulnerabilities.models import Exploit
12+
from vulnerabilities.models import VulnerabilityReference
13+
from vulnerabilities.models import VulnerabilityRelatedReference
14+
from vulnerabilities.pipelines import VulnerableCodePipeline
15+
16+
17+
class ExploitDBImproverPipeline(VulnerableCodePipeline):
18+
"""
19+
ExploitDB Improver Pipeline: Fetch ExploitDB data, iterate over it to find the vulnerability with
20+
the specified alias, and create or update the ref and ref-type accordingly.
21+
"""
22+
23+
license_expression = "GPL-2.0"
24+
25+
@classmethod
26+
def steps(cls):
27+
return (
28+
cls.fetch_exploits,
29+
cls.add_exploit,
30+
)
31+
32+
def fetch_exploits(self):
33+
exploit_db_url = (
34+
"https://gitlab.com/exploit-database/exploitdb/-/raw/main/files_exploits.csv"
35+
)
36+
37+
try:
38+
response = requests.get(exploit_db_url)
39+
response.raise_for_status()
40+
except requests.exceptions.HTTPError as http_err:
41+
self.log(
42+
f"Failed to fetch the Exploit-DB Exploits: {exploit_db_url} - {http_err}",
43+
level=logging.ERROR,
44+
)
45+
raise
46+
47+
self.exploit_data = io.StringIO(response.text)
48+
49+
def add_exploit(self):
50+
csvreader = csv.reader(self.exploit_data)
51+
header = next(csvreader)
52+
53+
raw_data = list(csvreader)
54+
fetched_exploit_count = len(raw_data)
55+
56+
vulnerability_exploit_count = 0
57+
progress = LoopProgress(total_iterations=fetched_exploit_count, logger=self.log)
58+
59+
for row in progress.iter(raw_data):
60+
vulnerability_exploit_count += add_vulnerability_exploit(row, header, self.log)
61+
62+
self.log(
63+
f"Successfully added {vulnerability_exploit_count:,d} exploit-db vulnerability exploit"
64+
)
65+
66+
67+
def add_vulnerability_exploit(row, header, logger):
68+
vulnerability = None
69+
aliases = row[11].split(";")
70+
71+
for raw_alias in aliases:
72+
try:
73+
if alias := Alias.objects.get(alias=raw_alias):
74+
vulnerability = alias.vulnerability
75+
break
76+
except Alias.DoesNotExist:
77+
continue
78+
79+
if not vulnerability:
80+
logger(f"No vulnerability found for aliases {aliases}")
81+
return 0
82+
83+
add_exploit_references(row[11], row[16], row[1], vulnerability, logger)
84+
85+
date_added = parse_date(row[header.index("date_added")])
86+
source_date_published = parse_date(row[header.index("date_published")])
87+
source_date_updated = parse_date(row[header.index("date_updated")])
88+
89+
try:
90+
Exploit.objects.update_or_create(
91+
vulnerability=vulnerability,
92+
data_source="Exploit-DB",
93+
defaults={
94+
"date_added": date_added,
95+
"description": row[header.index("description")],
96+
"known_ransomware_campaign_use": row[header.index("verified")],
97+
"source_date_published": source_date_published,
98+
"exploit_type": row[header.index("type")],
99+
"platform": row[header.index("platform")],
100+
"source_date_updated": source_date_updated,
101+
"source_url": row[header.index("source_url")],
102+
},
103+
)
104+
except DataError as e:
105+
logger(
106+
f"Failed to Create the Vulnerability Exploit-DB: {e}",
107+
level=logging.ERROR,
108+
)
109+
return 1
110+
111+
112+
def add_exploit_references(ref_id, direct_url, path, vul, logger):
113+
url_map = {
114+
"file_url": f"https://gitlab.com/exploit-database/exploitdb/-/blob/main/{path}",
115+
"direct_url": direct_url,
116+
}
117+
118+
for key, url in url_map.items():
119+
if url:
120+
try:
121+
ref, created = VulnerabilityReference.objects.update_or_create(
122+
url=url,
123+
defaults={
124+
"reference_id": ref_id,
125+
"reference_type": VulnerabilityReference.EXPLOIT,
126+
},
127+
)
128+
129+
if created:
130+
VulnerabilityRelatedReference.objects.get_or_create(
131+
vulnerability=vul,
132+
reference=ref,
133+
)
134+
135+
except DataError as e:
136+
logger(
137+
f"Failed to Create the Vulnerability Reference For Exploit-DB: {e}",
138+
level=logging.ERROR,
139+
)
140+
141+
142+
def parse_date(date_string):
143+
if date_string:
144+
try:
145+
date_obj = dateparser.parse(date_string).date()
146+
return date_obj.strftime("%Y-%m-%d")
147+
except (ValueError, TypeError, Exception) as e:
148+
logging.error(f"Error while parsing ExploitDB date '{date_string}': {e}")
149+
return
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import logging
2+
3+
import requests
4+
from aboutcode.pipeline import LoopProgress
5+
6+
from vulnerabilities.models import Alias
7+
from vulnerabilities.models import Exploit
8+
from vulnerabilities.pipelines import VulnerableCodePipeline
9+
10+
module_logger = logging.getLogger(__name__)
11+
12+
13+
class VulnerabilityKevPipeline(VulnerableCodePipeline):
14+
"""
15+
Known Exploited Vulnerabilities Pipeline: Retrieve KEV data, iterate through it to identify vulnerabilities
16+
by their associated aliases, and create or update the corresponding Exploit instances.
17+
"""
18+
19+
@classmethod
20+
def steps(cls):
21+
return (
22+
cls.fetch_exploits,
23+
cls.add_exploits,
24+
)
25+
26+
def fetch_exploits(self):
27+
kev_url = (
28+
"https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
29+
)
30+
try:
31+
response = requests.get(kev_url)
32+
response.raise_for_status()
33+
except requests.exceptions.HTTPError as http_err:
34+
self.log(
35+
f"Failed to fetch the KEV Exploits: {kev_url} - {http_err}",
36+
level=logging.ERROR,
37+
)
38+
raise
39+
self.kev_data = response.json()
40+
41+
def add_exploits(self):
42+
fetched_exploit_count = self.kev_data.get("count")
43+
self.log(f"Enhancing the vulnerability with {fetched_exploit_count:,d} exploit records")
44+
45+
vulnerability_exploit_count = 0
46+
progress = LoopProgress(total_iterations=fetched_exploit_count, logger=self.log)
47+
48+
for record in progress.iter(self.kev_data.get("vulnerabilities", [])):
49+
vulnerability_exploit_count += add_vulnerability_exploit(
50+
kev_vul=record,
51+
logger=self.log,
52+
)
53+
54+
self.log(f"Successfully added {vulnerability_exploit_count:,d} kev exploit")
55+
56+
57+
def add_vulnerability_exploit(kev_vul, logger):
58+
cve_id = kev_vul.get("cveID")
59+
60+
vulnerability = None
61+
try:
62+
if alias := Alias.objects.get(alias=cve_id):
63+
vulnerability = alias.vulnerability
64+
except Alias.DoesNotExist:
65+
logger(f"No vulnerability found for aliases {cve_id}")
66+
return 0
67+
68+
Exploit.objects.update_or_create(
69+
vulnerability=vulnerability,
70+
data_source="KEV",
71+
defaults={
72+
"description": kev_vul["shortDescription"],
73+
"date_added": kev_vul["dateAdded"],
74+
"required_action": kev_vul["requiredAction"],
75+
"due_date": kev_vul["dueDate"],
76+
"notes": kev_vul["notes"],
77+
"known_ransomware_campaign_use": True
78+
if kev_vul["knownRansomwareCampaignUse"] == "Known"
79+
else False,
80+
},
81+
)
82+
return 1
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import logging
2+
3+
import requests
4+
import saneyaml
5+
from aboutcode.pipeline import LoopProgress
6+
from dateutil import parser as dateparser
7+
8+
from vulnerabilities.models import Alias
9+
from vulnerabilities.models import Exploit
10+
from vulnerabilities.pipelines import VulnerableCodePipeline
11+
12+
module_logger = logging.getLogger(__name__)
13+
14+
15+
class MetasploitImproverPipeline(VulnerableCodePipeline):
16+
"""
17+
Metasploit Exploits Pipeline: Retrieve Metasploit data, iterate through it to identify vulnerabilities
18+
by their associated aliases, and create or update the corresponding Exploit instances.
19+
"""
20+
21+
license_expression = "BSD-3-clause"
22+
23+
@classmethod
24+
def steps(cls):
25+
return (
26+
cls.fetch_exploits,
27+
cls.add_vulnerability_exploits,
28+
)
29+
30+
def fetch_exploits(self):
31+
url = "https://raw.githubusercontent.com/rapid7/metasploit-framework/master/db/modules_metadata_base.json"
32+
self.log(f"Fetching {url}")
33+
try:
34+
response = requests.get(url)
35+
response.raise_for_status()
36+
except requests.exceptions.HTTPError as http_err:
37+
self.log(
38+
f"Failed to fetch the Metasploit Exploits: {url} - {http_err}", level=logging.ERROR
39+
)
40+
raise
41+
42+
self.metasploit_data = response.json()
43+
44+
def add_vulnerability_exploits(self):
45+
fetched_exploit_count = len(self.metasploit_data)
46+
self.log(f"Enhancing the vulnerability with {fetched_exploit_count:,d} exploit records")
47+
48+
vulnerability_exploit_count = 0
49+
progress = LoopProgress(total_iterations=fetched_exploit_count, logger=self.log)
50+
for _, record in progress.iter(self.metasploit_data.items()):
51+
vulnerability_exploit_count += add_vulnerability_exploit(
52+
record=record,
53+
logger=self.log,
54+
)
55+
self.log(f"Successfully added {vulnerability_exploit_count:,d} vulnerability exploit")
56+
57+
58+
def add_vulnerability_exploit(record, logger):
59+
vulnerability = None
60+
references = record.get("references", [])
61+
for ref in references:
62+
if ref.startswith("OSVDB") or ref.startswith("URL-"):
63+
# ignore OSV-DB and reference exploit for metasploit
64+
continue
65+
66+
try:
67+
if alias := Alias.objects.get(alias=ref):
68+
vulnerability = alias.vulnerability
69+
break
70+
except Alias.DoesNotExist:
71+
continue
72+
73+
if not vulnerability:
74+
logger(f"No vulnerability found for aliases {references}")
75+
return 0
76+
77+
description = record.get("description", "")
78+
notes = record.get("notes", {})
79+
platform = record.get("platform")
80+
81+
source_url = ""
82+
if path := record.get("path"):
83+
source_url = f"https://github.com/rapid7/metasploit-framework/tree/master{path}"
84+
source_date_published = None
85+
86+
if disclosure_date := record.get("disclosure_date"):
87+
try:
88+
source_date_published = dateparser.parse(disclosure_date).date()
89+
except ValueError:
90+
logger(f"Error while parsing date {disclosure_date}", level=logging.ERROR)
91+
92+
Exploit.objects.update_or_create(
93+
vulnerability=vulnerability,
94+
data_source="Metasploit",
95+
defaults={
96+
"description": description,
97+
"notes": saneyaml.dump(notes),
98+
"source_date_published": source_date_published,
99+
"platform": platform,
100+
"source_url": source_url,
101+
},
102+
)
103+
return 1

0 commit comments

Comments
 (0)