Repository navigation
Expand file tree
/
Copy pathpdf_phishing_analyzer.py
More file actions
202 lines (164 loc) · 6.65 KB
/
Copy pathpdf_phishing_analyzer.py
File metadata and controls
202 lines (164 loc) · 6.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
PDF Phishing Email Analyzer
Extracts text and URLs from an email exported as a PDF and analyzes them
for phishing indicators using the URL Threat Analyzer.
Usage:
python pdf_phishing_analyzer.py -f sample_email.pdf
"""
import argparse
import logging
import re
import sys
from pathlib import Path
from urllib.parse import urlparse
try:
# pyrefly: ignore [missing-import]
import fitz # PyMuPDF
except ImportError:
print("[ERROR] PyMuPDF is required. Please install it using: pip install PyMuPDF")
sys.exit(1)
# Import the existing URL threat analyzer
try:
from url_threat_analyzer import URLThreatAnalyzer
except ImportError:
print("[ERROR] Could not import url_threat_analyzer.py. Make sure it's in the same directory.")
sys.exit(1)
# ---------------------------------------------------------------------------
# CONFIGURATION
# ---------------------------------------------------------------------------
# Basic regex for finding plain text URLs
URL_REGEX = re.compile(
r'(?:http|https)://(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)',
re.IGNORECASE
)
# Phishing keywords to search for in the email text
PHISHING_TEXT_KEYWORDS = [
"urgent", "login", "verify", "account", "secure", "update",
"confirm", "banking", "paypal", "amazon", "microsoft", "apple",
"google", "facebook", "netflix", "support", "helpdesk",
"password", "credential", "wallet", "crypto", "free",
"action required", "suspended", "unauthorized"
]
# ---------------------------------------------------------------------------
# LOGGING
# ---------------------------------------------------------------------------
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
log = logging.getLogger("pdf_phishing_analyzer")
# ---------------------------------------------------------------------------
# ANALYSIS FUNCTIONS
# ---------------------------------------------------------------------------
def extract_pdf_content(pdf_path: str) -> tuple[str, list[str]]:
"""
Extracts text and all URLs (from text and annotations) from a PDF.
Returns (full_text, list_of_urls).
"""
full_text = []
urls = set()
try:
doc = fitz.open(pdf_path)
except Exception as e:
log.error(f"Failed to open PDF {pdf_path}: {e}")
return "", []
for page_num in range(len(doc)):
page = doc[page_num]
# 1. Extract text
text = page.get_text()
full_text.append(text)
# Find plain text URLs via regex
found_text_urls = URL_REGEX.findall(text)
for u in found_text_urls:
urls.add(u)
# 2. Extract embedded hyperlink annotations
links = page.get_links()
for link in links:
uri = link.get("uri")
if uri:
# Sometimes URI might be mailto: or other non-http schemes.
if uri.lower().startswith(("http://", "https://")):
urls.add(uri)
doc.close()
return "\n".join(full_text), list(urls)
def check_phishing_keywords(text: str) -> tuple[bool, list[str]]:
"""
Checks the text for common phishing keywords.
Returns (is_suspicious, list_of_found_keywords).
"""
text_lower = text.lower()
found = []
for kw in PHISHING_TEXT_KEYWORDS:
if kw in text_lower:
found.append(kw)
# Simple threshold: if 3 or more distinct keywords are found, it's suspicious
return len(found) >= 3, found
def print_report(pdf_path: str, found_keywords: list[str], threat_results: list) -> None:
sep = "=" * 70
print(f"\n{sep}")
print(" PDF PHISHING EMAIL ANALYSIS REPORT")
print(sep)
print(f" Target File : {pdf_path}")
# Determine overall verdict
malicious_urls = [r for r in threat_results if r.verdict == "MALICIOUS"]
suspicious_urls = [r for r in threat_results if r.verdict == "SUSPICIOUS"]
is_phishing = len(malicious_urls) > 0 or len(suspicious_urls) > 0
text_suspicious = len(found_keywords) >= 3
if is_phishing:
overall = "PHISHING ATTEMPT"
icon = "[!]"
elif text_suspicious:
overall = "SUSPICIOUS EMAIL"
icon = "[?]"
else:
overall = "CLEAN"
icon = "[+]"
print(f" Verdict : {icon} {overall}")
print(f" URLs Found : {len(threat_results)}")
print(f"\n -- Text Analysis --")
if found_keywords:
print(f" Found {len(found_keywords)} potential phishing keywords: {', '.join(found_keywords)}")
if text_suspicious:
print(" [!] Text indicates high urgency or credential harvesting.")
else:
print(" [+] No obvious phishing keywords detected in text.")
print(f"\n -- URL Analysis --")
if not threat_results:
print(" No URLs found in the PDF.")
else:
for r in threat_results:
u_icon = {"MALICIOUS": "[!]", "SUSPICIOUS": "[?]", "CLEAN": "[+]"}.get(r.verdict, "[-]")
print(f" {u_icon} [{r.verdict}] {r.url}")
print(f" Risk: {r.risk_score}/100")
if r.indicators:
print(f" Indicators: {', '.join(r.indicators)}")
print(f"{sep}\n")
# ---------------------------------------------------------------------------
# MAIN
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Analyze an email PDF for phishing indicators.")
parser.add_argument("-f", "--file", required=True, help="Path to the PDF file to analyze")
parser.add_argument("--workers", type=int, default=1, help="Parallel workers for URL analysis")
args = parser.parse_args()
pdf_path = args.file
if not Path(pdf_path).exists():
log.error(f"File not found: {pdf_path}")
sys.exit(1)
log.info(f"Analyzing PDF: {pdf_path}")
text, urls = extract_pdf_content(pdf_path)
log.info(f"Extracted {len(text)} characters of text and {len(urls)} URLs.")
text_suspicious, found_keywords = check_phishing_keywords(text)
threat_results = []
if urls:
log.info("Starting URL threat analysis...")
# Note: We will disable URLScan/VT API by default if keys aren't set in config.env,
# URLThreatAnalyzer handles this gracefully and falls back to heuristics.
analyzer = URLThreatAnalyzer()
threat_results = analyzer.analyze_bulk(urls, max_workers=args.workers)
print_report(pdf_path, found_keywords, threat_results)
if __name__ == "__main__":
main()