diff --git a/.gitignore b/.gitignore index c3c89e7..34b148c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .idea/ .venv/ __pycache__/ -todo.txt \ No newline at end of file +tests/ +todo* \ No newline at end of file diff --git a/README.md b/README.md index 822f029..0b905b6 100644 --- a/README.md +++ b/README.md @@ -39,10 +39,10 @@ - [x] 奇安信漏洞通告 -### TODO +### ~~TODO~~ DONE -- [ ] 每日所有漏洞信息邮件推送(模板doing,V2.0实现) -- [ ] flask+datatables+echarts实现完整前后端(V2.0实现) +- [x] 每日所有漏洞信息邮件推送 ~~(模板doing,V2.0实现)~~ +- [x] flask+datatables+echarts实现完整前后端 ~~(V2.0实现)~~ ## WHY @@ -115,9 +115,9 @@ notify: enable: false access_token: secret: - + + #填写相关配置信息,在每天6点会推送前一天的漏洞汇总 email: - enable: false smtp_server: smtp_port: username: @@ -166,16 +166,92 @@ while True: 安装完需要的库,配置好config.yaml后,即可开始运行。 -![image-20240730174829661](README/image-20240730174829661.png) +![image-20240801234222860](README/image-20240801234222860.png) + +注意,需要一直在后台保持运行,可配合screen等工具实现。 + +#### 高危漏洞预警 + +**注意:** 只会推送severity为high及以上的漏洞。 推送内容如下: ![image-20240730174957206](README/image-20240730174957206.png) -**注意:** 只会推送severity为high及以上的漏洞,但是所有漏洞均会存入数据库: +#### 数据库信息 + +所有漏洞均会存入数据库: ![image-20240731172901942](README/image-20240731172901942.png) +#### 每日漏洞汇总 + +每天六点会推送前一天的漏洞汇总,邮件内容如下: + +![image-20240801233643812](README/image-20240801233643812.png) + +由于漏洞描述可能较长,影响观感,因此邮件中隐藏了描述字段,每个漏洞均可通过点击link跳转至poc或详情页。 + +#### 数据可视化 + +在V2.0中,实现了数据的可视化,默认端口为5000。 + +**重要!!!** 在该项目中,后端由flask实现,在main.py中,通过run_flask_app来运行应用: + +```python +def run_flask_app(): + app.run(debug=False) +``` + +默认配置下,仅能本机访问,即`host = '127.0.0.1'`,若想进行远程访问,有如下两种方法: + +- 推荐通过nps等隧道工具实现,较为安全,但是需要配置隧道工具 +- 修改`app.run(debug=False)`为`app.run(debug=False,host='0.0.0.0')`,修改后直接对所有主机开放,简单粗暴,但是存在一定安全风险,如有意外作者概不负责! + + + +前端展示共有3处路由:首页('/'或'/index'),漏洞总览('/vuls')和每日漏洞详情('/daily/[date]') + + + +##### /index + +首页有两部分,第一部分是漏洞的统计结果,有四张图表,分别统计了所有漏洞中,CVE的占比,各种severity漏洞的占比,各漏洞源漏洞的占比以及近7天来的漏洞数量变化趋势。 + +![image-20240801235519773](README/image-20240801235519773.png) + +第二部分为最新的十条漏洞数据: + +![image-20240801235832583](README/image-20240801235832583.png) + +为了更加美观,在图表中没有采取换行的方式,如有超长字段,会隐藏一部分,鼠标悬停即可查看完整内容。 + +同样,单击右侧link即可跳转至漏洞POC或详情页。 + + + +##### /vuls + +该路由展示了全量的漏洞数据,并且添加了搜索功能: + +![image-20240802000129644](README/image-20240802000129644.png) + +右上方可选每页展示的漏洞条数,通过最下方页码即可跳转。 + +搜索框中为模糊搜索,支持全字段,例如搜索name,cve,source,severity,date等等: + +![image-20240802000310469](README/image-20240802000310469.png) + +##### /daily/date + +通过输入%Y-%m-%d形式的日期,可查询指定日期的漏洞情况,以2024-07-31为例: + +`/daily/2024-07-31` + +![image-20240802000456751](README/image-20240802000456751.png) + +展示了当前日期CVE漏洞占比,各severity和source的漏洞占比,以及当天更新的所有漏洞信息。 + ### 扩展 如需扩展其他漏洞源数据,实现base_collector.py中的VulnerabilityCollector类即可,漏洞信息的字段如下: diff --git a/README/image-20240730174829661.png b/README/image-20240730174829661.png deleted file mode 100644 index bf86bfa..0000000 Binary files a/README/image-20240730174829661.png and /dev/null differ diff --git a/README/image-20240801233643812.png b/README/image-20240801233643812.png new file mode 100644 index 0000000..22af379 Binary files /dev/null and b/README/image-20240801233643812.png differ diff --git a/README/image-20240801234222860.png b/README/image-20240801234222860.png new file mode 100644 index 0000000..fbf63cf Binary files /dev/null and b/README/image-20240801234222860.png differ diff --git a/README/image-20240801235519773.png b/README/image-20240801235519773.png new file mode 100644 index 0000000..227ac85 Binary files /dev/null and b/README/image-20240801235519773.png differ diff --git a/README/image-20240801235832583.png b/README/image-20240801235832583.png new file mode 100644 index 0000000..dd86333 Binary files /dev/null and b/README/image-20240801235832583.png differ diff --git a/README/image-20240802000129644.png b/README/image-20240802000129644.png new file mode 100644 index 0000000..a2e1e05 Binary files /dev/null and b/README/image-20240802000129644.png differ diff --git a/README/image-20240802000310469.png b/README/image-20240802000310469.png new file mode 100644 index 0000000..952f8de Binary files /dev/null and b/README/image-20240802000310469.png differ diff --git a/README/image-20240802000456751.png b/README/image-20240802000456751.png new file mode 100644 index 0000000..f96ad97 Binary files /dev/null and b/README/image-20240802000456751.png differ diff --git a/collectors/collector_github.py b/collectors/collector_github.py index ef78350..46e3557 100644 --- a/collectors/collector_github.py +++ b/collectors/collector_github.py @@ -42,7 +42,7 @@ def parse_data(self, raw_data): continue vulnerability = { - 'name': '', + 'name': cves, 'cve': cves, 'severity': severity, 'description': desc, diff --git a/config.py b/config.py index 93bff4c..398a038 100644 --- a/config.py +++ b/config.py @@ -1,7 +1,13 @@ import yaml +import os +config_dir = os.path.dirname(os.path.abspath(__file__)) + + +def load_config(config_path=None): + if config_path is None: + config_path = os.path.join(config_dir, 'config.yaml') -def load_config(config_path='config.yaml'): with open(config_path, 'r') as f: config = yaml.safe_load(f) return config diff --git a/config.yaml b/config.yaml index 0f65d3f..2a4e3a8 100644 --- a/config.yaml +++ b/config.yaml @@ -21,7 +21,6 @@ notify: secret: email: - enable: false smtp_server: smtp_port: username: diff --git a/database/db_class.py b/database/db_class.py index f64c06b..a686f3c 100644 --- a/database/db_class.py +++ b/database/db_class.py @@ -47,7 +47,7 @@ def fetch_results(self, query, params=None): print("Failed to connect to the database") return [] - cursor = self.connection.cursor() + cursor = self.connection.cursor(dictionary=True) results = [] try: cursor.execute(query, params) @@ -57,7 +57,7 @@ def fetch_results(self, query, params=None): cursor.close() return results - def insert_vulnerability(self, data): + def insert_vulnerability(self, data): query = f""" INSERT INTO vulnerabilities (name, cve, severity, description, source, date, link) VALUES (%s, %s, %s, %s, %s, %s, %s) @@ -75,6 +75,6 @@ def insert_vulnerability(self, data): )) def check_vulnerability_exists(self, key, value): - query = f"SELECT EXISTS(SELECT 1 FROM vulnerabilities WHERE {key}=%s)" + query = f"SELECT EXISTS(SELECT 1 FROM vulnerabilities WHERE {key}=%s) as exists_flag" result = self.fetch_results(query, (value,)) - return result[0][0] if result else False + return result[0]['exists_flag'] if result else False diff --git a/flask/templates/hello.html b/flask/templates/hello.html deleted file mode 100644 index e69de29..0000000 diff --git a/flaskr/app.py b/flaskr/app.py new file mode 100644 index 0000000..865efb8 --- /dev/null +++ b/flaskr/app.py @@ -0,0 +1,124 @@ +from flask import Flask, render_template, request, jsonify +from config import cfg +from database.db_class import MySQLDatabase + +app = Flask(__name__) +mysql_db = MySQLDatabase() + + +@app.route('/') +@app.route('/index') +def index(): + try: + + latest_vulnerabilities_query = "SELECT * FROM vulnerabilities ORDER BY date DESC LIMIT 10" + latest_vulnerabilities = mysql_db.fetch_results(latest_vulnerabilities_query) + + + severity_data_query = """ + SELECT COUNT(*) as total, + SUM(CASE WHEN severity = 'high' THEN 1 ELSE 0 END) as high_count, + SUM(CASE WHEN severity = 'medium' THEN 1 ELSE 0 END) as medium_count, + SUM(CASE WHEN severity = 'low' THEN 1 ELSE 0 END) as low_count, + SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count, + SUM(CASE WHEN severity = '' THEN 1 ELSE 0 END) as none_count + FROM vulnerabilities + """ + severity_data = mysql_db.fetch_results(severity_data_query)[0] + + source_data_query = "SELECT source, COUNT(*) as count FROM vulnerabilities GROUP BY source" + source_data = mysql_db.fetch_results(source_data_query) + + cve_data_query = "SELECT COUNT(*) as total, SUM(CASE WHEN cve <> '' THEN 1 ELSE 0 END) as cve_count FROM vulnerabilities" + cve_data = mysql_db.fetch_results(cve_data_query)[0] + + trend_data_query = """ + SELECT date, COUNT(*) as count + FROM vulnerabilities + WHERE date >= CURDATE() - INTERVAL 7 DAY + GROUP BY date + ORDER BY date + """ + trend_data = mysql_db.fetch_results(trend_data_query) + + return render_template('index.html', latest_vulnerabilities=latest_vulnerabilities, + severity_data=severity_data, source_data=source_data, cve_data=cve_data, + trend_data=trend_data) + except Exception as err: + return f"Error: {err}" + +@app.route('/vuls', methods=['GET']) +def vuls(): + try: + search = request.args.get('search', '') + per_page = int(request.args.get('per_page', 10)) + page = int(request.args.get('page', 1)) + offset = (page - 1) * per_page + + if search: + query = """ + SELECT * FROM vulnerabilities + WHERE name LIKE %s OR cve LIKE %s OR severity LIKE %s OR description LIKE %s OR source LIKE %s + ORDER BY date DESC + LIMIT %s, %s + """ + params = (f'%{search}%', f'%{search}%', f'%{search}%', f'%{search}%', f'%{search}%', offset, per_page) + vulnerabilities = mysql_db.fetch_results(query, params) + + count_query = """ + SELECT COUNT(*) as total + FROM vulnerabilities + WHERE name LIKE %s OR cve LIKE %s OR severity LIKE %s OR description LIKE %s OR source LIKE %s + """ + count_params = (f'%{search}%', f'%{search}%', f'%{search}%', f'%{search}%', f'%{search}%') + else: + query = "SELECT * FROM vulnerabilities ORDER BY date DESC LIMIT %s, %s" + params = (offset, per_page) + vulnerabilities = mysql_db.fetch_results(query, params) + + count_query = "SELECT COUNT(*) as total FROM vulnerabilities" + count_params = () + + total_result = mysql_db.fetch_results(count_query, count_params)[0] + total = total_result['total'] + + return render_template('vuls.html', vulnerabilities=vulnerabilities, total=total, per_page=per_page, page=page, search=search) + except Exception as err: + return f"Error: {err}" + +@app.route('/daily/') +def daily(date): + try: + vulnerabilities_query = "SELECT * FROM vulnerabilities WHERE date = %s ORDER BY date DESC" + vulnerabilities = mysql_db.fetch_results(vulnerabilities_query, (date,)) + + severity_data_query = """ + SELECT COUNT(*) as total, + SUM(CASE WHEN severity = 'high' THEN 1 ELSE 0 END) as high_count, + SUM(CASE WHEN severity = 'medium' THEN 1 ELSE 0 END) as medium_count, + SUM(CASE WHEN severity = 'low' THEN 1 ELSE 0 END) as low_count, + SUM(CASE WHEN severity = 'critical' THEN 1 ELSE 0 END) as critical_count, + SUM(CASE WHEN severity = '' THEN 1 ELSE 0 END) as none_count + FROM vulnerabilities + WHERE date = %s + """ + severity_data = mysql_db.fetch_results(severity_data_query, (date,))[0] + + source_data_query = "SELECT source, COUNT(*) as count FROM vulnerabilities WHERE date = %s GROUP BY source" + source_data = mysql_db.fetch_results(source_data_query, (date,)) + + + cve_data_query = "SELECT COUNT(*) as total, SUM(CASE WHEN cve <> '' THEN 1 ELSE 0 END) as cve_count FROM vulnerabilities WHERE date = %s" + cve_data = mysql_db.fetch_results(cve_data_query, (date,))[0] + + vuln_count = len(vulnerabilities) + + return render_template('daily.html', date=date, vulnerabilities=vulnerabilities, + severity_data=severity_data, source_data=source_data, cve_data=cve_data, + vuln_count=vuln_count) + except Exception as err: + return f"Error: {err}" + +@app.errorhandler(404) +def page_not_found(error): + return render_template('404.html'), 404 diff --git a/flaskr/static/css/style.css b/flaskr/static/css/style.css new file mode 100644 index 0000000..f348194 --- /dev/null +++ b/flaskr/static/css/style.css @@ -0,0 +1,329 @@ +body { + font-family: 'Arial', sans-serif; + background: linear-gradient(to right, #f0f0f0, #ffffff); + color: #333; + margin: 0; + padding: 0; +} + +.container { + width: 90%; + margin: auto; +} + +header { + text-align: center; + padding: 20px 0; +} + +.header-content { + display: flex; + align-items: center; + justify-content: center; +} + +.header-content h1 { + margin: 0; + font-size: 2em; +} + +#search-box { + margin-left: auto; + margin-right: 20px; + padding: 5px; + font-size: 1em; +} + +.charts { + display: flex; + flex-wrap: wrap; + justify-content: space-around; + margin: 20px 0; +} + +.chart-container { + width: 45%; + margin-bottom: 20px; +} + +.chart { + width: 100%; + height: 300px; +} + +.chart-label { + text-align: center; + margin-top: 10px; + font-weight: bold; +} + +.latest-vulnerabilities { + margin: 40px 0; +} + +.latest-vulnerabilities h2 { + text-align: center; + font-size: 1.5em; + margin-bottom: 10px; +} + +#vulnerability-table { + width: 100%; + border-collapse: collapse; + margin: auto; +} + +#vulnerability-table th, #vulnerability-table td { + padding: 10px; + border: 1px solid #ccc; + text-align: left; +} + +#vulnerability-table th { + background-color: #f9f9f9; +} + +#vulnerability-table td a { + text-decoration: none; + color: #007bff; + cursor: pointer; +} + +#vulnerability-table td a:hover { + text-decoration: underline; +} + +.tooltip-cell { + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; +} + +.tooltip-cell:not([title]) { + pointer-events: none; +} + +.tooltip-cell[title]:hover::after { + content: attr(title); + position: absolute; + background-color: #333; + color: #fff; + padding: 5px; + border-radius: 5px; + z-index: 10; + white-space: pre-wrap; + max-width: 300px; + top: 100%; + left: 0; + transform: translateY(5px); +} + +.tooltip-cell[title]:hover::before { + content: ''; + position: absolute; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #333; + top: 100%; + left: 10px; +} + +.dataTables_wrapper .dataTables_filter { + float: right; +} + +.dataTables_wrapper .dataTables_length { + float: left; +} + +.dataTables_wrapper .dataTables_info { + clear: both; + float: left; + margin-top: 10px; +} + +.dataTables_wrapper .dataTables_paginate { + float: right; + margin-top: 10px; +} + +.search-container { + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.search-container form { + display: flex; + width: 100%; + max-width: 600px; +} + +.search-container input[type="text"] { + flex: 1; + padding: 8px; + font-size: 1em; +} + +.search-container select { + padding: 8px; + font-size: 1em; +} + +.pagination { + margin: 20px 0; + text-align: center; +} + +.pagination a { + margin: 0 5px; + text-decoration: none; + color: #007bff; +} + +.pagination a:hover { + text-decoration: underline; +} + +.pagination span { + font-weight: bold; +} + +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; +} + +.container { + width: 90%; + max-width: 1200px; + margin: 0 auto; +} + +header { + padding: 10px 0; + text-align: center; +} + +header h1 { + margin: 0; +} + +/* 数据表格样式 */ +#vulnerability-table { + width: 100%; + border-collapse: collapse; + margin: auto; +} + +#vulnerability-table th, #vulnerability-table td { + padding: 10px; + border: 1px solid #ccc; + text-align: left; +} + +#vulnerability-table th { + background-color: #f9f9f9; +} + +#vulnerability-table td a { + text-decoration: none; + color: #007bff; + cursor: pointer; +} + +#vulnerability-table td a:hover { + text-decoration: underline; +} + +/* 工具提示样式 */ +.tooltip-cell { + max-width: 200px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + position: relative; +} + +.tooltip-cell:not([title]) { + pointer-events: none; +} + +.tooltip-cell[title]:hover::after { + content: attr(title); + position: absolute; + background-color: #333; + color: #fff; + padding: 5px; + border-radius: 5px; + z-index: 10; + white-space: pre-wrap; + max-width: 300px; + top: 100%; + left: 0; + transform: translateY(5px); +} + +.tooltip-cell[title]:hover::before { + content: ''; + position: absolute; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #333; + top: 100%; + left: 10px; +} + +/* 搜索框和每页条数选择样式 */ +.search-container { + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.search-container form { + display: flex; + width: 100%; + max-width: 600px; +} + +.search-container input[type="text"] { + flex: 1; + padding: 8px; + font-size: 1em; +} + +.search-container select { + padding: 8px; + font-size: 1em; +} + +/* 分页样式 */ +.pagination { + margin: 20px 0; + text-align: center; +} + +.pagination a { + margin: 0 5px; + text-decoration: none; + color: #007bff; +} + +.pagination a:hover { + text-decoration: underline; +} + +.pagination span { + font-weight: bold; +} diff --git a/flaskr/static/img/logo.png b/flaskr/static/img/logo.png new file mode 100644 index 0000000..ea1b824 Binary files /dev/null and b/flaskr/static/img/logo.png differ diff --git a/flask/app.py b/flaskr/static/js/scripts.js similarity index 100% rename from flask/app.py rename to flaskr/static/js/scripts.js diff --git a/flaskr/templates/404.html b/flaskr/templates/404.html new file mode 100644 index 0000000..e74ca9a --- /dev/null +++ b/flaskr/templates/404.html @@ -0,0 +1,52 @@ + + + + + + 404 - 页面未找到 + + + +
+
404
+
抱歉,您访问的页面不存在。
+ +
+ + diff --git a/flaskr/templates/daily.html b/flaskr/templates/daily.html new file mode 100644 index 0000000..e772cd3 --- /dev/null +++ b/flaskr/templates/daily.html @@ -0,0 +1,158 @@ + + + + + Athena - Daily Vulnerability Dashboard + + + + + + + + + +
+
+
+

