Files

33 KiB

Lab M2 — Máy học đầu tiên + log honeypot thật

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).

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.


Bài M2 — Máy học đầu tiên: Naive Bayes đối đầu cú lách luật

Ở 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.

M2-1. Xem dữ liệu: máy học từ đâu, thi trên gì?

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
# Mở bộ HỌC (50 tin có nhãn) và bộ THI (10 tin lách luật của bài M1)
hoc = pd.read_csv("Manual_SOC_Dataset.csv")
thi = pd.read_csv("Evasion_Dataset.csv")
# Đặ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)
hoc.columns = thi.columns = ["ma", "kenh", "nguoi_gui", "noi_dung", "nhan"]

# Đếm mỗi loại nhãn có bao nhiêu tin
print("BỘ HỌC  (Manual_SOC_Dataset.csv):", len(hoc), "tin")
print(hoc.nhan.value_counts().to_string())
print("\nBỘ THI  (Evasion_Dataset.csv):", len(thi), "tin")
print(thi.nhan.value_counts().to_string())

# In 2 tin đầu của mỗi loại nhãn (cắt 70 ký tự cho gọn)
print("\nVài tin mẫu trong bộ học (2 tin mỗi loại):")
for noi_dung, nhan in hoc.groupby("nhan").head(2)[["noi_dung", "nhan"]].values:
    print(" ", nhan, "|", noi_dung[:70])

Kết quả:

BỘ HỌC  (Manual_SOC_Dataset.csv): 50 tin
nhan
Lừa đảo (Phishing)    30
An toàn (Benign)      20

BỘ THI  (Evasion_Dataset.csv): 10 tin
nhan
Lừa đảo (Phishing)    10

Vài tin mẫu trong bộ học (2 tin mỗi loại):
  An toàn (Benign) | Ê ông tối nay mấy giờ đá bóng nhỉ? Sân cũ hay sân mới thế?
  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
  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
  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

👉 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.

M2-2. Máy "đọc" chữ thế nào? Thử TF-IDF trên 3 câu

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.

In [ ]:
# Mượn công cụ TF-IDF của thư viện máy học scikit-learn
from sklearn.feature_extraction.text import TfidfVectorizer

# 3 câu ví dụ (có thể sửa chữ trong ngoặc kép để thử câu của mình)
cau = ["chuyển khoản gấp vào tài khoản này",
       "chiều nay họp tại phòng họp lớn",
       "bấm vào link này để nhận quà"]

# 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ừ)
tfidf = TfidfVectorizer()
bang_so = tfidf.fit_transform(cau)
tu_dien = tfidf.get_feature_names_out()
print("Từ điển máy tự tạo (" + str(len(tu_dien)) + " từ):", list(tu_dien))

# Với mỗi câu: in các từ có điểm > 0, điểm cao xếp trước
for i in range(len(cau)):
    print("\nCâu", i + 1, ":", cau[i])
    diem = bang_so[i].toarray()[0]
    for tu, d in sorted(zip(tu_dien, diem), key=lambda x: -x[1]):
        if d > 0:
            print(f"   {tu:8s} {d:.2f}")

Kết quả:

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', 'để']

Câu 1 : chuyển khoản gấp vào tài khoản này
   khoản    0.70
   chuyển   0.35
   gấp      0.35
   tài      0.35
   này      0.27
   vào      0.27
...

👉 Hiểu:

  • khoản lặp 2 lần trong câu 1 → điểm cao nhất (0.70).
  • 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).
  • Máy tách theo từng tiếng: "tài khoản" thành tài + khoả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).

M2-3. Chạy bộ lọc máy học trên 10 tin lách luật

Phần chính của file ml_classifier.py:

df_train = pd.read_csv("Manual_SOC_Dataset.csv")              # 1. mở 50 tin để học
vectorizer = TfidfVectorizer()
X_train_tfidf = vectorizer.fit_transform(X_train_raw)            # 2. lập từ điển + biến 50 tin thành số (TF-IDF)
model = MultinomialNB()
model.fit(X_train_tfidf, y_train)                                # 3. Naive Bayes HỌC: từ nào đi với lừa đảo / an toà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
predictions = model.predict(X_test_tfidf)                        #    máy đoán từng tin thi
correct = sum(predictions == y_test_actual)                      #    đếm số tin đoán đúng
In [ ]:
%%bash
cd ~/lab/day1_labs
# Học từ 50 tin (Manual_SOC_Dataset.csv), thi trên 10 tin lách luật (Evasion_Dataset.csv)
python ml_classifier.py

Kết quả:

Dòng cần thấy Ý nghĩa
[+] 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
[+] 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ố
[+] 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)
5 dòng Mẫu: ... -> AI Phán đoán: Lừa đảo 🚨 5 tin thi đầu đều bị gắn lừa đảo
- Độ chính xác của AI: 10/10 (100.00%) so với 2/10 của luật cứng ở M1

👉 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.

M2-4. Máy chắc chắn đến đâu?

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:

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
# Công cụ TF-IDF (chữ -> số) và thuật toán Naive Bayes
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.naive_bayes import MultinomialNB

hoc = pd.read_csv("Manual_SOC_Dataset.csv")
thi = pd.read_csv("Evasion_Dataset.csv")
# Đặt tên ngắn cho 5 cột: mã tin, kênh, người gửi, nội dung, nhãn đúng
hoc.columns = thi.columns = ["ma", "kenh", "nguoi_gui", "noi_dung", "nhan"]

# Lập từ điển + biến 50 tin học thành số
tfidf = TfidfVectorizer()
X_hoc = tfidf.fit_transform(hoc.noi_dung)
# Nhãn -> số: 1 = lừa đảo, 0 = an toàn
y_hoc = hoc.nhan.str.contains("Lừa đảo").astype(int)
# Naive Bayes học
mo_hinh = MultinomialNB().fit(X_hoc, y_hoc)

# predict_proba = xác suất của từng tin; [:, 1] = cột xác suất "lừa đảo"
xac_suat = mo_hinh.predict_proba(tfidf.transform(thi.noi_dung))[:, 1]
for ma, tin, p in zip(thi.ma, thi.noi_dung, xac_suat):
    # Trên 50% -> kết luận lừa đảo
    ket_luan = "LỪA ĐẢO" if p >= 0.5 else "an toàn"
    print(f"{ma} | khả năng lừa đảo {p:4.0%} -> {ket_luan:7s} | {tin[:55]}")

Kết quả:

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
EVA-002 | khả năng lừa đảo  68% -> LỪA ĐẢO | Trang web www.viettel-khuyenmai.com đang có chương trì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à
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.
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
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
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
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ắ
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
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

👉 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.

M2-5. Đảo vai: học từ 10 tin lách luật, thi trên 50 tin cũ

In [ ]:
%%bash
cd ~/lab/day1_labs
# Đảo vai: HỌC từ 10 tin lách luật, THI trên 50 tin cũ
python ml_classifier.py --train Evasion_Dataset.csv --test Manual_SOC_Dataset.csv
# Đếm nhãn trong 10 tin vừa dùng để học (iloc[:, -1] = cột cuối cùng = nhãn)
python -c "import pandas as pd; print(pd.read_csv('Evasion_Dataset.csv').iloc[:, -1].rename('nhan').value_counts().to_string())"

Kết quả:

  • [+] Bước 2: AI đã đọc và tự tạo một từ điển chứa 174 từ vựng (đặc trưng).
  • 5 dòng Mẫu: — cả 5 đều Lừa đảo 🚨, kể cả tin hẹn đá bóng và tin nhắc họp
  • - Độ chính xác của AI: 30/50 (60.00%)
  • đếm nhãn: Lừa đảo (Phishing) 10

👉 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 đó.

M2-6. Tự ra đề thi có cả tin an toà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:

In [ ]:
%%bash
cd ~/lab/day1_labs
# 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)
head -1 Manual_SOC_Dataset.csv > ~/lab/my_test_M2.csv
cat >> ~/lab/my_test_M2.csv <<'EOF'
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)
T-02,Zalo,Mẹ,Con ơi tối nay về ăn cơm nhé mẹ nấu canh chua.,An toàn (Benign)
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)
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)
EOF
# Học từ 50 tin như cũ, thi trên 4 tin vừa tạo
python ml_classifier.py --test ~/lab/my_test_M2.csv

Kết quả:

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 ✅
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 🚨
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 🚨
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 🚨
- Độ chính xác của AI: 2/4 (50.00%)

👉 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ố.


Bài M2.2 — Log honeypot thật: làm sạch → biểu đồ → đặc trưng cho máy học

M2.2-1. Thông tin của một lần tấn công nằm trên nhiều dòng

In [ ]:
%%bash
# Tìm mọi dòng log có mã phiên (session) 54613b5cec67 trong ngày 22/09
grep '"54613b5cec67"' /opt/csai/data/honeypot_m1/cowrie.json.2026-09-22

Kết quả:

{"eventid": "cowrie.session.connect", "timestamp": "2026-09-22T00:00:02.779416Z", "src_ip": "109.160.32.67", "src_port": 36330, "session": "54613b5cec67"}
{"eventid": "cowrie.client.version", "timestamp": "2026-09-22T00:00:02.793738Z", "src_ip": "109.160.32.67", "session": "54613b5cec67", "version": "SSH-2.0-Go"}
{"eventid": "cowrie.login.failed", "timestamp": "2026-09-22T00:00:03.650371Z", "src_ip": "109.160.32.67", "session": "54613b5cec67", "username": "ftpuser1", "password": "123456"}

