diff --git a/app_sap_logo.py b/app_sap_logo.py new file mode 100644 index 0000000..81c9854 --- /dev/null +++ b/app_sap_logo.py @@ -0,0 +1,278 @@ +import streamlit as st +import pandas as pd +import numpy as np +import plotly.express as px +import plotly.graph_objects as go +import streamlit_antd_components as sac +from datetime import date, timedelta +import httpx +import re + +# Caching data loading +@st.cache_data +def load_data(): + df = pd.read_csv('data/sap_cve_last_202412_all.csv') + cwe_top_25 = pd.read_csv('data/cwe_top_25_2024.csv') + ll_cwe_t25 = list(cwe_top_25['ID']) + + df['datePublished'] = pd.to_datetime(df['datePublished'], format='mixed', utc=True) + df['dateUpdated'] = pd.to_datetime(df['dateUpdated'], format='mixed', utc=True) + df['cwe_t25'] = df['cweId'].isin(ll_cwe_t25) + + df.drop_duplicates(subset=['Note#'], inplace=True) + + df['sap_note_year'] = df['sap_note_year'].astype('category') + df['year'] = pd.to_datetime(df['sap_note_year'], format='%Y', utc=True) + df['Note#'] = df['Note#'].astype('category') + df['priority'] = df['priority'].astype('category') + df['priority_l'] = df['priority_l'].astype('category') + df['Priority'] = df['Priority'].astype('category') + df['cvss_severity'] = df['cvss_severity'].astype('category') + df['kev'].fillna(False, inplace=True) + df['cveInfo'] = df['cve_id'].apply(lambda x: f'https://www.cvedetails.com/cve/{x}') + df['cveSAP'] = df['cve_id'].apply(lambda x: f'https://www.cve.org/CVERecord?id={x}') + df['epss'] = (df['epss'] * 100).astype('float').round(2) + + return df + +# Caching EPSS data fetching +@st.cache_data +def fetch_epss_data(cve): + r = httpx.get(f'https://api.first.org/data/v1/epss?cve={cve}&scope=time-series') + epss_ts = r.json()['data'][0] + return [float(l['epss'])*100 for l in reversed(epss_ts['time-series'])] + +# Select A+|1+ CVEs & Get EPSS data of TOP Priorities CVEs +@st.cache_data +def sap_cve_top_priority(xdf): + #sap_cve_top = xdf[(xdf['priority_l'].isin(['A+', 'B'])) | (xdf['priority'] == 'Priority 1+')] + sap_cve_top = xdf[(xdf['priority_l'].isin(['A+'])) | + (xdf['priority'] == 'Priority 1+') | + (xdf['cvss'] > 7.5)] + col_epss_hist = [fetch_epss_data(row['cve_id']) for _, row in sap_cve_top.iterrows()] + return sap_cve_top, col_epss_hist + +# Function to calculate EPSS trend +def calculate_epss_trend(epss_values, up_threshold=1.01, down_threshold=0.99): + if len(epss_values) < 2: + return 'stable' + first_val, last_val = epss_values[0], epss_values[-1] + if last_val > first_val * up_threshold: + return 'up' + elif last_val < first_val * down_threshold: + return 'down' + return 'stable' + +# Function to calculate individual scores +def calculate_scores(row, kev_weight=3, cvss_multiplier=2, epss_up_multiplier=3, epss_stable_multiplier=2, cwe_weight=1.5): + kev_score = kev_weight if row['kev'] else 0 + cvss_score = row['cvss'] * cvss_multiplier + epss_trend = calculate_epss_trend(row['epss_l_30']) + epss_avg = np.mean(row['epss_l_30']) if len(row['epss_l_30']) > 0 else 0 + epss_score = epss_avg * (epss_up_multiplier if epss_trend == 'up' else epss_stable_multiplier if epss_trend == 'stable' else 1) + cwe_score = cwe_weight if row['cwe_t25'] else 0 + priority_score = 1 + + return { + 'epss_trend': epss_trend, + 'epss_avg': epss_avg, + 'kev_score': kev_score, + 'cvss_score': cvss_score, + 'epss_score': epss_score, + 'cwe_score': cwe_score, + 'priority_score': priority_score, + 'composite_score': kev_score + cvss_score + epss_score + cwe_score + priority_score + } + +# Main function to process the DataFrame and rank vulnerabilities +@st.cache_data +def process_vulnerability_data(ydf, kev_weight=3, cvss_multiplier=2, epss_up_multiplier=3, epss_stable_multiplier=2, cwe_weight=1.5): + score_columns = ydf.apply( + lambda row: calculate_scores(row, kev_weight, cvss_multiplier, epss_up_multiplier, epss_stable_multiplier, cwe_weight), + axis=1, + result_type='expand' + ) + + ydf = pd.concat([ydf, score_columns], axis=1) + return ydf.sort_values(by='composite_score', ascending=False) + +# Streamlit app setup +st.set_page_config( + page_title="SAP Compass Vulns", + page_icon="assets/favicon.ico", + layout="wide", + initial_sidebar_state="collapsed", +) + +# Load data +df = load_data() +# UI Components +st.logo("assets/logo.png", link="https://dub.sh/dso-days", icon_image="assets/logo.png") + +sac.divider(label=" Compass Priority Vulnerabilities", color='#ffffff') + +# Sidebar +st.sidebar.markdown('
Last updated 10-12-2024
', unsafe_allow_html=True) +sentiment_mapping = [":red[:material/thumb_down:]", ":green[:material/thumb_up:]"] +st.sidebar.markdown('


