39 KiB
39 KiB
In [ ]:
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import random, string, base64, math, re
from collections import Counter
from urllib.parse import urlparse
%matplotlib inline
pd.set_option("display.width", 170); pd.set_option("display.max_colwidth", 90)
print("sẵn sàng | pandas", pd.__version__)In [ ]:
# ===== BỘ SINH URL (giữ nguyên thuật toán của bộ kit; đọc để hiểu các kiểu URL độc) =====
BRANDS = ["paypal","microsoft","apple","vietcombank","google","facebook","amazon","zalo","shopee","netflix"]
GOOD_DOMAINS = ["shop-example.com","news-daily.vn","tintuc24h.vn","bookstore.org","travel-blog.net","university.edu.vn",
"cooking-recipes.com","techreview.io","sportsnow.vn","community-forum.org"]
WORDS = ["login","secure","update","verify","account","billing","support","signin","confirm","wallet"]
TLDS = [".com",".net",".xyz",".top",".info",".click"]
def rnd(rng, n, alphabet=string.ascii_lowercase+string.digits):
return "".join(rng.choice(alphabet) for _ in range(n)) # chuỗi ngẫu nhiên n ký tự
def benign(rng): # URL thật: tên miền tốt, đường dẫn hợp lý, có thể có query dài
d = rng.choice(GOOD_DOMAINS)
path = "/".join(rng.choice(["products","category","article","2026","page","item","search"]) for _ in range(rng.randint(1,4)))
q = ""
if rng.random() < 0.6:
q = "?" + "&".join("%s=%s" % (rng.choice(["id","ref","utm_source","sort","page","color","size"]), rnd(rng, rng.randint(2,10)))
for _ in range(rng.randint(1,6)))
return "%s://www.%s/%s%s" % (rng.choice(["https","https","http"]), d, path, q)
def phishing(rng): # 4 chiêu lừa "lộ liễu"
brand, w = rng.choice(BRANDS), rng.choice(WORDS)
style = rng.randint(0,3)
if style == 0: # IP trần làm host
host = ".".join(str(rng.randint(1,254)) for _ in range(4))
return "http://%s/%s/%s.php?user=%s" % (host, w, brand, rnd(rng,6))
if style == 1: # rất nhiều subdomain
return "http://%s.%s.%s.%s%s/%s" % (w, brand, "secure-"+rnd(rng,4), rnd(rng,7), rng.choice(TLDS), w)
if style == 2: # giả chính tả: paypal -> paypa1
typo = brand.replace("o","0").replace("l","1") if rng.random() < 0.5 else brand+"-"+w
return "http://%s%s/%s-%s?session=%s" % (typo, rng.choice(TLDS), w, brand, rnd(rng,12))
return "http://%s@%s%s/%s" % (brand+".com", rnd(rng,9), rng.choice(TLDS), w) # chiêu user@host
def obfuscated(rng): # 4 chiêu "che giấu" (khó cho regex)
brand, w = rng.choice(BRANDS), rng.choice(WORDS)
style = rng.randint(0,3)
if style == 0: # rút gọn: không đọc được gì
return "https://%s/%s" % (rng.choice(["bit.ly","tinyurl.com","t.co","s.id"]), rnd(rng,7,string.ascii_letters+string.digits))
if style == 1: # mã hoá %xx
s = "/%s/%s" % (w, brand)
return "http://%s%s/%s" % (rnd(rng,8), rng.choice(TLDS), "".join("%%%02x" % ord(c) for c in s))
if style == 2: # nhúng base64 trong data URI
payload = base64.b64encode(("<form action=http://%s%s>" % (rnd(rng,6), rng.choice(TLDS))).encode()).decode()
return "data:text/html;base64," + payload
return "https://www.%s/redirect?u=%s" % (rng.choice(GOOD_DOMAINS), # trang thật + payload giấu trong tham số redirect
base64.urlsafe_b64encode(("http://%s%s/%s" % (brand, rng.choice(TLDS), w)).encode()).decode().rstrip("="))
SEED = 42 # ✏️ SỬA: đổi seed để ra bộ URL khác
rng = random.Random(SEED)
N = 5000 # ✏️ SỬA: đổi số lượng URL
n_b, n_p = int(N*0.60), int(N*0.25); n_o = N - n_b - n_p
rows = [(benign(rng),0,"benign") for _ in range(n_b)] + [(phishing(rng),1,"phishing") for _ in range(n_p)] + [(obfuscated(rng),1,"obfuscated") for _ in range(n_o)]
rng.shuffle(rows)
urls = pd.DataFrame(rows, columns=["url","label","kind"]) # 📦 urls = DataFrame 5000×3
print("📦 urls =", urls.shape, "| an toàn:", int((urls.label==0).sum()), "| độc hại:", int((urls.label==1).sum()))
print(urls.kind.value_counts().to_string())In [ ]:
for k in ["benign","phishing","obfuscated"]:
print("### %s" % k)
for u in urls[urls.kind==k].url.head(3):
print(" ", u[:110])In [ ]:
print("Ô trống trong dữ liệu:", int(urls.isna().sum().sum()), "| URL trùng:", int(urls.url.duplicated().sum()))
vd = urls.url.iloc[0]
p = urlparse(vd if "://" in vd else "http://"+vd)
print("\nVí dụ tách 1 URL:")
print(" url :", vd[:90])
print(" host :", p.hostname, "| path:", p.path[:50], "| query:", (p.query[:40] or "(trống)"))In [ ]:
IP_RE = re.compile(r"^https?://\d{1,3}(\.\d{1,3}){3}") # bắt đầu bằng http(s):// rồi 4 cụm số
def luat_regex(u):
return bool("@" in u or u.count("-") >= 3 or IP_RE.match(u) or len(u) > 75)
urls["co_chan"] = urls.url.map(luat_regex) # 📦 co_chan = Series True/False cho từng URL
doc = urls.label == 1
tp = int((urls.co_chan & doc).sum()); fp = int((urls.co_chan & ~doc).sum())
fn = int((~urls.co_chan & doc).sum())
print("Luật regex trên 5000 URL:")
print(" bắt đúng độc hại : %d / %d (recall %.1f%%)" % (tp, int(doc.sum()), 100*tp/int(doc.sum())))
print(" BỎ LỌT : %d <- kẻ tấn công đi thẳng qua" % fn)
print(" BÁO NHẦM : %d <- khách thật bị chặn (%.1f%% số URL an toàn)" % (fp, 100*fp/int((~doc).sum())))
print("\nTheo loại ẩn:")
print(urls.groupby("kind").co_chan.agg(flagged="sum", total="count").to_string())In [ ]:
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix
import time
IP_HOST = re.compile(r"^\d{1,3}(\.\d{1,3}){3}$")
SHORTENERS = {"bit.ly","tinyurl.com","t.co","s.id","goo.gl","ow.ly"}
def shannon(s): # entropy Shannon: đo độ hỗn loạn ký tự
if not s: return 0.0
c = Counter(s); return -sum((v/len(s))*math.log2(v/len(s)) for v in c.values())
def dac_trung(u):
p = urlparse(u if "://" in u or u.startswith("data:") else "http://"+u)
host = p.hostname or ""
return {"url_length": len(u), "num_special_chars": sum(u.count(ch) for ch in "@-_?="),
"entropy": round(shannon(u),4), "has_ip": int(bool(IP_HOST.match(host))),
"num_subdomains": max(0, host.count(".")-1), "is_shortener": int(host in SHORTENERS),
"has_percent_encoding": int("%" in u), "is_data_uri": int(u.startswith("data:")),
"digit_ratio": round(sum(ch.isdigit() for ch in u)/max(1,len(u)),4)}
t0 = time.perf_counter()
X = pd.DataFrame([dac_trung(u) for u in urls.url]) # 📦 X = bảng 5000×9 con số
y = urls.label.values
print("rút %d đặc trưng cho %d URL trong %.2f giây" % (X.shape[1], len(X), time.perf_counter()-t0))
X.head(3)In [ ]:
# chia 80% học / 20% kiểm tra, giữ đúng tỉ lệ độc hại (stratify)
Xtr, Xte, ytr, yte, ktr, kte = train_test_split(X, y, urls.kind.values, test_size=0.2, stratify=y, random_state=42)
rf = RandomForestClassifier(n_estimators=200, n_jobs=2, random_state=42) # ✏️ SỬA: thử n_estimators=20 hoặc 500
rf.fit(Xtr, ytr)
pred = rf.predict(Xte)
tn, fp, fn, tp = confusion_matrix(yte, pred, labels=[0,1]).ravel()
print("RANDOM FOREST trên %d URL kiểm tra:" % len(Xte))
print(" acc=%.3f precision=%.3f recall=%.3f | báo nhầm=%d bỏ lọt=%d" % (
accuracy_score(yte,pred), precision_score(yte,pred), recall_score(yte,pred), fp, fn))
for k in ["phishing","obfuscated"]:
m = kte==k; print(" %-10s bắt %d/%d" % (k, int(((pred==1)&m).sum()), int(m.sum())))In [ ]:
tqt = pd.Series(rf.feature_importances_, index=X.columns).sort_values()
ax = tqt.plot.barh(figsize=(8,4), color="tab:green"); ax.set_xlabel("độ quan trọng (tổng=1)")
ax.set_title("URL: đặc trưng nào tố cáo độc hại")
for i,v in enumerate(tqt): ax.text(v+.004, i, "%.3f"%v, va="center", fontsize=9)
plt.tight_layout(); plt.show()
print(tqt.sort_values(ascending=False).round(3).to_string())In [ ]:
def diem(u):
x = pd.DataFrame([dac_trung(u)])
return rf.predict(x)[0], rf.predict_proba(x)[0,1] # (0/1, xác suất độc hại)
# ✏️ SỬA: thử các URL của riêng anh/chị
thu = [
("trang thật + thêm nhiều subdomain", "https://www.a.b.c.d.shop-example.com/products"),
("giả trang ngân hàng, trông hợp lý", "https://login.vietcombank.com.vn/"),
("phishing IP trần (giống dữ liệu học)", "http://185.23.11.9/login/paypal.php?user=abc123"),
]
for ten, u in thu:
nhan, xs = diem(u)
print("%-38s -> %-8s (xác suất độc %.2f) | %s" % (ten, "ĐỘC HẠI" if nhan else "AN TOÀN", xs, u[:55]))