👉 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.

M2.2-2. Part A — làm sạch và ghép dữ liệu

Bước trong Lab_M2_Part_A_Clean.py Làm gì
1. Nạp đọc cả 30 file, mỗi dòng JSON thành một hàng (395.124 hàng)
Chuẩn bị ghép lập "danh bạ": mã session → tên phần mềm (từ các dòng client.version)
2. Lọc chỉ giữ các dòng kết nối — mỗi kết nối 1 hàng
Ghép tra "danh bạ" để điền tên phần mềm vào từng kết nối
3. Tách thời gian từ chuỗi giờ tách ra ngày, giờ (UTC), thứ trong tuầ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
Lưu ghi bảng sạch ra cleaned_honeypot_data.csv
In [ ]:
%%bash
cd ~/lab/day1_labs
# 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
python Lab_M2_Part_A_Clean.py

Kết quả:

Dòng cần thấy Ý nghĩa
[!] Đã tải: 395,124 events trong … giây. đọc hết 30 ngày log trong khoảng 2 giây
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
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
bảng 5 dòng timestamp, src_ip, hour, client_version hàng đầu: 217.154.13.82, giờ 0, SSH-2.0-Go
Đã lưu file 'cleaned_honeypot_data.csv' (136,063 dòng) ... bảng sạch sẵn sàng

👉 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.

M2.2-3. Part B — 5 biểu đồ, mỗi biểu đồ gợi ra một đặc trưng

In [ ]:
%%bash
cd ~/lab/day1_labs
# 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 đồ
python Lab_M2_Part_B_Visualize.py
# Xem tên 5 file ảnh vừa tạo
ls out_m2
In [ ]:
# Hiện các biểu đồ trong out_m2/ ngay dưới ô này
import glob, os
from IPython.display import Image, display
for f in sorted(glob.glob(os.path.expanduser('~/lab/day1_labs/out_m2/*.png'))):
    print(f)
    display(Image(filename=f, width=800))

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):

Biểu đồ Dòng cần thấy 👉 Hiểu Đặc trưng
— 136,063 kết nối, 2,906 IP, 30 ngày đã mở đúng bảng sạch —
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
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
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
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
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
— 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 —

("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.)

M2.2-4. Xếp đặc trưng thành bảng cho máy học

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.

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
# Mở bảng sạch của Part A (mỗi hàng = 1 kết nối)
d = pd.read_csv("cleaned_honeypot_data.csv")

# 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"
d["phan_mem"] = d.client_version.str.extract(r"^SSH-[\d.]+-([A-Za-z]+)")[0].fillna("unknown")

# 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:
bang = d.groupby("src_ip").agg(
    so_ket_noi=("src_ip", "size"),                  # tổng số kết nối
    so_ngay=("date", "nunique"),                    # xuất hiện bao nhiêu ngày khác nhau
    so_khung_gio=("hour", "nunique"),               # hoạt động trong bao nhiêu giờ khác nhau của ngày (tối đa 24)
    phan_mem=("phan_mem", lambda s: s.mode()[0]),   # phần mềm dùng nhiều nhất
)
# Trung bình mỗi ngày hoạt động kết nối bao nhiêu lần
bang["ket_noi_moi_ngay"] = (bang.so_ket_noi / bang.so_ngay).round(1)

print("Bảng đặc trưng:", len(bang), "IP x", len(bang.columns), "đặc trưng")
# 5 IP kết nối nhiều nhất
print("\n5 IP kết nối nhiều nhất:")
print(bang.sort_values("so_ket_noi", ascending=False).head(5).to_string())
# 5 IP chọn ngẫu nhiên (random_state=1: lần nào chạy cũng ra cùng 5 IP)
print("\n5 IP chọn ngẫu nhiên:")
print(bang.sample(5, random_state=1).to_string())

Kết quả:

Bảng đặc trưng: 2906 IP x 5 đặc trưng

5 IP kết nối nhiều nhất:
                so_ket_noi  so_ngay  so_khung_gio phan_mem  ket_noi_moi_ngay
src_ip
154.18.197.29         5569       12            24       Go             464.1
130.12.182.196        3757        6            24       Go             626.2
109.160.32.31         2774        3             5       Go             924.7
103.245.237.17        2350        7            24       Go             335.7
85.11.167.127         2291        4             7       Go             572.8

5 IP chọn ngẫu nhiên:
                 so_ket_noi  so_ngay  so_khung_gio phan_mem  ket_noi_moi_ngay
src_ip
197.185.168.102           1        1             1  unknown               1.0
184.58.114.52             2        1             1  unknown               2.0
87.103.105.10             1        1             1       Go               1.0
124.106.88.166            2        1             1  unknown               2.0
5.226.140.24              2        2             2  OpenSSH               1.0

👉 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.

M2.2-5. Top 5 IP chiếm bao nhiêu? Ai gây ra ngày bùng nổ?

In [ ]:
%%bash
cd ~/lab/day1_labs
# 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%)
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))"
# 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
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))"