Athena Daily Vulnerability Dashboard

+
+
+
+
+
+
CVE Proportion
+
+
+
+
Severity Proportion
+
+
+
+
Source Proportion
+
+
+
+

Vulnerabilities on {{ date }}

+
Total Vulnerabilities: {{ vuln_count }}
+ + + + + + + + + + + + + + {% for vulnerability in vulnerabilities %} + + + + + + + + + + {% endfor %} + +
NameCVESeverityDescriptionSourceDateLink
{{ vulnerability.name }}{{ vulnerability.cve }}{{ vulnerability.severity }}{{ vulnerability.description }}{{ vulnerability.source }}{{ vulnerability.date }}Link
+
+
+ + + + diff --git a/flaskr/templates/index.html b/flaskr/templates/index.html new file mode 100644 index 0000000..c851402 --- /dev/null +++ b/flaskr/templates/index.html @@ -0,0 +1,186 @@ + + + + + Athena - Vulnerability Dashboard + + + + + + + + + +
+
+
+

Athena Vulnerability Dashboard

+
+
+
+
+
+
CVE Proportion
+
+
+
+
Severity Proportion
+
+
+
+
Source Proportion
+
+
+
+
Vulnerability Trends (Last 7 Days)
+
+
+
+

Latest 10 Vulnerabilities

