CSAI2026 labs: Lab-2..Lab-6 (Practical + Solution, data), Module-2..Module-6 notebooks and datasets

This commit is contained in:
mikrotik
2026-10-12 19:16:14 +07:00
commit 2fefa615cc
184 changed files with 1016761 additions and 0 deletions
@@ -0,0 +1,325 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "1727969b",
"metadata": {},
"source": [
"# Module 2 — Lab 6: Capstone — Investigating Suspicious Logins\n",
"\n",
" \n",
"**Topics:** Pandas, NumPy, Matplotlib, export, analyst summary\n",
"\n",
"\n",
"> **Solution version:** This notebook contains the complete solution. Run the cells in order and compare the results with your Lab notebook.\n"
]
},
{
"cell_type": "markdown",
"id": "467cdb20",
"metadata": {},
"source": [
"## Mission\n",
"\n",
"As a junior security analyst, you must build a complete workflow:\n",
"\n",
"1. load and clean authentication logs;\n",
"2. identify IP addresses with repeated failed logins;\n",
"3. identify off-hours activity;\n",
"4. visualize the results;\n",
"5. export the data and write a short conclusion.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "525cb519",
"metadata": {},
"outputs": [],
"source": [
"from pathlib import Path\n",
"import numpy as np\n",
"import pandas as pd\n",
"import matplotlib.pyplot as plt\n",
"\n",
"DATA_PATH = Path(\"data/auth_log.csv\")\n",
"OUTPUT_DIR = Path(\"output\")\n",
"OUTPUT_DIR.mkdir(exist_ok=True)\n"
]
},
{
"cell_type": "markdown",
"id": "d94da21b",
"metadata": {
"tags": [
"exercise"
]
},
"source": [
"## 1. Write a reusable cleaning function\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c33bbbcb",
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"source": [
"def clean_auth_log(df):\n",
" result = df.copy().drop_duplicates()\n",
" result[\"timestamp\"] = pd.to_datetime(\n",
" result[\"timestamp\"], errors=\"coerce\", utc=True\n",
" )\n",
" result = result.dropna(subset=[\"timestamp\", \"source_ip\", \"status\"])\n",
" result[\"source_ip\"] = result[\"source_ip\"].str.strip()\n",
" result[\"username\"] = result[\"username\"].fillna(\"unknown\").str.strip().str.lower()\n",
" result[\"status\"] = result[\"status\"].str.strip().str.upper()\n",
" result = result[result[\"status\"].isin([\"SUCCESS\", \"FAILED\"])]\n",
" result[\"hour\"] = result[\"timestamp\"].dt.hour\n",
" result[\"is_off_hours\"] = (result[\"hour\"] < 6) | (result[\"hour\"] >= 22)\n",
" return result.sort_values(\"timestamp\").reset_index(drop=True)\n",
"\n",
"\n",
"raw_df = pd.read_csv(DATA_PATH)\n",
"clean_df = clean_auth_log(raw_df)\n",
"print(f\"Raw rows: {len(raw_df)} | Clean rows: {len(clean_df)}\")\n",
"clean_df.head()\n"
]
},
{
"cell_type": "markdown",
"id": "5205cafa",
"metadata": {
"tags": [
"exercise"
]
},
"source": [
"## 2. Analyze failures and rank IP addresses\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "085dd483",
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"source": [
"failed_df = clean_df[clean_df[\"status\"] == \"FAILED\"].copy()\n",
"failed_by_ip = (\n",
" failed_df.groupby(\"source_ip\")\n",
" .size()\n",
" .sort_values(ascending=False)\n",
" .rename(\"failed_attempts\")\n",
")\n",
"\n",
"THRESHOLD = 3\n",
"suspicious_ips = failed_by_ip[failed_by_ip >= THRESHOLD]\n",
"\n",
"print(\"Failed attempts by IP:\")\n",
"print(failed_by_ip)\n",
"print(\"\\nSuspicious IPs:\")\n",
"print(suspicious_ips)\n"
]
},
{
"cell_type": "markdown",
"id": "5967176b",
"metadata": {
"tags": [
"exercise"
]
},
"source": [
"## 3. Calculate additional investigation metrics\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7cab677c",
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"source": [
"status_counts = clean_df[\"status\"].value_counts().reindex([\"SUCCESS\", \"FAILED\"], fill_value=0)\n",
"off_hours_failed = failed_df[failed_df[\"is_off_hours\"]]\n",
"top_ip = failed_by_ip.index[0]\n",
"top_ip_count = int(failed_by_ip.iloc[0])\n",
"failure_rate = np.round((status_counts[\"FAILED\"] / status_counts.sum()) * 100, 2)\n",
"\n",
"print(f\"Top suspicious IP: {top_ip} ({top_ip_count} failures)\")\n",
"print(f\"Failure rate: {failure_rate}%\")\n",
"print(f\"Off-hours failed attempts: {len(off_hours_failed)}\")\n"
]
},
{
"cell_type": "markdown",
"id": "c472f43a",
"metadata": {
"tags": [
"exercise"
]
},
"source": [
"## 4. Visualize the findings\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4816f960",
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"source": [
"fig, axes = plt.subplots(1, 2, figsize=(11, 4))\n",
"\n",
"status_counts.plot(\n",
" kind=\"bar\",\n",
" ax=axes[0],\n",
" color=[\"#2a9d8f\", \"#e76f51\"],\n",
" title=\"Authentication status\",\n",
")\n",
"axes[0].set_xlabel(\"Status\")\n",
"axes[0].set_ylabel(\"Number of attempts\")\n",
"axes[0].tick_params(axis=\"x\", rotation=0)\n",
"\n",
"failed_by_ip.head(5).sort_values().plot(\n",
" kind=\"barh\",\n",
" ax=axes[1],\n",
" color=\"#e76f51\",\n",
" title=\"Top IPs by failed logins\",\n",
")\n",
"axes[1].set_xlabel(\"Failed attempts\")\n",
"axes[1].set_ylabel(\"Source IP\")\n",
"\n",
"fig.suptitle(\"Authentication Log Investigation\")\n",
"fig.tight_layout()\n",
"fig.savefig(OUTPUT_DIR / \"auth_log_analysis.png\", dpi=150, bbox_inches=\"tight\")\n",
"plt.show()\n"
]
},
{
"cell_type": "markdown",
"id": "933a5898",
"metadata": {
"tags": [
"exercise"
]
},
"source": [
"## 5. Export results and write a short report\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fc68ed13",
"metadata": {
"tags": [
"solution"
]
},
"outputs": [],
"source": [
"clean_df.to_csv(OUTPUT_DIR / \"capstone_clean_auth_log.csv\", index=False)\n",
"suspicious_ips.to_csv(\n",
" OUTPUT_DIR / \"capstone_suspicious_ips.csv\",\n",
" header=True,\n",
")\n",
"\n",
"summary = f'''SECURITY ANALYSIS SUMMARY\n",
"=========================\n",
"Clean events: {len(clean_df)}\n",
"Failed events: {int(status_counts['FAILED'])}\n",
"Failure rate: {failure_rate}%\n",
"Top suspicious IP: {top_ip} ({top_ip_count} failed attempts)\n",
"Off-hours failed attempts: {len(off_hours_failed)}\n",
"Decision threshold: {THRESHOLD} failed attempts\n",
"\n",
"Recommended action:\n",
"- Review events from {top_ip}.\n",
"- Correlate off-hours failures with endpoint and identity logs.\n",
"- Validate whether the account owners recognize these attempts.\n",
"'''\n",
"\n",
"(OUTPUT_DIR / \"capstone_summary.txt\").write_text(summary, encoding=\"utf-8\")\n",
"print(summary)\n"
]
},
{
"cell_type": "markdown",
"id": "023ea53a",
"metadata": {},
"source": [
"## Self-check\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "560fa29e",
"metadata": {},
"outputs": [],
"source": [
"assert len(raw_df) > len(clean_df)\n",
"assert clean_df.duplicated().sum() == 0\n",
"assert failed_by_ip.index[0] == \"198.51.100.14\"\n",
"assert top_ip_count >= THRESHOLD\n",
"assert len(off_hours_failed) >= 1\n",
"assert (OUTPUT_DIR / \"auth_log_analysis.png\").exists()\n",
"assert (OUTPUT_DIR / \"capstone_summary.txt\").exists()\n",
"print(\"✓ Capstone lab completed\")\n"
]
},
{
"cell_type": "markdown",
"id": "dedf0889",
"metadata": {},
"source": [
"## Report questions\n",
"\n",
"1. Which IP address should be investigated first, and what evidence supports that decision?\n",
"2. In what situations could a threshold of three failed attempts produce a false positive?\n",
"3. Which additional log sources should be reviewed before deciding to block an IP address?\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}