Detecție phishing cu RAG local – reducere fals-pozitive prin bază de cunoștințe cu campanii cunoscute

Configurare noua (How To)

Situatie

Detecția de phishing cu un LLM simplu (prompt direct către model) funcționează, dar generează fals-pozitive: emailuri legitime cu ton urgent sunt marcate greșit ca phishing, iar modelul nu are context despre campaniile specifice care vizează organizația.

Această soluție adaugă un strat RAG peste detecția existentă: o bază de cunoștințe locală cu exemple de phishing confirmate, emailuri legitime cunoscute (newsletter-e interne, notificări de sistem) și politici interne. Modelul compară emailul suspect cu exemplele similare înainte de a da verdictul — rezultat: mai puține alarme false și motivare explicită bazată pe cazuri concrete.

Backup

Baza de cunoștințe este separată de emailurile analizate. Fă backup înainte de re-indexare:

powershell
Copy-Item "C:\PhishRAG\db" "C:\PhishRAG\db_backup_$(Get-Date -Format 'yyyy-MM-dd')" -Recurse

Solutie

Pasi de urmat

Pasul 1 — Pregătire mediu și structură de foldere

powershell
pip install chromadb ollama colorama

# Structura de lucru
New-Item -ItemType Directory -Force -Path "C:\PhishRAG\KB\phishing_confirmat"
New-Item -ItemType Directory -Force -Path "C:\PhishRAG\KB\legitim_confirmat"
New-Item -ItemType Directory -Force -Path "C:\PhishRAG\Inbox"
New-Item -ItemType Directory -Force -Path "C:\PhishRAG\Reports"
New-Item -ItemType Directory -Force -Path "C:\PhishRAG\db"

Pasul 2 — Populare bază de cunoștințe

Salvează exemple în cele două foldere, ca fișiere .txt:

powershell
# Exemplu: phishing confirmat anterior
@"
Expeditor: hr-notificari@companie-portal.net
Subiect: Actualizare urgenta date salariale
Continut: Va rugam accesati linkul pentru a confirma datele bancare
inainte de procesarea salariului. Link: http://companie-portal.net/hr-login
INDICATORI: domeniu extern care imita numele companiei, cerere date bancare,
urgenta artificiala legata de salariu
"@ | Out-File "C:\PhishRAG\KB\phishing_confirmat\campanie_hr_salarii.txt" -Encoding UTF8

# Exemplu: email legitim recurent (pentru a evita fals-pozitive)
@"
Expeditor: no-reply@companie.ro
Subiect: Notificare expirare parola in 7 zile
Continut: Parola contului tau de domeniu expira in 7 zile. Schimba parola
folosind Ctrl+Alt+Del > Change Password sau portalul intern.
CARACTERISTICI: domeniu intern verificat, NU cere click pe link extern,
instructiuni catre metode native Windows
"@ | Out-File "C:\PhishRAG\KB\legitim_confirmat\notificare_parola.txt" -Encoding UTF8

Pasul 3 — Script de indexare bază de cunoștințe

Salvează ca kb_indexer.py:

python
# ============================================================
# Phishing KB Indexer - indexare exemple confirmate
# Autor: Claudiu Ceausescu
# ============================================================

import os
import chromadb
import ollama
from colorama import Fore, init

init(autoreset=True)

KB_PATH     = r"C:\PhishRAG\KB"
DB_PATH     = r"C:\PhishRAG\db"
EMBED_MODEL = "nomic-embed-text"


def get_embedding(text):
    return ollama.embeddings(model=EMBED_MODEL, prompt=text)["embedding"]


