{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lab M2 — Máy học đầu tiên + log honeypot thật\n", "\n", "> **Cách làm trong Jupyter:** bấm vào một ô code rồi nhấn **Shift + Enter** để chạy ô đó và sang ô kế tiếp. Kết quả hiện ngay dưới ô. Muốn thử khác: sửa số / chữ trong ô rồi chạy lại. Chạy **lần lượt từ trên xuống** (có bước dùng kết quả của bước trước). Kernel phải là **Python (lớp CSAI)** (góc trên bên phải)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Lab Module 2** — 2 bài: **M2** (máy học đầu tiên: Naive Bayes đối đầu cú lách luật) · **M2.2** (log honeypot thật → làm sạch → đặc trưng cho máy học). Làm lần lượt từ trên xuống.\n", "\n", "\n", "\n", "---\n", "\n", "## Bài M2 — Máy học đầu tiên: Naive Bayes đối đầu cú lách luật\n", "\n", "Ở bài M1, bộ lọc từ khóa chỉ bắt được **2/10** tin lách luật. Bài này cho **máy tự học** từ các tin nhắn đã có nhãn.\n", "\n", "### M2-1. Xem dữ liệu: máy học từ đâu, thi trên gì?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "# Mở bộ HỌC (50 tin có nhãn) và bộ THI (10 tin lách luật của bài M1)\n", "hoc = pd.read_csv(\"Manual_SOC_Dataset.csv\")\n", "thi = pd.read_csv(\"Evasion_Dataset.csv\")\n", "# Đặt tên ngắn cho 5 cột của file: mã tin, kênh, người gửi, nội dung, nhãn đúng (lừa đảo / an toàn)\n", "hoc.columns = thi.columns = [\"ma\", \"kenh\", \"nguoi_gui\", \"noi_dung\", \"nhan\"]\n", "\n", "# Đếm mỗi loại nhãn có bao nhiêu tin\n", "print(\"BỘ HỌC (Manual_SOC_Dataset.csv):\", len(hoc), \"tin\")\n", "print(hoc.nhan.value_counts().to_string())\n", "print(\"\\nBỘ THI (Evasion_Dataset.csv):\", len(thi), \"tin\")\n", "print(thi.nhan.value_counts().to_string())\n", "\n", "# In 2 tin đầu của mỗi loại nhãn (cắt 70 ký tự cho gọn)\n", "print(\"\\nVài tin mẫu trong bộ học (2 tin mỗi loại):\")\n", "for noi_dung, nhan in hoc.groupby(\"nhan\").head(2)[[\"noi_dung\", \"nhan\"]].values:\n", " print(\" \", nhan, \"|\", noi_dung[:70])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "BỘ HỌC (Manual_SOC_Dataset.csv): 50 tin\n", "nhan\n", "Lừa đảo (Phishing) 30\n", "An toàn (Benign) 20\n", "\n", "BỘ THI (Evasion_Dataset.csv): 10 tin\n", "nhan\n", "Lừa đảo (Phishing) 10\n", "\n", "Vài tin mẫu trong bộ học (2 tin mỗi loại):\n", " An toàn (Benign) | Ê ông tối nay mấy giờ đá bóng nhỉ? Sân cũ hay sân mới thế?\n", " An toàn (Benign) | Nhắc nhở: Cuộc họp toàn công ty sẽ diễn ra vào lúc 3h chiều nay tại ph\n", " Lừa đảo (Phishing) | Duy nhất hôm nay! Mua iPhone 15 Pro Max chỉ với 15 triệu. Click vào li\n", " Lừa đảo (Phishing) | Mật khẩu 'honghanh123' của bạn đã bị lộ. Tôi có toàn quyền kiểm soát m\n", "```\n", "👉 **Hiểu:** máy học từ 50 tin (30 lừa đảo, 20 an toàn). Bộ thi 10 tin — **toàn bộ là tin lừa đảo**, không có tin an toàn nào.\n", "\n", "### M2-2. Máy \"đọc\" chữ thế nào? Thử TF-IDF trên 3 câu\n", "Máy tính chỉ hiểu **con số**. **TF-IDF** chấm điểm từng từ trong câu: từ càng **nổi bật** (lặp nhiều trong câu, ít gặp ở câu khác) thì điểm càng cao." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Mượn công cụ TF-IDF của thư viện máy học scikit-learn\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "\n", "# 3 câu ví dụ (có thể sửa chữ trong ngoặc kép để thử câu của mình)\n", "cau = [\"chuyển khoản gấp vào tài khoản này\",\n", " \"chiều nay họp tại phòng họp lớn\",\n", " \"bấm vào link này để nhận quà\"]\n", "\n", "# Máy tự lập từ điển từ 3 câu, rồi biến mỗi câu thành một hàng số (điểm của từng từ)\n", "tfidf = TfidfVectorizer()\n", "bang_so = tfidf.fit_transform(cau)\n", "tu_dien = tfidf.get_feature_names_out()\n", "print(\"Từ điển máy tự tạo (\" + str(len(tu_dien)) + \" từ):\", list(tu_dien))\n", "\n", "# Với mỗi câu: in các từ có điểm > 0, điểm cao xếp trước\n", "for i in range(len(cau)):\n", " print(\"\\nCâu\", i + 1, \":\", cau[i])\n", " diem = bang_so[i].toarray()[0]\n", " for tu, d in sorted(zip(tu_dien, diem), key=lambda x: -x[1]):\n", " if d > 0:\n", " print(f\" {tu:8s} {d:.2f}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Từ điển máy tự tạo (17 từ): ['bấm', 'chiều', 'chuyển', 'gấp', 'họp', 'khoản', 'link', 'lớn', 'nay', 'nhận', 'này', 'phòng', 'quà', 'tài', 'tại', 'vào', 'để']\n", "\n", "Câu 1 : chuyển khoản gấp vào tài khoản này\n", " khoản 0.70\n", " chuyển 0.35\n", " gấp 0.35\n", " tài 0.35\n", " này 0.27\n", " vào 0.27\n", "...\n", "```\n", "👉 **Hiểu:**\n", "- `khoản` lặp **2 lần** trong câu 1 → điểm cao nhất (0.70).\n", "- `vào`, `này` có ở **cả câu 1 và câu 3** → kém đặc trưng hơn → điểm thấp hơn (0.27 so với 0.35).\n", "- Máy tách theo từng tiếng: \"tài khoản\" thành `tài` + `khoản`.\n", "- Sau bước này, **Naive Bayes** học xem từ nào hay đi với tin lừa đảo, từ nào với tin an toàn, rồi tính **xác suất** lừa đảo cho tin mới (trên 50% → lừa đảo).\n", "\n", "### M2-3. Chạy bộ lọc máy học trên 10 tin lách luật\n", "Phần chính của file `ml_classifier.py`:\n", "```python\n", "df_train = pd.read_csv(\"Manual_SOC_Dataset.csv\") # 1. mở 50 tin để học\n", "vectorizer = TfidfVectorizer()\n", "X_train_tfidf = vectorizer.fit_transform(X_train_raw) # 2. lập từ điển + biến 50 tin thành số (TF-IDF)\n", "model = MultinomialNB()\n", "model.fit(X_train_tfidf, y_train) # 3. Naive Bayes HỌC: từ nào đi với lừa đảo / an toàn\n", "X_test_tfidf = vectorizer.transform(df_test[\"Nội dung / Link\"]) # 4. biến tin THI thành số bằng ĐÚNG từ điển đã học\n", "predictions = model.predict(X_test_tfidf) # máy đoán từng tin thi\n", "correct = sum(predictions == y_test_actual) # đếm số tin đoán đúng\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Học từ 50 tin (Manual_SOC_Dataset.csv), thi trên 10 tin lách luật (Evasion_Dataset.csv)\n", "python ml_classifier.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "\n", "| Dòng cần thấy | Ý nghĩa |\n", "|---|---|\n", "| `[+] Bước 1: Đã tải dữ liệu học tập từ Manual_SOC_Dataset.csv (50 mẫu).` | đã mở 50 tin để học |\n", "| `[+] Bước 2: AI đã đọc và tự tạo một từ điển chứa 350 từ vựng (đặc trưng).` | mỗi tin giờ là một dãy 350 con số |\n", "| `[+] Bước 3: AI đã học xong chỉ trong ... giây.` | học trong chưa tới 1/10 giây (số giây mỗi lần một khác) |\n", "| 5 dòng `Mẫu: ... -> AI Phán đoán: Lừa đảo 🚨` | 5 tin thi đầu đều bị gắn lừa đảo |\n", "| `- Độ chính xác của AI: 10/10 (100.00%)` | so với **2/10** của luật cứng ở M1 |\n", "\n", "👉 **Hiểu:** máy học bắt được cả 10 tin lách luật mà không cần ai viết thêm từ khóa. Nhưng nhớ: bộ thi **không có tin an toàn nào** — 100% chưa nói gì về việc máy có **chặn nhầm** tin thật hay không.\n", "\n", "### M2-4. Máy chắc chắn đến đâu?\n", "Học lại đúng như bước 3, nhưng in **xác suất lừa đảo** máy tính ra cho từng tin:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "# Công cụ TF-IDF (chữ -> số) và thuật toán Naive Bayes\n", "from sklearn.feature_extraction.text import TfidfVectorizer\n", "from sklearn.naive_bayes import MultinomialNB\n", "\n", "hoc = pd.read_csv(\"Manual_SOC_Dataset.csv\")\n", "thi = pd.read_csv(\"Evasion_Dataset.csv\")\n", "# Đặt tên ngắn cho 5 cột: mã tin, kênh, người gửi, nội dung, nhãn đúng\n", "hoc.columns = thi.columns = [\"ma\", \"kenh\", \"nguoi_gui\", \"noi_dung\", \"nhan\"]\n", "\n", "# Lập từ điển + biến 50 tin học thành số\n", "tfidf = TfidfVectorizer()\n", "X_hoc = tfidf.fit_transform(hoc.noi_dung)\n", "# Nhãn -> số: 1 = lừa đảo, 0 = an toàn\n", "y_hoc = hoc.nhan.str.contains(\"Lừa đảo\").astype(int)\n", "# Naive Bayes học\n", "mo_hinh = MultinomialNB().fit(X_hoc, y_hoc)\n", "\n", "# predict_proba = xác suất của từng tin; [:, 1] = cột xác suất \"lừa đảo\"\n", "xac_suat = mo_hinh.predict_proba(tfidf.transform(thi.noi_dung))[:, 1]\n", "for ma, tin, p in zip(thi.ma, thi.noi_dung, xac_suat):\n", " # Trên 50% -> kết luận lừa đảo\n", " ket_luan = \"LỪA ĐẢO\" if p >= 0.5 else \"an toàn\"\n", " print(f\"{ma} | khả năng lừa đảo {p:4.0%} -> {ket_luan:7s} | {tin[:55]}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "EVA-001 | khả năng lừa đảo 81% -> LỪA ĐẢO | Hệ thống bank đang cập nhật nha. Cậu chuyển khoản vào t\n", "EVA-002 | khả năng lừa đảo 68% -> LỪA ĐẢO | Trang web www.viettel-khuyenmai.com đang có chương trìn\n", "EVA-003 | khả năng lừa đảo 63% -> LỪA ĐẢO | Ông ơi tớ bị tông xe trên đường cao tốc, bắn tớ 5 củ và\n", "EVA-004 | khả năng lừa đảo 79% -> LỪA ĐẢO | Hòm thư sẽ tự động vô hiệu hóa. Truy cập đường dẫn www.\n", "EVA-005 | khả năng lừa đảo 55% -> LỪA ĐẢO | Kính gửi anh/chị. Công ty chúng tôi đang tái cấu trúc q\n", "EVA-006 | khả năng lừa đảo 67% -> LỪA ĐẢO | Giao dịch của bạn đã bị hoãn tạm thời. Vui lòng xác min\n", "EVA-007 | khả năng lừa đảo 79% -> LỪA ĐẢO | De hoan thue dien tu thu nhap ca nhan nam nay. Vui long\n", "EVA-008 | khả năng lừa đảo 56% -> LỪA ĐẢO | Chồng ơi máy em hỏng màn hình phải dùng máy bạn, anh bắ\n", "EVA-009 | khả năng lừa đảo 71% -> LỪA ĐẢO | Tôi nắm trong tay đoạn video nhạy cảm của bạn. Nộp 10 t\n", "EVA-010 | khả năng lừa đảo 65% -> LỪA ĐẢO | Thiết bị di động lạ đang cố gắng truy cập tài khoản Goo\n", "```\n", "👉 **Hiểu:** cả 10 tin đều đúng, nhưng máy **không chắc lắm**: cao nhất chỉ 81%, EVA-005 (55%) và EVA-008 (56%) chỉ nhỉnh hơn ngưỡng 50% một chút. Tin lách luật dùng nhiều từ máy chưa từng gặp, nên máy có ít \"bằng chứng\" để dựa vào.\n", "\n", "### M2-5. Đảo vai: học từ 10 tin lách luật, thi trên 50 tin cũ" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Đảo vai: HỌC từ 10 tin lách luật, THI trên 50 tin cũ\n", "python ml_classifier.py --train Evasion_Dataset.csv --test Manual_SOC_Dataset.csv\n", "# Đếm nhãn trong 10 tin vừa dùng để học (iloc[:, -1] = cột cuối cùng = nhãn)\n", "python -c \"import pandas as pd; print(pd.read_csv('Evasion_Dataset.csv').iloc[:, -1].rename('nhan').value_counts().to_string())\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "- `[+] Bước 2: AI đã đọc và tự tạo một từ điển chứa 174 từ vựng (đặc trưng).`\n", "- 5 dòng `Mẫu:` — **cả 5** đều `Lừa đảo 🚨`, kể cả tin hẹn đá bóng và tin nhắc họp\n", "- `- Độ chính xác của AI: 30/50 (60.00%)`\n", "- đếm nhãn: `Lừa đảo (Phishing) 10`\n", "\n", "👉 **Hiểu:** 10 tin dùng để học **toàn là lừa đảo** → máy chưa từng thấy tin an toàn → nó gắn **mọi** tin là lừa đảo. 60% = đúng bằng 30 tin lừa đảo trong 50 tin cũ. **Dữ liệu học thiếu một loại thì máy không thể nhận ra loại đó.**\n", "\n", "### M2-6. Tự ra đề thi có cả tin an toàn\n", "Tạo file đề thi 4 tin (3 an toàn, 1 lừa đảo), cho máy (học từ 50 tin như bước 3) làm bài:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Tạo file đề thi: chép dòng tiêu đề từ file có sẵn, rồi thêm 4 tin (3 an toàn, 1 lừa đảo)\n", "head -1 Manual_SOC_Dataset.csv > ~/lab/my_test_M2.csv\n", "cat >> ~/lab/my_test_M2.csv <<'EOF'\n", "T-01,Email,hr@company.com,Chiều nay 3h họp toàn công ty tại phòng họp lớn. Mọi người nhớ mang laptop.,An toàn (Benign)\n", "T-02,Zalo,Mẹ,Con ơi tối nay về ăn cơm nhé mẹ nấu canh chua.,An toàn (Benign)\n", "T-03,Email,IT,Hệ thống email sẽ bảo trì từ 22h đến 23h tối nay. Không cần làm gì thêm.,An toàn (Benign)\n", "T-04,SMS,Ngân hàng,Tài khoản của bạn bị khóa. Bấm vào link xac-minh-ngay.com và nhập mã OTP để mở khóa.,Lừa đảo (Phishing)\n", "EOF\n", "# Học từ 50 tin như cũ, thi trên 4 tin vừa tạo\n", "python ml_classifier.py --test ~/lab/my_test_M2.csv" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Mẫu: Chiều nay 3h họp toàn công ty tại phòng họp lớn. Mọi người n... -> AI Phán đoán: An toàn ✅\n", "Mẫu: Con ơi tối nay về ăn cơm nhé mẹ nấu canh chua.... -> AI Phán đoán: Lừa đảo 🚨\n", "Mẫu: Hệ thống email sẽ bảo trì từ 22h đến 23h tối nay. Không cần ... -> AI Phán đoán: Lừa đảo 🚨\n", "Mẫu: Tài khoản của bạn bị khóa. Bấm vào link xac-minh-ngay.com và... -> AI Phán đoán: Lừa đảo 🚨\n", "- Độ chính xác của AI: 2/4 (50.00%)\n", "```\n", "👉 **Hiểu:** mô hình \"100%\" ở bước 3 gắn nhầm **tin mẹ nhắn con** và **thông báo bảo trì của IT** là lừa đảo. Đưa vào hệ thống thật, người dùng sẽ bị chặn tin bình thường. Dòng cuối script luôn in \"AI vẫn tóm được!\" kể cả khi chỉ đúng 50% — **đừng tin câu kết luận do chính công cụ in ra, hãy nhìn con số**.\n", "\n", "---\n", "\n", "## Bài M2.2 — Log honeypot thật: làm sạch → biểu đồ → đặc trưng cho máy học\n", "\n", "### M2.2-1. Thông tin của một lần tấn công nằm trên nhiều dòng" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "# Tìm mọi dòng log có mã phiên (session) 54613b5cec67 trong ngày 22/09\n", "grep '\"54613b5cec67\"' /opt/csai/data/honeypot_m1/cowrie.json.2026-09-22" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "{\"eventid\": \"cowrie.session.connect\", \"timestamp\": \"2026-09-22T00:00:02.779416Z\", \"src_ip\": \"109.160.32.67\", \"src_port\": 36330, \"session\": \"54613b5cec67\"}\n", "{\"eventid\": \"cowrie.client.version\", \"timestamp\": \"2026-09-22T00:00:02.793738Z\", \"src_ip\": \"109.160.32.67\", \"session\": \"54613b5cec67\", \"version\": \"SSH-2.0-Go\"}\n", "{\"eventid\": \"cowrie.login.failed\", \"timestamp\": \"2026-09-22T00:00:03.650371Z\", \"src_ip\": \"109.160.32.67\", \"session\": \"54613b5cec67\", \"username\": \"ftpuser1\", \"password\": \"123456\"}\n", "```\n", "👉 **Hiểu:** cùng **một** lần tấn công (cùng `session`) nhưng nằm trên **3 dòng**: dòng `connect` có IP và giờ, còn tên phần mềm (`version`) ở dòng khác. Muốn mỗi kết nối là một hàng đủ IP + giờ + phần mềm thì phải **ghép các dòng theo `session`** — việc của Part A.\n", "\n", "### M2.2-2. Part A — làm sạch và ghép dữ liệu\n", "\n", "| Bước trong `Lab_M2_Part_A_Clean.py` | Làm gì |\n", "|---|---|\n", "| 1. Nạp | đọc cả 30 file, mỗi dòng JSON thành một hàng (395.124 hàng) |\n", "| Chuẩn bị ghép | lập \"danh bạ\": **mã session → tên phần mềm** (từ các dòng `client.version`) |\n", "| 2. Lọc | chỉ giữ các dòng **kết nối** — mỗi kết nối 1 hàng |\n", "| Ghép | tra \"danh bạ\" để điền tên phần mềm vào từng kết nối |\n", "| 3. Tách thời gian | từ chuỗi giờ tách ra **ngày**, **giờ** (UTC), **thứ trong tuần** |\n", "| 4. Làm sạch | bỏ dòng thiếu IP; kết nối không khai phần mềm ghi `unknown` |\n", "| Lưu | ghi bảng sạch ra `cleaned_honeypot_data.csv` |" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Part A: nạp 30 ngày log, lọc kết nối, ghép tên phần mềm theo session, tách ngày/giờ, lưu bảng sạch\n", "python Lab_M2_Part_A_Clean.py" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "\n", "| Dòng cần thấy | Ý nghĩa |\n", "|---|---|\n", "| `[!] Đã tải: 395,124 events trong … giây.` | đọc hết 30 ngày log trong khoảng 2 giây |\n", "| `Còn lại: 136,063 kết nối hợp lệ \\| Từ 2,906 địa chỉ IP riêng biệt.` | mỗi hàng giờ là **một kết nối** |\n", "| `Có phiên bản client: 95.5% kết nối (…)` | 95,5% kết nối ghép được tên phần mềm |\n", "| bảng 5 dòng `timestamp`, `src_ip`, `hour`, `client_version` | hàng đầu: `217.154.13.82`, giờ `0`, `SSH-2.0-Go` |\n", "| `Đã lưu file 'cleaned_honeypot_data.csv' (136,063 dòng) ...` | bảng sạch sẵn sàng |\n", "\n", "👉 **Hiểu:** 395.124 dòng còn 136.063 dòng vì chỉ giữ dòng **kết nối**; các dòng `client.version` không bị mất mà đã được **ghép** vào kết nối tương ứng.\n", "\n", "### M2.2-3. Part B — 5 biểu đồ, mỗi biểu đồ gợi ra một đặc trưng" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Part B: đọc bảng sạch, vẽ 5 biểu đồ (lưu ảnh vào out_m2/) và in 1 dòng số liệu cho mỗi biểu đồ\n", "python Lab_M2_Part_B_Visualize.py\n", "# Xem tên 5 file ảnh vừa tạo\n", "ls out_m2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Hiện các biểu đồ trong out_m2/ ngay dưới ô này\n", "import glob, os\n", "from IPython.display import Image, display\n", "for f in sorted(glob.glob(os.path.expanduser('~/lab/day1_labs/out_m2/*.png'))):\n", " print(f)\n", " display(Image(filename=f, width=800))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả** (ảnh biểu đồ lưu trong `out_m2/`, giảng viên chiếu trên lớp; các dòng số liệu dưới đây là đủ để hiểu):\n", "\n", "| Biểu đồ | Dòng cần thấy | 👉 Hiểu | Đặc trưng |\n", "|---|---|---|---|\n", "| — | `136,063 kết nối, 2,906 IP, 30 ngày` | đã mở đúng bảng sạch | — |\n", "| 1. Theo ngày | `ngày đông nhất: 2026-09-20 (12,620 kết nối) \\| vắng nhất: 2026-09-05 (1,314) \\| trung bình 4,535/ngày` | có ngày **bùng nổ** gấp gần 3 lần trung bình | số kết nối mỗi ngày |\n", "| 2. Top 20 IP | `top 20 IP = 29% số kết nối; top 1% IP (29 IP) = 38%` | chỉ **1% số IP** tạo ra gần **40%** lưu lượng | tổng kết nối của mỗi IP |\n", "| 3. Theo giờ | `giờ đông nhất 17h UTC (7,459) \\| vắng nhất 11h UTC (4,049) \\| gấp 1.8 lần` | tấn công **gần như suốt 24h** (17h UTC = 0h Việt Nam) | giờ tấn công |\n", "| 4. Phần mềm | `Go 88.5% · unknown 5.0% · libssh 3.1% · PuTTY 1.0% · phpseclib 0.7%` | gần 9/10 kết nối là **chương trình tự động** viết bằng Go | có phải công cụ tự động không |\n", "| 5. Kết nối/ngày mỗi IP | `kết nối/ngày hoạt động mỗi IP: trung vị 2.0 · 90% 19.2 · 99% 785.0 · cao nhất 1,437` | một nửa số IP chỉ ~2 kết nối/ngày; 1% hung hãn nhất trên 785/ngày | mức độ hung hăng |\n", "| — | `HOÀN THÀNH! 5 biểu đồ trong thư mục out_m2/.` + 5 tên file `chart1_daily.png` … `chart5_conn_per_day.png` | đủ 5 ảnh | — |\n", "\n", "(\"Trung vị 2.0\" = xếp mọi IP theo số kết nối/ngày, IP **đứng giữa** có 2. \"99% 785.0\" = chỉ 1% số IP vượt quá 785 kết nối/ngày.)\n", "\n", "### M2.2-4. Xếp đặc trưng thành bảng cho máy học\n", "Máy học (Module 3) cần một **bảng**: mỗi IP một hàng, mỗi đặc trưng một cột." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "# Mở bảng sạch của Part A (mỗi hàng = 1 kết nối)\n", "d = pd.read_csv(\"cleaned_honeypot_data.csv\")\n", "\n", "# Tên phần mềm ngắn gọn: \"SSH-2.0-Go\" -> \"Go\", \"SSH-2.0-PuTTY_Release_0.84\" -> \"PuTTY\"; không khai báo -> \"unknown\"\n", "d[\"phan_mem\"] = d.client_version.str.extract(r\"^SSH-[\\d.]+-([A-Za-z]+)\")[0].fillna(\"unknown\")\n", "\n", "# Gom mọi kết nối của cùng một IP thành MỘT hàng; mỗi cột là một đặc trưng:\n", "bang = d.groupby(\"src_ip\").agg(\n", " so_ket_noi=(\"src_ip\", \"size\"), # tổng số kết nối\n", " so_ngay=(\"date\", \"nunique\"), # xuất hiện bao nhiêu ngày khác nhau\n", " so_khung_gio=(\"hour\", \"nunique\"), # hoạt động trong bao nhiêu giờ khác nhau của ngày (tối đa 24)\n", " phan_mem=(\"phan_mem\", lambda s: s.mode()[0]), # phần mềm dùng nhiều nhất\n", ")\n", "# Trung bình mỗi ngày hoạt động kết nối bao nhiêu lần\n", "bang[\"ket_noi_moi_ngay\"] = (bang.so_ket_noi / bang.so_ngay).round(1)\n", "\n", "print(\"Bảng đặc trưng:\", len(bang), \"IP x\", len(bang.columns), \"đặc trưng\")\n", "# 5 IP kết nối nhiều nhất\n", "print(\"\\n5 IP kết nối nhiều nhất:\")\n", "print(bang.sort_values(\"so_ket_noi\", ascending=False).head(5).to_string())\n", "# 5 IP chọn ngẫu nhiên (random_state=1: lần nào chạy cũng ra cùng 5 IP)\n", "print(\"\\n5 IP chọn ngẫu nhiên:\")\n", "print(bang.sample(5, random_state=1).to_string())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Bảng đặc trưng: 2906 IP x 5 đặc trưng\n", "\n", "5 IP kết nối nhiều nhất:\n", " so_ket_noi so_ngay so_khung_gio phan_mem ket_noi_moi_ngay\n", "src_ip\n", "154.18.197.29 5569 12 24 Go 464.1\n", "130.12.182.196 3757 6 24 Go 626.2\n", "109.160.32.31 2774 3 5 Go 924.7\n", "103.245.237.17 2350 7 24 Go 335.7\n", "85.11.167.127 2291 4 7 Go 572.8\n", "\n", "5 IP chọn ngẫu nhiên:\n", " so_ket_noi so_ngay so_khung_gio phan_mem ket_noi_moi_ngay\n", "src_ip\n", "197.185.168.102 1 1 1 unknown 1.0\n", "184.58.114.52 2 1 1 unknown 2.0\n", "87.103.105.10 1 1 1 Go 1.0\n", "124.106.88.166 2 1 1 unknown 2.0\n", "5.226.140.24 2 2 2 OpenSSH 1.0\n", "```\n", "👉 **Hiểu:** 395.124 dòng log → **2.906 hàng × 5 cột số**. IP top kết nối hàng nghìn lần, suốt 24 giờ; IP ngẫu nhiên chỉ 1–2 lần rồi biến mất. Bảng này là \"nhiên liệu\" cho máy học ở Module 3.\n", "\n", "### M2.2-5. Top 5 IP chiếm bao nhiêu? Ai gây ra ngày bùng nổ?" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%%bash\n", "cd ~/lab/day1_labs\n", "# Top 5 IP chiếm bao nhiêu phần tổng số kết nối? (cộng số kết nối của 5 IP đầu, chia cho tổng; 0.25 = 25%)\n", "python -c \"import pandas as pd; d=pd.read_csv('cleaned_honeypot_data.csv'); print(d.src_ip.value_counts().head(5).sum()/len(d))\"\n", "# Ngày bùng nổ 20/09: chỉ giữ kết nối của ngày đó, rồi lấy 3 IP kết nối nhiều nhất\n", "python -c \"import pandas as pd; d=pd.read_csv('cleaned_honeypot_data.csv'); print(d[d.date=='2026-09-20'].src_ip.value_counts().head(3))\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "- `0.12303859241674812` → top 5 IP ≈ **12,3%** tổng số kết nối\n", "- `109.160.32.15 1099` · `109.160.32.19 1099` · `109.160.32.18 1099`\n", "\n", "👉 **Hiểu:** 3 IP **liền nhau** (cùng dải `109.160.32.x`) với **đúng cùng 1.099** kết nối trong một ngày → nhiều khả năng là **một** hệ thống điều khiển nhiều máy, không phải 3 kẻ tấn công riêng lẻ.\n", "\n", "### M2.2-6. Tra cứu sâu trên bảng sạch\n", "\n", "**6a — IP nào đổi nhiều phần mềm nhất?**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "d = pd.read_csv(\"cleaned_honeypot_data.csv\")\n", "# Rút gọn tên phần mềm: \"SSH-2.0-Go\" -> \"Go\"; kết nối không khai báo -> \"unknown\"\n", "d[\"phan_mem\"] = d.client_version.str.extract(r\"^SSH-[\\d.]+-([A-Za-z]+)\")[0].fillna(\"unknown\")\n", "# Với mỗi IP: đếm số phần mềm KHÁC NHAU nó đã dùng (bỏ qua \"unknown\")\n", "so_phan_mem = d[d.phan_mem != \"unknown\"].groupby(\"src_ip\").phan_mem.nunique()\n", "print(\"Số IP chỉ dùng 1 phần mềm: \", (so_phan_mem == 1).sum())\n", "print(\"Số IP dùng từ 2 phần mềm trở lên:\", (so_phan_mem >= 2).sum())\n", "# 5 IP đổi nhiều phần mềm nhất, kèm danh sách phần mềm (\", \".join = nối thành một dòng)\n", "print(\"\\nTOP 5 IP đổi nhiều phần mềm nhất:\")\n", "for ip in so_phan_mem.sort_values(ascending=False).head(5).index:\n", " cac_pm = d[(d.src_ip == ip) & (d.phan_mem != \"unknown\")].phan_mem.unique()\n", " print(f\" {ip:16s} {len(cac_pm)} phần mềm: {', '.join(sorted(cac_pm))}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Số IP chỉ dùng 1 phần mềm: 1279\n", "Số IP dùng từ 2 phần mềm trở lên: 67\n", "\n", "TOP 5 IP đổi nhiều phần mềm nhất:\n", " 185.246.128.133 8 phần mềm: Granados, JSCH, Nmap, OpenSSH, PuTTY, WinSCP, libssh, paramiko\n", " 34.14.58.11 4 phần mềm: Fingerprintx, Go, Nmap, libssh\n", " 34.14.104.231 4 phần mềm: Fingerprintx, Go, Nmap, libssh\n", " 172.232.232.48 4 phần mềm: Nmap, NmapNSE, OpenSSH, libssh\n", " 172.232.232.179 4 phần mềm: Nmap, NmapNSE, OpenSSH, libssh\n", "```\n", "👉 **Hiểu:** đa số IP chỉ dùng **một** công cụ (một con bot làm một việc). Vài IP dùng hàng loạt công cụ, có cả **Nmap** (công cụ quét và nhận dạng dịch vụ) → đang dò xem máy chủ trả lời thế nào. \"Số phần mềm khác nhau của một IP\" cũng là một đặc trưng tốt.\n", "\n", "**6b — IP \"không bao giờ ngủ\": hoạt động đủ 24 giờ trong ngày**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "d = pd.read_csv(\"cleaned_honeypot_data.csv\")\n", "# Với mỗi IP: đếm số GIỜ khác nhau (0-23, giờ UTC) mà nó có kết nối\n", "so_gio = d.groupby(\"src_ip\").hour.nunique()\n", "print(\"Số IP hoạt động ĐỦ 24 giờ trong ngày:\", (so_gio == 24).sum())\n", "print(\"Số IP chỉ hoạt động trong 1 giờ: \", (so_gio == 1).sum())\n", "# Các IP đủ 24 giờ chiếm bao nhiêu phần trăm tổng số kết nối? (.isin = \"nằm trong danh sách\")\n", "ip_24h = so_gio[so_gio == 24].index\n", "print(f\"-> {len(ip_24h)} IP 'không bao giờ ngủ' tạo ra {d.src_ip.isin(ip_24h).mean():.0%} tổng số kết nối\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Số IP hoạt động ĐỦ 24 giờ trong ngày: 80\n", "Số IP chỉ hoạt động trong 1 giờ: 2312\n", "-> 80 IP 'không bao giờ ngủ' tạo ra 35% tổng số kết nối\n", "```\n", "👉 **Hiểu:** chỉ **80 IP** (chưa tới 3% số IP) nhưng tạo ra hơn 1/3 lưu lượng, có mặt ở **mọi giờ** → máy chạy tự động.\n", "\n", "**6c — Mổ xẻ ngày bùng nổ**" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%cd ~/lab/day1_labs\n", "import pandas as pd\n", "# ===== SỬA DÒNG NÀY: ngày muốn soi (theo ngày UTC như Part B), trong khoảng 2026-08-24 .. 2026-09-22 =====\n", "NGAY_CAN_SOI = \"2026-09-20\"\n", "d = pd.read_csv(\"cleaned_honeypot_data.csv\")\n", "# Lọc các kết nối của đúng ngày đó (cột date do Part A tạo)\n", "mot_ngay = d[d.date == NGAY_CAN_SOI]\n", "# Trung bình mỗi ngày = tổng kết nối / số ngày khác nhau\n", "tb = len(d) / d.date.nunique()\n", "print(f\"Ngày {NGAY_CAN_SOI}: {len(mot_ngay)} kết nối = gấp {len(mot_ngay) / tb:.1f} lần trung bình ({tb:.0f}/ngày), từ {mot_ngay.src_ip.nunique()} IP\")\n", "# Cắt số cuối của IP -> dải /24, đếm số kết nối của mỗi dải trong ngày đó\n", "dai = mot_ngay.src_ip.str.rsplit(\".\", n=1).str[0] + \".0/24\"\n", "print(\"TOP 3 dải mạng trong ngày:\")\n", "print(dai.rename(\"dai_mang\").value_counts().head(3).to_string())\n", "# Dải đứng đầu chiếm bao nhiêu phần trăm số kết nối của ngày\n", "print(f\"-> dải đứng đầu chiếm {dai.value_counts().iloc[0] / len(mot_ngay):.0%} số kết nối của ngày\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Kết quả:**\n", "```\n", "Ngày 2026-09-20: 12620 kết nối = gấp 2.8 lần trung bình (4535/ngày), từ 180 IP\n", "TOP 3 dải mạng trong ngày:\n", "dai_mang\n", "109.160.32.0/24 9889\n", "103.245.237.0/24 559\n", "195.178.110.0/24 155\n", "-> dải đứng đầu chiếm 78% số kết nối của ngày\n", "```\n", "👉 **Hiểu:** ngày bùng nổ không phải \"cả Internet cùng tấn công\" — **một dải mạng** tạo ra gần 4/5 số kết nối. Thử đổi ngày thành `\"2026-09-05\"` (ngày vắng nhất) để so sánh." ] } ], "nbformat": 4, "nbformat_minor": 5, "metadata": { "kernelspec": { "name": "csai", "display_name": "Python (lớp CSAI)", "language": "python" }, "language_info": { "name": "python" } } }