- Posted on
- • Artificial Intelligence
Artificial Intelligence Phishing Detection
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI-Powered Phishing Detection From Your Linux Terminal
If you’ve ever tried to “grep” your way out of a phishing storm, you know the feeling: attackers iterate faster than your handcrafted rules. Links hide behind URL shorteners, look-alike domains use punycode tricks, and attachments pack payloads into nested archives. In this post, we’ll build a practical, AI-assisted phishing detection pipeline you can run from Bash, combining open threat feeds, lightweight machine intelligence, and proven security tools.
What you’ll get:
A Bash-first workflow to score URLs and emails for phishing risk
A small Python helper that applies ML-style features and outputs reasons
Integration with threat feeds, DKIM/DMARC verification, and malware scanning
Copy-pasteable install and run steps for apt, dnf, and zypper
Why it’s worth your time:
Phishing adapts too quickly for static rules alone
AI-style features catch signals like obfuscation, domain tricks, and risky URL patterns
Running locally keeps sensitive content inside your perimeter
The pipeline is modular—swap, extend, or automate as you like
Prerequisites: Install the CLI and libraries
We’ll use curl, jq, ripgrep, YARA, ClamAV, OpenDKIM, OpenDMARC, Python 3, and a virtualenv for a tiny Python helper.
On Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq ripgrep yara clamav opendkim-tools opendmarc mpack python3 python3-pip python3-venv git
On Fedora/RHEL/CentOS Stream (dnf):
sudo dnf install -y curl jq ripgrep yara clamav opendkim-tools opendmarc mpack python3 python3-pip python3-virtualenv git
On openSUSE/SLE (zypper):
sudo zypper install -y curl jq ripgrep yara clamav opendkim-tools opendmarc mpack python3 python3-pip python3-virtualenv git
Initialize ClamAV signatures (optional but recommended):
sudo freshclam
Create a project folder and Python virtual environment:
mkdir -p ~/phish-lab/{bin,data,work}
cd ~/phish-lab
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install tldextract idna
Note: If your distro requires a CA/SSL bundle for Python, ensure it’s installed (e.g., ca-certificates).
Step 1: Pull community phishing feeds and normalize
We’ll aggregate URLs from OpenPhish and URLHaus into a single local blocklist.
Create bin/fetch_feeds.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT="${ROOT:-$HOME/phish-lab}"
DATA="$ROOT/data"
TMP="$ROOT/work"
mkdir -p "$DATA" "$TMP"
# Feeds: community OpenPhish and URLHaus recent URLs
OPENPHISH_URL="https://openphish.com/feed.txt"
URLHAUS_CSV="https://urlhaus.abuse.ch/downloads/urlhaus_recent.csv"
curl -fsSL "$OPENPHISH_URL" -o "$TMP/openphish.txt"
curl -fsSL "$URLHAUS_CSV" -o "$TMP/urlhaus.csv"
# Extract URLs from URLHaus CSV (skip comments and header)
awk -F, 'BEGIN{OFS=","} !/^#/ && NR>1 {print $2}' "$TMP/urlhaus.csv" \
| sed 's/^"//;s/"$//' > "$TMP/urlhaus.txt"
# Normalize, de-dupe, and keep http(s) only
cat "$TMP/openphish.txt" "$TMP/urlhaus.txt" \
| grep -Eoi 'https?://[^ ]+' \
| sed 's/[[:space:]]//g' \
| sort -u > "$DATA/blocklist_urls.txt"
echo "Wrote $(wc -l < "$DATA/blocklist_urls.txt") blocklisted URLs to $DATA/blocklist_urls.txt"
Make it executable and run:
chmod +x bin/fetch_feeds.sh
bin/fetch_feeds.sh
Pro tip: Schedule regular refresh with cron or a systemd timer.
Step 2: Add a lightweight AI URL scorer
We’ll create a small Python tool that:
Extracts ML-style features from each URL (length, punycode, IP literals, entropy hints, etc.)
Applies a logistic-style scoring to estimate phishing probability
Explains the score with human-readable reasons
Optionally short-circuits to “known-bad” if the URL is on our blocklist
Create bin/url_ai.py:
#!/usr/bin/env python3
import sys, re, json, math, os
from urllib.parse import urlparse, parse_qs
import idna
try:
import tldextract
except ImportError:
print("Missing dependency: tldextract. Activate venv and run: pip install tldextract idna", file=sys.stderr)
sys.exit(2)
ROOT = os.environ.get("ROOT", os.path.expanduser("~/phish-lab"))
BLOCKLIST = os.path.join(ROOT, "data", "blocklist_urls.txt")
blocklisted = set()
if os.path.exists(BLOCKLIST):
with open(BLOCKLIST, "r", encoding="utf-8", errors="ignore") as f:
blocklisted = {line.strip() for line in f if line.strip()}
ip_regex = re.compile(r'^(?:\d{1,3}\.){3}\d{1,3}$')
ipv6_regex = re.compile(r'^\[?[0-9a-f:]+\]?$',
re.IGNORECASE)
suspicious_tlds = {
"zip","mov","click","link","top","xyz","gq","tk","ml","cf","work","fit",
"cam","quest","country","kim","men","loan","download","review","accountants"
}
suspicious_keywords = ["login","verify","update","secure","confirm","password","invoice","webscr","support","helpdesk"]
def is_ip_literal(host):
if not host: return False
host = host.strip("[]")
return bool(ip_regex.match(host) or ipv6_regex.match(host))
def pct_ratio(url):
total = max(len(url), 1)
return url.count('%') / total
def extract_features(u):
reasons = []
try:
p = urlparse(u)
except Exception:
return None, ["unparseable"], 0.99
scheme = p.scheme.lower()
netloc = p.netloc.lower()
host = netloc.split('@')[-1].split(':')[0]
path = p.path or ""
query = p.query or ""
frag = p.fragment or ""
full = u.strip()
ext = tldextract.extract(u)
domain = ".".join([x for x in [ext.domain, ext.suffix] if x])
subdomain = ext.subdomain or ""
features = {}
features["len_url"] = len(full)
features["has_at"] = 1 if "@" in netloc or "@" in path else 0
features["has_ip"] = 1 if is_ip_literal(host) else 0
features["dots_host"] = host.count(".")
features["hyphens_host"] = host.count("-")
features["digits_host"] = sum(ch.isdigit() for ch in host)
features["pct_ratio"] = pct_ratio(full)
features["has_punycode"] = 1 if "xn--" in host else 0
features["path_len"] = len(path)
features["query_len"] = len(query)
features["num_params"] = len(parse_qs(query))
features["frag_len"] = len(frag)
tld = ext.suffix.lower() if ext.suffix else ""
features["sus_tld"] = 1 if tld in suspicious_tlds else 0
kw = sum(1 for k in suspicious_keywords if k in full.lower())
features["kw_hits"] = kw
features["sub_len"] = len(subdomain)
# Reasons
if features["has_ip"]: reasons.append("URL uses raw IP")
if features["has_punycode"]: reasons.append("Punycode in hostname")
if features["sus_tld"]: reasons.append(f"Suspicious TLD: .{tld}")
if features["kw_hits"] > 0: reasons.append("Sensitive keywords in URL")
if features["pct_ratio"] > 0.03: reasons.append("High percent-encoding")
if features["has_at"]: reasons.append("@ present in URL")
if features["dots_host"] >= 4: reasons.append("Many dots in hostname")
if features["hyphens_host"] >= 3: reasons.append("Many hyphens in hostname")
if features["digits_host"] >= 4: reasons.append("Many digits in hostname")
if features["path_len"] > 60: reasons.append("Long path")
if features["num_params"] >= 5: reasons.append("Many query parameters")
return features, reasons, None
# Heuristic weights approximating a logistic regressor
WEIGHTS = {
"bias": -3.0,
"len_url": 0.003, # long URLs are riskier
"has_at": 1.5,
"has_ip": 1.8,
"dots_host": 0.35,
"hyphens_host": 0.25,
"digits_host": 0.18,
"pct_ratio": 40.0, # percent-encoding spikes are strong signals
"has_punycode": 1.6,
"path_len": 0.01,
"query_len": 0.01,
"num_params": 0.3,
"frag_len": 0.006,
"sus_tld": 1.4,
"kw_hits": 0.9,
"sub_len": 0.02
}
def sigmoid(x):
return 1 / (1 + math.exp(-x))
def score(features):
z = WEIGHTS["bias"]
for k, w in WEIGHTS.items():
if k == "bias": continue
z += w * features.get(k, 0)
return sigmoid(z)
def main():
urls = []
if len(sys.argv) > 1 and sys.argv[1] != "-":
with open(sys.argv[1], "r", encoding="utf-8", errors="ignore") as f:
urls = [l.strip() for l in f if l.strip()]
else:
urls = [l.strip() for l in sys.stdin if l.strip()]
for u in urls:
label = "unknown"
# Feed short-circuit
if u in blocklisted:
out = {"url": u, "score": 99.0, "label": "phishy", "reasons": ["Listed in threat feed"]}
print(json.dumps(out, ensure_ascii=False))
continue
feats, reasons, parse_err_prob = extract_features(u)
if feats is None:
out = {"url": u, "score": 95.0 if parse_err_prob else 50.0, "label": "phishy", "reasons": reasons}
print(json.dumps(out, ensure_ascii=False))
continue
p = score(feats)
pct = round(p * 100, 2)
if pct >= 60:
label = "phishy"
elif pct <= 30:
label = "likely-benign"
else:
label = "uncertain"
out = {"url": u, "score": pct, "label": label, "reasons": reasons}
print(json.dumps(out, ensure_ascii=False))
if __name__ == "__main__":
main()
Make it executable:
chmod +x bin/url_ai.py
Test with a couple of URLs:
printf "%s\n" \
"http://xn--paypol-6ve.com/security/verify" \
"https://accounts.google.com/signin" \
"http://198.51.100.23/login?user=you" \
| bin/url_ai.py | jq .
Step 3: A Bash-first email/URL scanner that ties it together
We’ll build a wrapper that:
Accepts either a raw .eml file or a list of URLs
Extracts URLs from the message
Checks feeds and AI score
Optionally validates DKIM/DMARC and scans attachments with ClamAV/YARA
Create bin/phish_scan.sh:
#!/usr/bin/env bash
set -euo pipefail
ROOT="${ROOT:-$HOME/phish-lab}"
BIN="$ROOT/bin"
DATA="$ROOT/data"
WORK="$ROOT/work"
mkdir -p "$WORK"
usage() {
echo "Usage:"
echo " $0 --eml message.eml"
echo " $0 --urls urls.txt"
exit 1
}
mode=""
input=""
while [[ $# -gt 0 ]]; do
case "$1" in
--eml) mode="eml"; input="$2"; shift 2;;
--urls) mode="urls"; input="$2"; shift 2;;
*) usage;;
esac
done
[[ -z "$mode" || -z "$input" ]] && usage
extract_urls() {
# Basic URL regex; for better extraction consider 'urlextract' in Python
grep -Eo 'https?://[^)"'\'"[:space:]]+' "$1" \
| sed 's/[>),.;:]*$//' \
| sort -u
}
scan_urls() {
local urlfile="$1"
echo "== URL Scoring =="
"$BIN/url_ai.py" "$urlfile" \
| jq -r '. | "\(.label)\t\(.score)\t\(.url)\t\(.reasons|join("; "))"'
}
if [[ "$mode" == "urls" ]]; then
tmpu="$WORK/urls.$$.txt"
sort -u "$input" > "$tmpu"
scan_urls "$tmpu"
rm -f "$tmpu"
exit 0
fi
# Mode: EML
EML="$input"
if [[ ! -f "$EML" ]]; then
echo "Missing file: $EML" >&2; exit 2
fi
echo "== Extracting URLs from $(basename "$EML") =="
tmpurls="$WORK/extracted.$$.txt"
extract_urls "$EML" > "$tmpurls" || true
wc -l "$tmpurls"
if command -v opendkim-testmsg >/dev/null 2>&1; then
echo "== DKIM verification =="
# Will exit non-zero on failure; capture status and continue
if opendkim-testmsg -d < "$EML" >/dev/null 2>&1; then
echo "DKIM: pass"
else
echo "DKIM: fail or none"
fi
fi
if command -v opendmarc >/dev/null 2>&1 || command -v opendmarc-test >/dev/null 2>&1; then
echo "== DMARC (basic) =="
if command -v opendmarc-test >/dev/null 2>&1; then
if opendmarc-test < "$EML" >/dev/null 2>&1; then
echo "DMARC: pass"
else
echo "DMARC: fail or none"
fi
else
echo "DMARC: tool not available (install opendmarc for deeper checks)"
fi
fi
# Optionally unpack and scan attachments
ATT_DIR="$WORK/att.$$.d"
mkdir -p "$ATT_DIR"
if command -v munpack >/dev/null 2>&1; then
echo "== Extracting attachments =="
(cd "$ATT_DIR" && munpack -q ../"$EML" >/dev/null 2>&1 || true)
if compgen -G "$ATT_DIR/*" > /dev/null; then
echo "Attachments extracted to $ATT_DIR"
if command -v clamscan >/dev/null 2>&1; then
echo "== ClamAV scan =="
clamscan -ri "$ATT_DIR" || true
fi
if command -v yara >/dev/null 2>&1; then
echo "== YARA quick scan =="
# Quick heuristic: detect base64-heavy JS/VBS
cat > "$WORK/simple_phish_rules.yar" <<'YAR'
rule Suspicious_Encoded_Script {
strings:
$a = /[A-Za-z0-9+\/]{200,}={0,2}/
$b = "FromCharCode" nocase
$c = "WScript.Shell" nocase
condition:
(uint16(0) == 0x5a4d or filesize < 2MB) and 1 of ($a,$b,$c)
}
YAR
yara -r "$WORK/simple_phish_rules.yar" "$ATT_DIR" || true
fi
else
echo "No attachments extracted (or not MIME-encoded)."
fi
else
echo "munpack not found; skip attachment extraction (install mpack)"
fi
if [[ -s "$tmpurls" ]]; then
scan_urls "$tmpurls"
else
echo "No URLs found in message."
fi
rm -f "$tmpurls"
Make it executable:
chmod +x bin/phish_scan.sh
Try it on a saved email:
bin/phish_scan.sh --eml suspicious.eml
Or on a URL list:
printf "%s\n" "https://secure-update.example.top/login" "https://example.com/" > /tmp/u.txt
bin/phish_scan.sh --urls /tmp/u.txt
Step 4: Improve results with 3–5 practical tactics
1) Combine feeds with AI scoring
Feeds catch known-bad quickly; AI-style scoring flags look-alikes and zero-day variants.
Keep feeds fresh:
bin/fetch_feeds.shPut it in cron (e.g., run hourly).
2) Seed allowlists and cut false positives
If you have a known-good domain list (your company, vendors), prefer them.
Add a quick allowlist check to bin/url_ai.py (before scoring), or filter in Bash:
grep -v -F -f data/allow_domains.txt urls.txt | bin/url_ai.py
3) Strengthen feature extraction
Expand suspicious_tlds and suspicious_keywords in url_ai.py with what you see in your telemetry.
Add features: homograph detection, Levenshtein distance to known brands, URL shortening domains expansion (resolving HEAD Location with curl -I).
Example to unshorten:
curl -sIL https://bit.ly/xyz | awk -F': ' '/^location:/ {print $2}' | tail -n1
4) Verify email authenticity signals
DKIM and DMARC checking helps demote obviously spoofed messages.
If you run Postfix/Exim, integrate OpenDKIM/OpenDMARC milters server-wide; for offline checks, opendkim-testmsg and opendmarc-test help.
5) Scan attachments proactively
clamscan catches many known malware families in attachments.
YARA fills the gap with custom rules tailored to your environment.
Keep rules small and targeted to reduce noise; start with the high-signal patterns you encounter.
Real-world example pattern: A message with “DocuSign” theme using a .zip attachment and a link to hxxps://docusign-secure-update.xyz/s/doc.html will usually:
Fail DMARC or arrive with no DKIM
Score high due to suspicious TLD, punycode/IP literals, long encoded URLs
Trigger YARA if the extracted HTML/JS is heavily base64-encoded
Step 5: Automate and integrate
Run as a procmail or sieve filter: pipe inbound emails through bin/phish_scan.sh, then add headers like X-Phish-Score based on the JSON labels/scores.
Use exit codes: treat “phishy” over 80 as quarantine, 60–80 as review, under 30 as pass with logging.
Log to JSONL for SIEM ingestion:
bin/url_ai.py urls.txt >> logs/url_scores.jsonl
Conclusion and Next Steps (CTA)
You now have a Bash-first, AI-assisted phishing detector that:
Ingests live threat intelligence
Scores URLs with explainable, ML-style features
Verifies DKIM/DMARC and scans attachments
Works entirely on your Linux box with familiar CLI tools
Where to go from here:
Expand features and tuning in bin/url_ai.py to match your environment
Integrate with your MTA or ticketing system for automated triage
Add a simple web or TUI front end for analysts
Build a small labeled corpus of your own “ham vs phish” and replace heuristic weights with a trained model
If you found this useful, wire it into cron today, then iterate weekly based on what your telemetry shows. The best phishing defense is both adaptive and automated—start simple, ship it, and keep improving.