+ + + + + + + + + + + + + + {% for vulnerability in latest_vulnerabilities %} + + + + + + + + + + {% endfor %} + +
NameCVESeverityDescriptionSourceDateLink
{{ vulnerability.name }}{{ vulnerability.cve }}{{ vulnerability.severity }}{{ vulnerability.description }}{{ vulnerability.source }}{{ vulnerability.date }}Link
+
+
+ + + + diff --git a/flaskr/templates/vuls.html b/flaskr/templates/vuls.html new file mode 100644 index 0000000..edc01d9 --- /dev/null +++ b/flaskr/templates/vuls.html @@ -0,0 +1,188 @@ + + + + + Athena - Vulnerability List + + + + + + + +
+
+
+

Athena Vulnerability List

+
+
+
+

Total vulnerabilities: {{ total }}

+
+ + +
+
+
+ + + + + + + + + + + + + + {% for vulnerability in vulnerabilities %} + + + + + + + + + + {% endfor %} + +
NameCVESeverityDescriptionSourceDateLink
{{ vulnerability.name }}{{ vulnerability.cve }}{{ vulnerability.severity }}{{ vulnerability.description }}{{ vulnerability.source }}{{ vulnerability.date }}Link
+
+ +
+ + diff --git a/main.py b/main.py index 8a767da..e7ba129 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,11 @@ +import datetime +import threading import time import art +from notifications.notifier import send_daily_notifications from processing.filter import gather_data, filter_high_risk_vuls from database.init import create_db +from flaskr.app import app def display_banner(): @@ -9,10 +13,31 @@ def display_banner(): print(banner) +def daily_task(): + yesterday = (datetime.date.today() - datetime.timedelta(days=1)).strftime("%Y-%m-%d") + send_daily_notifications(yesterday) + + +def run_flask_app(): + app.run(debug=False) + + def main(): display_banner() create_db() + + flask_thread = threading.Thread(target=run_flask_app) + flask_thread.start() + + last_sent_date = None while True: + current_time = datetime.datetime.now() + current_date = current_time.date() + + if current_time.hour == 6 and last_sent_date != current_date: + daily_task() + last_sent_date = current_date + vulnerabilities = gather_data() filter_high_risk_vuls(vulnerabilities) time.sleep(600) diff --git a/notifications/email.py b/notifications/email.py deleted file mode 100644 index 6c0eebd..0000000 --- a/notifications/email.py +++ /dev/null @@ -1,20 +0,0 @@ -import smtplib -from email.mime.text import MIMEText -from email.mime.multipart import MIMEMultipart - - -def email_notification(server, port, username, password, from_user, to_user, subject, message): - msg = MIMEMultipart() - msg['From'] = from_user - msg['To'] = ', '.join(to_user) - msg['Subject'] = subject - - msg.attach(MIMEText(message, 'plain')) - - try: - with smtplib.SMTP(server, port) as server: - server.login(username, password) - server.sendmail(from_user, to_user, msg.as_string()) - print("Email sent successfully") - except Exception as e: - print(f"Failed to send email: {e}") diff --git a/notifications/email_template.html b/notifications/email_template.html new file mode 100644 index 0000000..8e70de7 --- /dev/null +++ b/notifications/email_template.html @@ -0,0 +1,63 @@ + + + + + + Vulnerabilities + + + +