def main():
    client     = chromadb.PersistentClient(path=DB_PATH)
    collection = client.get_or_create_collection("phishing_kb")

    total = 0
    for label in ["phishing_confirmat", "legitim_confirmat"]:
        folder = os.path.join(KB_PATH, label)
        if not os.path.exists(folder):
            continue

        files = [f for f in os.listdir(folder) if f.endswith(".txt")]
        print(Fore.CYAN + f"\nIndexez categoria: {label} ({len(files)} fisiere)")

        for filename in files:
            filepath = os.path.join(folder, filename)
            with open(filepath, "r", encoding="utf-8") as f:
                content = f.read()

            if len(content.strip()) < 20:
                continue

            embedding = get_embedding(content)
            collection.upsert(
                ids=[f"{label}_{filename}"],
                embeddings=[embedding],
                documents=[content],
                metadatas=[{"label": label, "source": filename}]
            )
            print(Fore.GREEN + f"   -> {filename}")
            total += 1

    print(Fore.GREEN + f"\n[DONE] Total exemple indexate: {total}")
    print(Fore.GREEN + f"Total in colectie: {collection.count()}")


if __name__ == "__main__":
    main()

Pasul 4 — Script de analiză cu RAG

Salvează ca phish_rag_check.py:

python
# ============================================================
# Phishing RAG Detector - analiza cu context din KB
# Autor: Claudiu Ceausescu
# ============================================================

import os
import re
import chromadb
import ollama
from datetime import datetime
from colorama import Fore, init

init(autoreset=True)

DB_PATH      = r"C:\PhishRAG\db"
INBOX_PATH   = r"C:\PhishRAG\Inbox"
REPORTS_PATH = r"C:\PhishRAG\Reports"
EMBED_MODEL  = "nomic-embed-text"
GEN_MODEL    = "llama3"
TOP_K        = 3


def get_embedding(text):
    return ollama.embeddings(model=EMBED_MODEL, prompt=text)["embedding"]


def parse_eml(path):
    with open(path, "r", encoding="utf-8", errors="ignore") as f:
        content = f.read()

    def find(pattern):
        m = re.search(pattern, content, re.MULTILINE | re.IGNORECASE)
        return m.group(1).strip() if m else "N/A"

    sender  = find(r"^From:\s*(.+)$")
    subject = find(r"^Subject:\s*(.+)$")

    # Corpul mesajului: dupa prima linie goala
    parts = content.split("\n\n", 1)
    body  = parts[1][:1500] if len(parts) > 1 else content[:1500]

    return {"from": sender, "subject": subject, "body": body}


def find_similar_examples(email, collection):
    query_text = f"{email['from']} {email['subject']} {email['body']}"
    embedding  = get_embedding(query_text)

    results = collection.query(
        query_embeddings=[embedding],
        n_results=TOP_K,
        include=["documents", "metadatas"]
    )
    return results["documents"][0], results["metadatas"][0]


def analyze_with_rag(email, examples, metadatas):
    context = ""
    for doc, meta in zip(examples, metadatas):
        context += f"\n[EXEMPLU {meta['label'].upper()}]\n{doc}\n"

    prompt = f"""Esti analist de securitate. Analizeaza emailul suspect comparandu-l
cu exemplele confirmate din baza de cunostinte a organizatiei.

Raspunde STRICT in formatul:
VERDICT: <Legitim / Suspect / Phishing>
SCOR_RISC: <0-100>
EXEMPLU_SIMILAR: <la ce exemplu din baza seamana cel mai mult si de ce>
INDICATORI: <indicatorii concreti detectati>
RECOMANDARE: <ce ar trebui sa faca utilizatorul>

IMPORTANT: daca emailul seamana cu un exemplu LEGITIM_CONFIRMAT, nu il marca
drept phishing doar pentru ton urgent. Raspunde DOAR in limba romana.

BAZA DE CUNOSTINTE (exemple similare):
{context}

EMAIL DE ANALIZAT:
Expeditor: {email['from']}
Subiect: {email['subject']}
Continut: {email['body']}
"""
    response = ollama.chat(model=GEN_MODEL, messages=[{"role": "user", "content": prompt}])
    return response["message"]["content"]