Kết quả:

  • 0.12303859241674812 → top 5 IP ≈ 12,3% tổng số kết nối
  • 109.160.32.15 1099 · 109.160.32.19 1099 · 109.160.32.18 1099

👉 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ẻ.

M2.2-6. Tra cứu sâu trên bảng sạch

6a — IP nào đổi nhiều phần mềm nhất?

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
d = pd.read_csv("cleaned_honeypot_data.csv")
# 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"
d["phan_mem"] = d.client_version.str.extract(r"^SSH-[\d.]+-([A-Za-z]+)")[0].fillna("unknown")
# Với mỗi IP: đếm số phần mềm KHÁC NHAU nó đã dùng (bỏ qua "unknown")
so_phan_mem = d[d.phan_mem != "unknown"].groupby("src_ip").phan_mem.nunique()
print("Số IP chỉ dùng 1 phần mềm:      ", (so_phan_mem == 1).sum())
print("Số IP dùng từ 2 phần mềm trở lên:", (so_phan_mem >= 2).sum())
# 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)
print("\nTOP 5 IP đổi nhiều phần mềm nhất:")
for ip in so_phan_mem.sort_values(ascending=False).head(5).index:
    cac_pm = d[(d.src_ip == ip) & (d.phan_mem != "unknown")].phan_mem.unique()
    print(f"  {ip:16s} {len(cac_pm)} phần mềm: {', '.join(sorted(cac_pm))}")

Kết quả:

Số IP chỉ dùng 1 phần mềm:       1279
Số IP dùng từ 2 phần mềm trở lên: 67

TOP 5 IP đổi nhiều phần mềm nhất:
  185.246.128.133  8 phần mềm: Granados, JSCH, Nmap, OpenSSH, PuTTY, WinSCP, libssh, paramiko
  34.14.58.11      4 phần mềm: Fingerprintx, Go, Nmap, libssh
  34.14.104.231    4 phần mềm: Fingerprintx, Go, Nmap, libssh
  172.232.232.48   4 phần mềm: Nmap, NmapNSE, OpenSSH, libssh
  172.232.232.179  4 phần mềm: Nmap, NmapNSE, OpenSSH, libssh

👉 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.

6b — IP "không bao giờ ngủ": hoạt động đủ 24 giờ trong ngày

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
d = pd.read_csv("cleaned_honeypot_data.csv")
# Với mỗi IP: đếm số GIỜ khác nhau (0-23, giờ UTC) mà nó có kết nối
so_gio = d.groupby("src_ip").hour.nunique()
print("Số IP hoạt động ĐỦ 24 giờ trong ngày:", (so_gio == 24).sum())
print("Số IP chỉ hoạt động trong 1 giờ:     ", (so_gio == 1).sum())
# 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")
ip_24h = so_gio[so_gio == 24].index
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")

Kết quả:

Số IP hoạt động ĐỦ 24 giờ trong ngày: 80
Số IP chỉ hoạt động trong 1 giờ:      2312
-> 80 IP 'không bao giờ ngủ' tạo ra 35% tổng số kết nối

👉 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.

6c — Mổ xẻ ngày bùng nổ

In [ ]:
%cd ~/lab/day1_labs
import pandas as pd
# ===== 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 =====
NGAY_CAN_SOI = "2026-09-20"
d = pd.read_csv("cleaned_honeypot_data.csv")
# Lọc các kết nối của đúng ngày đó (cột date do Part A tạo)
mot_ngay = d[d.date == NGAY_CAN_SOI]
# Trung bình mỗi ngày = tổng kết nối / số ngày khác nhau
tb = len(d) / d.date.nunique()
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")
# 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 đó
dai = mot_ngay.src_ip.str.rsplit(".", n=1).str[0] + ".0/24"
print("TOP 3 dải mạng trong ngày:")
print(dai.rename("dai_mang").value_counts().head(3).to_string())
# Dải đứng đầu chiếm bao nhiêu phần trăm số kết nối của ngày
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")

Kết quả:

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
TOP 3 dải mạng trong ngày:
dai_mang
109.160.32.0/24     9889
103.245.237.0/24     559
195.178.110.0/24     155
-> dải đứng đầu chiếm 78% số kết nối của ngày

👉 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.