Vulnerabilities

+ + + + + + + + + + + + + + {% for vulnerability in vulnerabilities %} + + + + + + + + + {% endfor %} + +
NameCVESeveritySourceDateLink
{{ vulnerability.name }}{{ vulnerability.cve }}{{ vulnerability.severity }}{{ vulnerability.source }}{{ vulnerability.date }}Link
+ + + diff --git a/notifications/mail.py b/notifications/mail.py new file mode 100644 index 0000000..09f931f --- /dev/null +++ b/notifications/mail.py @@ -0,0 +1,28 @@ +import smtplib +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText +from jinja2 import Environment, FileSystemLoader, select_autoescape + + +def email_notification(server, port, username, password, from_user, to_user, subject, vulnerabilities): + file_loader = FileSystemLoader('.') + env = Environment(loader=file_loader, autoescape=select_autoescape(['html', 'xml'])) + template = env.get_template('notifications/email_template.html') + html_content = template.render(vulnerabilities=vulnerabilities) + + try: + msg = MIMEMultipart() + msg['From'] = from_user + msg['To'] = ', '.join(to_user) + msg['Subject'] = subject + + msg.attach(MIMEText(html_content, 'html')) + + with smtplib.SMTP(server, port) as server: + server.starttls() + server.login(username, password) + server.sendmail(msg['From'], msg['To'], msg.as_string()) + print('Email sent successfully!') + except Exception as e: + print(f'Failed to send email. Error: {str(e)}') + diff --git a/notifications/notifier.py b/notifications/notifier.py index 00a2903..e4485ac 100644 --- a/notifications/notifier.py +++ b/notifications/notifier.py @@ -1,19 +1,24 @@ from config import cfg +from database.db_class import MySQLDatabase +from notifications.mail import email_notification from notifications.dingtalk import dingtalk_notification from notifications.wxwork import wxwork_notification -from notifications.email import email_notification -def send_notifications(subject, message, content): +def send_realtime_notifications(content): notify = cfg['notify'] - - if notify['email']['enable']: - email_notification(notify['email']['smtp_server'], notify['email']['smtp_port'], notify['email']['username'], notify['email']['password'], notify['email']['from'], notify['email']['to'], subject, message) - if notify['wxwork']['enable']: wxwork_notification(notify['wxwork']['key'], content) - if notify['dingtalk']['enable']: dingtalk_notification(notify['dingtalk']['access_token'], notify['dingtalk']['secret'], content) +def send_daily_notifications(date): + notify = cfg['notify'] + db = MySQLDatabase() + query = f"SELECT * FROM vulnerabilities where date = %s" + vulnerabilities = db.fetch_results(query, (date,)) + if vulnerabilities: + email_notification(notify['email']['smtp_server'], notify['email']['smtp_port'], notify['email']['username'], + notify['email']['password'], notify['email']['from'], notify['email']['to'], + "Daily Vulnerability Report", vulnerabilities) diff --git a/processing/filter.py b/processing/filter.py index fb92988..1f64da9 100644 --- a/processing/filter.py +++ b/processing/filter.py @@ -3,7 +3,7 @@ from config import cfg from database.db_class import MySQLDatabase from collectors.manager import VulnerabilityManager -from notifications.notifier import send_notifications +from notifications.notifier import send_realtime_notifications def gather_data(): @@ -46,6 +46,6 @@ def filter_high_risk_vuls(vulnerabilities): if value: content += f"{key}: {value}\n" content = content.rstrip("\n") - send_notifications("vulnerability notifier", "", content) + send_realtime_notifications(content) print(f"High risk vulnerabilities found: {high_risk_num}\n") diff --git a/requirements.txt b/requirements.txt index 7ba7523..6b0cc94 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +Flask==2.2.5 art==6.2 mysql==0.0.3 mysql-connector-python==9.0.0 @@ -5,4 +6,5 @@ PyYAML==6.0.1 requests==2.32.3 beautifulsoup4==4.12.3 tqdm==4.66.4 +Jinja2==3.1.2 DingtalkChatbot==1.5.7 \ No newline at end of file