How do you like this app?
', unsafe_allow_html=True) +selected = st.sidebar.feedback("thumbs") +if selected is not None: + st.sidebar.markdown(f'### You selected: {sentiment_mapping[selected]}') +st.sidebar.caption("Info and Details") +st.sidebar.caption(":blue[:material/neurology:] [SAP Vulnerabilities - CVE-IDs](https://dso-days-siteblog.vercel.app/blog/sap-cve-ids/)") + +# Main content +#st.html("") +#st.title("SAP Compass Priority Vulnerabilities") + +st.toast('New 2024 CWE Top 25 for Rethink process', icon=":material/emergency_heat:") + + +with st.expander("Vulnerability Summary 2021-2024", expanded=False, icon=":material/explore:"): + st.header(f"From January 2021 to date, :blue[{df.shape[0]} SAP Notes] related to :orange[{len(df['cve_id'].unique())} CVE-IDs] are reported.") + + count_by_month = df.groupby([df['datePublished'].dt.to_period('M'), 'Priority']).size().reset_index(name='v') + count_by_month['cumulative_v'] = count_by_month.groupby('Priority')['v'].cumsum() + total_by_priority = count_by_month.groupby('Priority')['v'].sum().reset_index() + + with st.container(): + metrics = st.columns(4, gap='large') + for priority, color in zip(['Hot News', 'High', 'Medium', 'Low'], ['violet', 'red', 'orange', 'blue']): + value = total_by_priority.loc[total_by_priority['Priority'] == priority, 'v'].values[0] + metrics[['Hot News', 'High', 'Medium', 'Low'].index(priority)].metric(f":{color}[{priority}]", value=value) + +st.divider() + +# Filters +col1s, col2s, col3s = st.columns([2,2,1], vertical_alignment='center') +with col1s: + priority_filter = st.multiselect("Select SAP Priority Level", df['Priority'].unique(), default=df['Priority'].unique()) +with col2s: + year_filter = st.multiselect("Select SAP Note Year", df['sap_note_year'].unique(), default=df['sap_note_year'].unique()) +with col3s: + on = st.toggle(":blue[:material/neurology:] Rethink Priorities", key="on_rethink", help="Run process Rethink Priority Score") + +filtered_df = df[df['Priority'].isin(priority_filter) & df['sap_note_year'].isin(year_filter)] + +st.divider() + +if on: + with st.container(): + epss_h = sap_cve_top_priority(filtered_df) + sap_cve_top25 = epss_h[0].copy() + sap_cve_top25['epss_l_30'] = epss_h[1] + sap_cve_top25 = process_vulnerability_data(sap_cve_top25) + top = sap_cve_top25.shape[0] + top_vs = sap_cve_top25.drop_duplicates(subset=['cve_id']) + kev = top_vs[top_vs['kev']] + cweT25 = top_vs[top_vs['cwe_t25']] + + tab1, tab2 = st.tabs(["Vunls Top Priority", "CVE Info"]) + with tab1: + st.header(f":violet[Top {top}] Priority Vulnerabilities of :blue[{filtered_df.shape[0]}] selected SAP Notes") + st.header(f':orange[{top_vs.shape[0]}] Unique CVE-IDs & :red[{kev.shape[0]} on KEV]') + + st.dataframe( + sap_cve_top25[['Note#','cve_id','Priority','priority_l','priority','cvss','kev','epss','cweId','cwe_t25','composite_score']], + column_config = { + "composite_score": st.column_config.NumberColumn("Score", help="Rethink Priority Score.", format="%.3f"), + }, + hide_index=True, + ) + + # CVSS Distribution + chart_data = sap_cve_top25[["cvss","epss","cve_id","Note#"]] + fig = px.scatter(chart_data, x='cvss', y='epss', color_discrete_sequence=["#ff1493"], + labels={"cvss": "CVSS score", "epss": "EPSS %"}) + fig.add_hline(y=25, line_color='grey', line_dash='dash', + annotation_text="Threshold EPSS: 25%", annotation_position="bottom right") + fig.add_vline(x=6.0, line_color='grey', line_dash='dash', + annotation_text="Threshold CVSS: 6.0", annotation_position="top right") + fig.update_layout(xaxis_title="CVSS Score", yaxis_title="EPSS %") + st.subheader("EPSS Score Distribution") + st.plotly_chart(fig, use_container_width=True) + + with tab2: + st.subheader('CVE Details by Rethink Priority Score') + st.header(f':orange[{top_vs.shape[0]} CVE-IDs] | :red[{kev.shape[0]} on KEV] | :blue[{cweT25.shape[0]} on CWE Top 25]') + st.dataframe( + top_vs[['cveInfo','Priority','priority_l','priority','cweId','epss','cvss', + 'cvss_severity','kev','sap_note_year','cwe_t25','epss_l_30','epss_trend', + 'epss_avg','kev_score','cvss_score','epss_score','cwe_score','priority_score', + 'composite_score','vendor','product_l','descriptions']], + column_config={ + "cveInfo": st.column_config.LinkColumn("cveInfo", help="CVE Details", max_chars=50, display_text=r"(CVE-....-\d+)"), + "epss_l_30": st.column_config.AreaChartColumn("EPSS (Last 30 days)", y_min=0, y_max=100), + "composite_score": st.column_config.NumberColumn("Score", help="Rethink Priority Score.", format="%.2f"), + }, + hide_index=True + ) + + st.subheader('Treemap Score Priorities') + fig_tm = px.treemap(top_vs, path=[px.Constant("CVE Details"), 'Priority', 'sap_note_year', 'priority', 'priority_l'], values='composite_score') + fig_tm.update_traces(marker_colorscale=['#5eadf2','#3b2e8c','#04adbf','#ba38f2','#ff1493']) + fig_tm.update_layout(margin = dict(t=50, l=25, r=25, b=25)) + st.plotly_chart(fig_tm, theme=None, use_container_width=True) + st.divider() + +st.header(f":violet[{filtered_df.shape[0]}] Selected Vulnerabilities") +st.dataframe( + filtered_df[['Note#', 'cveInfo', 'cveSAP', 'Priority', 'priority_l', 'priority', 'epss', 'cvss', 'product_l']], + column_config={ + "epss": st.column_config.NumberColumn("EPSS %", help="Probabilidad para explotar la vulnerabilidad."), + "cveInfo": st.column_config.LinkColumn("cveInfo", help="CVE Details", max_chars=50, display_text=r"(CVE-....-\d+)"), + "cveSAP": st.column_config.LinkColumn("cveSAP", help="CVE SAP Details", max_chars=50, display_text=r"(CVE-....-\d+)"), + }, + hide_index=True +) + +col1, col2 = st.columns(2, vertical_alignment="bottom") + +with col1: + # Show CVSS Distribution + st.subheader("EPSS Score Distribution") + chart_data = filtered_df[["cvss","epss","cve_id","Note#"]] + st.scatter_chart(chart_data, + y="epss", + x="cvss", + x_label="CVSS Score", + y_label="EPSS %", + color="#ff1493", + use_container_width=True) + +with col2: + # Potentially Display another chart (like by date) + st.subheader("Vulns Year Published") + filtered_df['yp'] = filtered_df['datePublished'].values.astype('datetime64[Y]') + count_by_date = filtered_df.groupby(filtered_df['yp'].dt.date).size().reset_index(name='count') + print(count_by_date) + st.bar_chart(count_by_date, y="count", x="yp", x_label="CVE Year Published", + color="#ba38f2", use_container_width=True) + + + + +st.subheader("Parallel Category Diagram") +dfp = filtered_df[['sap_note_year','year','priority_l','priority','Priority','cvss_severity']] +#dfp['team'] = pd.factorize(dfp['year'])[0].astype('int') +fig_parallel = px.parallel_categories( + dfp, dimensions=['sap_note_year','Priority','cvss_severity','priority_l','priority'], + labels={'sap_note_year':'Year', + 'priority_l':'SploitScan', + 'priority':'CVE-Prioritizer', + 'Priority':'SAP', + 'cvss_severity':'cvssSeverity'}, + color=dfp['sap_note_year'], + #range_color=year_c[1]) '#4e79a7' #5f45bf '#3b2e8c' #5eadf2 + color_continuous_scale=['#5eadf2','#3b2e8c','#ba38f2','#ff1493'], + color_continuous_midpoint=2022) +st.plotly_chart(fig_parallel, theme=None, use_container_width=True) + + + + +st.divider() + +with st.expander("Dataset SAP Vulnerabilities"): + st.subheader("Dataset Raw") + st.write(df) diff --git a/run.sh b/run.sh index 5e9f626..942e360 100755 --- a/run.sh +++ b/run.sh @@ -22,7 +22,7 @@ trap stopRunningProcess EXIT TERM source venv/bin/activate #streamlit run ${HOME}/appuser/streamlit_app.py & -streamlit run streamlit_app.py & +streamlit run app_sap_logo.py & APP_ID=${!} wait ${APP_ID}