def main():
    client     = chromadb.PersistentClient(path=DB_PATH)
    collection = client.get_or_create_collection("phishing_kb")

    if collection.count() == 0:
        print(Fore.RED + "Baza de cunostinte este goala. Ruleaza mai intai kb_indexer.py")
        return

    eml_files = [f for f in os.listdir(INBOX_PATH) if f.endswith(".eml")]
    if not eml_files:
        print(Fore.YELLOW + f"Nu exista fisiere .eml in {INBOX_PATH}")
        return

    timestamp   = datetime.now().strftime("%Y-%m-%d_%H-%M")
    report_file = os.path.join(REPORTS_PATH, f"Raport_PhishRAG_{timestamp}.txt")
    report      = [f"RAPORT DETECTIE PHISHING (RAG) - {datetime.now():%d.%m.%Y %H:%M}", "=" * 60, ""]

    for i, filename in enumerate(eml_files, 1):
        print(Fore.CYAN + f"[{i}/{len(eml_files)}] Analizez: {filename}")

        email = parse_eml(os.path.join(INBOX_PATH, filename))
        examples, metadatas = find_similar_examples(email, collection)
        analysis = analyze_with_rag(email, examples, metadatas)

        report += ["-" * 50, f"FISIER: {filename}",
                   f"EXPEDITOR: {email['from']}",
                   f"SUBIECT: {email['subject']}", "", analysis, ""]

    report += ["=" * 60, f"ANALIZA FINALIZATA - Total: {len(eml_files)} emailuri"]

    with open(report_file, "w", encoding="utf-8") as f:
        f.write("\n".join(report))

    print(Fore.GREEN + f"\n[DONE] Raport: {report_file}")


if __name__ == "__main__":
    main()

Pasul 5 — Rulare

powershell
# 1. Indexare baza de cunostinte (o data, apoi la fiecare exemplu nou adaugat)
python kb_indexer.py

# 2. Copiaza emailuri suspecte in Inbox si analizeaza
python phish_rag_check.py

Exemplu output:

--------------------------------------------------
FISIER: notificare_parola_suspecta.eml
EXPEDITOR: it-support@companie.ro

VERDICT: Legitim
SCOR_RISC: 15
EXEMPLU_SIMILAR: Seamana cu "notificare_parola.txt" (legitim confirmat) -
domeniu intern, instructiuni catre metode native Windows, fara link extern
INDICATORI: ton usor urgent, dar consistent cu notificarile interne standard
RECOMANDARE: Email legitim. Schimba parola prin Ctrl+Alt+Del.

Pasul 6 — Îmbunătățire continuă a bazei de cunoștințe

powershell
# Dupa fiecare caz confirmat manual, adauga-l in KB si re-indexeaza
# Astfel acuratetea creste in timp, adaptata la campaniile specifice organizatiei
python kb_indexer.py

Tip solutie

Permanent

Impact colateral

Acuratețea depinde direct de calitatea și volumul exemplelor din baza de cunoștințe — cu 2-3 exemple rezultatele sunt slabe, cu 20-30 devin utile. Adaugă exemple treptat, după fiecare caz confirmat. Verdictul rămâne orientativ: un analist trebuie să confirme cazurile critice, iar un email de phishing complet nou (campanie necunoscută) poate scăpa dacă nu seamănă cu nimic din bază. Exemplele stocate în KB pot conține date sensibile (nume, adrese interne) — folderul trebuie protejat cu permisiuni NTFS restrictive.

Plan de restaurare in caz de nefunctionare

powershell
# Sterge baza vectoriala (exemplele .txt din KB ramin intacte)
Remove-Item "C:\PhishRAG\db" -Recurse -Force

# Restaurare din backup
Copy-Item "C:\PhishRAG\db_backup_2026-09-07" "C:\PhishRAG\db" -Recurse

Voteaza

(33 din 61 persoane apreciaza acest articol)

Despre Autor

Leave A Comment?