npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
NIST CSF 2.0
MITRE ATLAS
Overview
QR code phishing (quishing) is a rapidly growing attack vector where malicious URLs are embedded in QR code images within phishing emails. Quishing incidents grew fivefold from 46,000 to 250,000 between August and November 2025, with credential phishing comprising 89.3% of detected incidents. Traditional email security filters struggle because QR codes cannot be read by humans or standard URL scanners, and when scanned, users typically use personal mobile devices that lack corporate security controls. Attackers have evolved to use split QR codes (two separate images), nested QR codes, and ASCII text-based QR codes to evade detection.
When to Use
- When investigating security incidents that require detecting qr code phishing with email security
- When building detection rules or threat hunting queries for this domain
- When SOC analysts need structured procedures for this analysis type
- When validating security monitoring coverage for related attack techniques
Prerequisites
- Email security gateway with image analysis capabilities
- Understanding of QR code structure and encoding
- Mobile device management (MDM) or mobile threat defense solution
- Security awareness training program
- SIEM platform for correlation and alerting
Key Concepts
Why Quishing Works
- Bypasses URL Scanners: Traditional gateways scan text-based URLs but cannot decode image-embedded URLs
- Shifts to Unprotected Devices: Corporate email arrives on secured systems but QR scan occurs on personal mobile devices
- User Trust: QR codes are normalized in daily life (payments, menus, parking)
- Low Detection Rate: Only 36% of quishing incidents are accurately identified by recipients
Evasion Techniques (2025)
- Split QR Codes: QR code divided into two separate images that look benign individually (Gabagool PhaaS kit)
- Nested QR Codes: QR code within a QR code, with first scan leading to intermediate page
- ASCII QR Codes: QR rendered as text characters instead of images, bypassing image analysis (12% of attacks in Jan 2026)
- Styled/Artistic QR Codes: Custom-designed QR codes with logos that evade pattern matching
- PDF Attachment QR: QR code embedded in PDF attachment rather than email body
Detection Challenges
- Pattern-based detection faces trade-off: aggressive tuning causes false positives, cautious tuning causes misses
- Average similarity score of 0.209 between quishing and legitimate QR emails
- QR codes in image attachments require OCR and deep image processing
Workflow
Step 1: Enable Image-Based Threat Detection
- Configure email gateway to scan embedded images for QR codes
- Enable OCR processing on image attachments (PNG, JPG, GIF, BMP)
- Deploy multimodal AI that combines image processing, OCR, and NLP analysis
- Configure PDF scanning to detect QR codes within attachments
- Set up detection for ASCII/text-based QR code rendering
Step 2: Configure QR Code URL Analysis
- Extract URLs from detected QR codes and submit to URL reputation services
- Apply same URL scanning policies to QR-extracted URLs as text-based URLs
- Enable real-time sandbox analysis for QR-decoded destination pages
- Configure time-of-click protection for QR-extracted URLs where possible
- Block known phishing domains extracted from QR codes
Step 3: Deploy Mobile-Side Protection
- Implement mobile threat defense (MTD) with QR code scanning capability
- Deploy Palo Alto ALFA or equivalent safe-by-design QR scanning
- Configure MDM policies to warn users before opening scanned URLs
- Enable corporate VPN/secure browser for QR-scanned destinations
- Block known credential harvesting domains at the mobile proxy level
Step 4: Build Detection Rules
- Alert on emails containing only an image and minimal text (common quishing pattern)
- Flag emails with QR code images from external first-time senders
- Detect urgency language combined with QR code presence
- Alert on emails impersonating IT/security team requesting QR scan for MFA setup
- Monitor for common quishing themes: MFA reset, document signing, voicemail notification
Step 5: Train Users on Quishing Recognition
- Update security awareness program to include QR code phishing scenarios
- Conduct quishing simulation campaigns using controlled QR codes
- Teach users to verify QR destination URLs before entering credentials
- Establish reporting process for suspicious QR code emails
- Distribute guidance on safe QR scanning practices
Tools & Resources
- Barracuda Multimodal AI: OCR + deep image processing for QR detection
- Palo Alto ALFA: Safe-by-design QR code scanning assessment
- Microsoft Defender for O365: QR code detection in email images
- Proofpoint TAP: Image-based threat analysis with QR decoding
- Lookout/Zimperium: Mobile threat defense with QR scanning
Validation
- QR code phishing emails detected in controlled testing
- Split QR code and ASCII QR code evasion techniques caught
- QR-extracted URLs submitted to sandbox analysis
- Mobile devices alert on malicious QR destinations
- User reporting rate for quishing simulations exceeds 50%
- False positive rate for QR detection below 1%
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md2.4 KB
API Reference: QR Code Phishing Detection
pyzbar — QR/Barcode Decoding
Installation
pip install pyzbar Pillow
# On Linux: apt-get install libzbar0Core Functions
from pyzbar.pyzbar import decode
from PIL import Image
results = decode(Image.open("qr.png"))
for r in results:
print(r.type) # "QRCODE"
print(r.data) # b"https://..."
print(r.rect) # Rect(left=40, top=40, width=200, height=200)Decoded Object Attributes
| Attribute | Type | Description |
|---|---|---|
data |
bytes | Decoded content |
type |
str | Barcode type (QRCODE, EAN13, etc.) |
rect |
Rect | Bounding rectangle |
polygon |
list | Corner points |
quality |
int | Decode quality score |
Python email Module — EML Parsing
Parsing an EML file
import email
from email import policy
with open("message.eml", "rb") as f:
msg = email.message_from_binary_file(f, policy=policy.default)
subject = msg["Subject"]
sender = msg["From"]Walking MIME Parts
for part in msg.walk():
ctype = part.get_content_type()
if ctype.startswith("image/"):
payload = part.get_payload(decode=True)
filename = part.get_filename()URL Analysis Indicators
Suspicious TLD List
.xyz, .top, .club, .work, .buzz, .tk, .ml, .ga, .cf, .gq
Phishing URL Patterns
| Pattern | Risk |
|---|---|
| IP address in domain | High |
| Domain > 40 chars | Medium |
| HTTP (no TLS) | Medium |
| 3+ subdomains | Medium |
| URL shortener | High |
| Base64 in path | High |
Microsoft Defender for Office 365 — Safe Links API
Check URL reputation
POST https://graph.microsoft.com/v1.0/security/tiIndicators
Content-Type: application/json
Authorization: Bearer {token}
{
"targetProduct": "Azure Sentinel",
"threatType": "Phishing",
"url": "https://suspicious-domain.xyz/login"
}VirusTotal URL Scan API
Submit URL
POST https://www.virustotal.com/api/v3/urls
x-apikey: {API_KEY}
Content-Type: application/x-www-form-urlencoded
url=https://suspicious-domain.xyzResponse Fields
| Field | Description |
|---|---|
data.attributes.last_analysis_stats.malicious |
Engines flagging as malicious |
data.attributes.last_analysis_stats.harmless |
Engines flagging as clean |
data.attributes.categories |
URL categorization |
standards.md1.6 KB
Standards & References: Detecting QR Code Phishing
Industry Statistics (2025-2026)
- Quishing incidents grew from 46,000 to 250,000 between Aug-Nov 2025 (Kaspersky)
- 89.3% of QR code phishing targets credential theft
- 12% of January 2026 attacks used ASCII text-based QR codes
- Only 36% of quishing incidents accurately identified by recipients
- 25% year-over-year growth in quishing incidents
MITRE ATT&CK References
- T1566.001: Phishing: Spearphishing Attachment (QR in PDF/image)
- T1566.002: Phishing: Spearphishing Link (QR-encoded URL)
- T1204.001: User Execution: Malicious Link (user scans QR)
- T1598.003: Phishing for Information: Spearphishing Link
Quishing Attack Patterns
| Pattern | Description | Detection Difficulty |
|---|---|---|
| Inline QR image | QR code embedded directly in email body | Medium |
| PDF attachment QR | QR code inside attached PDF document | High |
| Split QR code | QR divided into two benign-looking images | Very High |
| ASCII QR code | QR rendered as text characters | Very High |
| Nested QR code | QR within QR with intermediate redirect | High |
| Styled QR code | Artistic QR with logos/colors | Medium |
Common Quishing Themes
- MFA enrollment/reset requiring QR scan
- Document signing via QR code
- Voicemail notification with QR access
- Package delivery QR confirmation
- IT security update requiring QR authentication
- Shared document access via QR
Detection Technologies
- Multimodal AI (OCR + deep image + NLP)
- Computer vision QR code detection
- URL reputation analysis for decoded URLs
- Mobile threat defense QR scanning
- Behavioral analysis of image-only emails
workflows.md2.5 KB
Workflows: Detecting QR Code Phishing
Workflow 1: QR Code Email Detection Pipeline
Inbound email arrives at gateway
|
v
[Standard text/URL scanning]
+-- Check text-based URLs (standard pipeline)
+-- No malicious URLs found in text
|
v
[Image analysis module]
+-- Scan all embedded images and attachments
+-- Apply QR code detection algorithm
+-- Check for ASCII/text-rendered QR codes
+-- Scan PDF attachments for embedded QR codes
|
v
[QR code detected?]
+-- NO --> Continue standard delivery
+-- YES --> Extract encoded URL
|
v
[URL reputation and analysis]
+-- Check URL against threat intelligence feeds
+-- Check domain age and registration data
+-- Submit to sandbox for real-time analysis
+-- Check for credential harvesting indicators
|
v
[Decision]
+-- MALICIOUS URL: Block email, alert SOC
+-- SUSPICIOUS URL: Quarantine, add warning banner
+-- UNKNOWN URL: Tag email with QR warning banner
+-- CLEAN URL: Deliver with informational bannerWorkflow 2: Quishing Incident Response
User reports QR code phishing email
|
v
[Triage (15 minutes)]
+-- Extract QR code and decode URL
+-- Check if URL is active credential harvester
+-- Search mailboxes for same email to other recipients
|
v
[Containment]
+-- Block sender domain across email gateway
+-- Retract email from all recipient inboxes
+-- Block decoded URL at web proxy/firewall
+-- If user scanned: check for credential compromise
|
v
[Investigation]
+-- Did any user submit credentials on phishing page?
+-- Check authentication logs for compromised accounts
+-- If credentials entered: force password reset + revoke sessions
+-- Review phishing page infrastructure
|
v
[Recovery and prevention]
+-- Add QR URL pattern to detection rules
+-- Update security awareness training
+-- Send targeted alert to affected users
+-- Document IOCs for threat intelligence sharingWorkflow 3: Mobile QR Scanning Protection
User scans QR code with mobile device
|
v
[Mobile threat defense intercepts]
+-- Decode QR destination URL
+-- Check against mobile threat intelligence
|
v
[URL assessment]
+-- KNOWN MALICIOUS: Block and alert user
+-- SUSPICIOUS: Display warning, require confirmation
+-- CREDENTIAL PAGE: Extra warning about entering passwords
+-- CLEAN: Allow access
|
v
[If user proceeds to suspicious site]
+-- Route through secure browser/VPN
+-- Monitor for credential submission
+-- Log URL and user action for SOC reviewScripts 2
agent.py5.9 KB
#!/usr/bin/env python3
"""Agent for detecting QR code phishing (quishing) in email attachments and bodies."""
import argparse
import email
import json
import os
import re
from datetime import datetime, timezone
from email import policy
from urllib.parse import urlparse
try:
from PIL import Image
from pyzbar.pyzbar import decode as qr_decode
HAS_QR = True
except ImportError:
HAS_QR = False
try:
HAS_REQUESTS = True
except ImportError:
HAS_REQUESTS = False
SUSPICIOUS_TLDS = {
".xyz", ".top", ".club", ".work", ".buzz", ".tk", ".ml", ".ga", ".cf",
".gq", ".info", ".online", ".site", ".icu",
}
PHISHING_KEYWORDS = [
"verify", "account", "suspended", "confirm", "urgent", "expire",
"password", "login", "credential", "security", "update", "click",
"immediate", "unauthorized", "invoice",
]
def extract_images_from_eml(eml_path):
"""Extract image attachments and inline images from an .eml file."""
images = []
with open(eml_path, "rb") as f:
msg = email.message_from_binary_file(f, policy=policy.default)
for part in msg.walk():
content_type = part.get_content_type()
if content_type.startswith("image/"):
payload = part.get_payload(decode=True)
if payload:
ext = content_type.split("/")[1].split(";")[0]
fname = part.get_filename() or f"inline_image.{ext}"
images.append({"filename": fname, "data": payload, "type": content_type})
return images, msg
def decode_qr_from_bytes(image_data):
"""Decode QR codes from raw image bytes."""
if not HAS_QR:
return []
import io
img = Image.open(io.BytesIO(image_data))
results = qr_decode(img)
return [r.data.decode("utf-8", errors="replace") for r in results]
def analyze_url(url):
"""Score a URL for phishing risk indicators."""
indicators = []
parsed = urlparse(url)
domain = parsed.netloc.lower()
for tld in SUSPICIOUS_TLDS:
if domain.endswith(tld):
indicators.append(f"Suspicious TLD: {tld}")
break
if re.search(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", domain):
indicators.append("URL uses IP address instead of domain")
if len(domain) > 40:
indicators.append(f"Unusually long domain: {len(domain)} chars")
if domain.count(".") > 3:
indicators.append(f"Many subdomains: {domain.count('.')} dots")
if parsed.scheme == "http":
indicators.append("Uses HTTP instead of HTTPS")
path = parsed.path + (parsed.query or "")
for kw in PHISHING_KEYWORDS:
if kw in path.lower():
indicators.append(f"Phishing keyword in URL path: '{kw}'")
break
return {
"url": url,
"domain": domain,
"indicators": indicators,
"risk_score": min(len(indicators) * 25, 100),
}
def analyze_email(eml_path):
"""Full QR phishing analysis of an email file."""
results = {
"file": eml_path,
"timestamp": datetime.now(timezone.utc).isoformat(),
"images_found": 0,
"qr_codes_found": 0,
"urls_extracted": [],
"phishing_indicators": [],
"risk_level": "LOW",
}
images, msg = extract_images_from_eml(eml_path)
results["images_found"] = len(images)
results["subject"] = msg.get("Subject", "")
results["from"] = msg.get("From", "")
subject_lower = results["subject"].lower()
for kw in PHISHING_KEYWORDS:
if kw in subject_lower:
results["phishing_indicators"].append(f"Phishing keyword in subject: '{kw}'")
all_urls = []
for img_info in images:
decoded = decode_qr_from_bytes(img_info["data"])
for url in decoded:
if url.startswith(("http://", "https://")):
analysis = analyze_url(url)
all_urls.append(analysis)
results["qr_codes_found"] = len(all_urls)
results["urls_extracted"] = all_urls
max_risk = max((u["risk_score"] for u in all_urls), default=0)
if max_risk >= 75:
results["risk_level"] = "CRITICAL"
elif max_risk >= 50:
results["risk_level"] = "HIGH"
elif max_risk >= 25:
results["risk_level"] = "MEDIUM"
return results
def scan_directory(dir_path):
"""Scan a directory for .eml files and analyze each."""
all_results = []
for root, _, files in os.walk(dir_path):
for fname in files:
if fname.lower().endswith(".eml"):
fpath = os.path.join(root, fname)
result = analyze_email(fpath)
all_results.append(result)
return all_results
def main():
parser = argparse.ArgumentParser(
description="Detect QR code phishing (quishing) in emails"
)
parser.add_argument("input", help="Path to .eml file or directory of .eml files")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
print("[*] QR Code Phishing Detection Agent")
print(f"[*] QR decoding available: {HAS_QR}")
if os.path.isdir(args.input):
results = scan_directory(args.input)
else:
results = [analyze_email(args.input)]
report = {
"scan_time": datetime.now(timezone.utc).isoformat(),
"files_scanned": len(results),
"qr_phishing_detected": sum(1 for r in results if r["risk_level"] in ("HIGH", "CRITICAL")),
"results": results,
}
if args.verbose:
for r in results:
print(f"\n File: {r['file']}")
print(f" Subject: {r.get('subject', 'N/A')}")
print(f" Images: {r['images_found']}, QR codes: {r['qr_codes_found']}")
print(f" Risk: {r['risk_level']}")
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
process.py11.5 KB
#!/usr/bin/env python3
"""
QR Code Phishing (Quishing) Detection Engine
Detects QR codes in images and email content, extracts encoded URLs,
and checks them against known phishing indicators.
Usage:
python process.py scan-image --image qr_image.png
python process.py scan-email --eml-file message.eml
python process.py check-url --url "https://example.com/login"
"""
import argparse
import json
import re
import sys
import base64
from dataclasses import dataclass, field, asdict
from urllib.parse import urlparse
try:
from PIL import Image
HAS_PIL = True
except ImportError:
HAS_PIL = False
try:
from pyzbar.pyzbar import decode as qr_decode
HAS_PYZBAR = True
except ImportError:
HAS_PYZBAR = False
@dataclass
class QRCodeFinding:
"""A detected QR code and its analysis."""
source: str = ""
decoded_url: str = ""
domain: str = ""
is_suspicious: bool = False
risk_score: int = 0
indicators: list = field(default_factory=list)
@dataclass
class QuishingAnalysis:
"""Complete quishing analysis result."""
source_file: str = ""
qr_codes_found: int = 0
findings: list = field(default_factory=list)
email_indicators: list = field(default_factory=list)
overall_risk: str = "low"
recommended_action: str = ""
# Suspicious URL patterns for credential phishing
SUSPICIOUS_URL_PATTERNS = [
r'login|signin|sign-in|log-in',
r'verify|verification|validate',
r'account|password|credential',
r'microsoft|office365|outlook|sharepoint',
r'google|gmail|workspace',
r'secure|security|auth',
r'update|confirm|suspend',
r'\.tk$|\.ml$|\.ga$|\.cf$|\.gq$',
r'bit\.ly|tinyurl|t\.co|is\.gd|cutt\.ly',
]
# Known phishing infrastructure patterns
PHISHING_INFRA_PATTERNS = [
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', # IP address URLs
r'[a-z0-9]{20,}\.web\.app', # Firebase hosting abuse
r'[a-z0-9]{20,}\.netlify\.app', # Netlify abuse
r'[a-z0-9-]+\.glitch\.me', # Glitch abuse
r'[a-z0-9-]+\.workers\.dev', # Cloudflare workers abuse
]
# Common quishing email indicators
QUISHING_EMAIL_PATTERNS = [
r'scan\s+(this|the)\s+qr\s+code',
r'scan\s+to\s+(verify|authenticate|confirm|access)',
r'multi.?factor\s+authentication',
r'mfa\s+(setup|enrollment|reset|update)',
r'voicemail\s+(notification|message)',
r'document\s+(sign|signing|review)',
r'security\s+update\s+required',
r'action\s+required',
]
def analyze_url(url: str) -> QRCodeFinding:
"""Analyze a URL extracted from a QR code for phishing indicators."""
finding = QRCodeFinding(decoded_url=url)
try:
parsed = urlparse(url)
finding.domain = parsed.netloc
except Exception:
finding.indicators.append("Could not parse URL")
finding.is_suspicious = True
finding.risk_score = 50
return finding
score = 0
# Check suspicious URL patterns
url_lower = url.lower()
for pattern in SUSPICIOUS_URL_PATTERNS:
if re.search(pattern, url_lower):
finding.indicators.append(f"Suspicious URL pattern: {pattern}")
score += 15
# Check phishing infrastructure patterns
for pattern in PHISHING_INFRA_PATTERNS:
if re.search(pattern, url_lower):
finding.indicators.append(f"Known phishing infrastructure pattern: {pattern}")
score += 25
# Check for URL shorteners (hiding true destination)
shorteners = ['bit.ly', 'tinyurl.com', 't.co', 'is.gd', 'cutt.ly',
'rebrand.ly', 'ow.ly', 'buff.ly']
if finding.domain in shorteners:
finding.indicators.append(f"URL shortener detected: {finding.domain}")
score += 20
# Check for IP address URL
if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', finding.domain):
finding.indicators.append("URL uses IP address instead of domain name")
score += 30
# Check for excessive subdomains (common in phishing)
subdomain_count = finding.domain.count('.')
if subdomain_count > 3:
finding.indicators.append(f"Excessive subdomains ({subdomain_count})")
score += 15
# Check for homoglyph characters in domain
non_ascii = [c for c in finding.domain if ord(c) > 127]
if non_ascii:
finding.indicators.append("Non-ASCII characters in domain (possible homoglyph)")
score += 25
# Check protocol
if not url.startswith('https://'):
finding.indicators.append("URL does not use HTTPS")
score += 10
finding.risk_score = min(score, 100)
finding.is_suspicious = score >= 30
return finding
def scan_image_for_qr(image_path: str) -> list:
"""Scan an image file for QR codes and extract URLs."""
findings = []
if not HAS_PIL:
print("Pillow not installed. Install with: pip install Pillow", file=sys.stderr)
return findings
if not HAS_PYZBAR:
print("pyzbar not installed. Install with: pip install pyzbar", file=sys.stderr)
return findings
try:
img = Image.open(image_path)
decoded_objects = qr_decode(img)
for obj in decoded_objects:
data = obj.data.decode('utf-8', errors='replace')
if data.startswith(('http://', 'https://', 'www.')):
url = data if data.startswith('http') else f'https://{data}'
finding = analyze_url(url)
finding.source = image_path
findings.append(finding)
else:
finding = QRCodeFinding(
source=image_path,
decoded_url=data,
indicators=["QR code contains non-URL data"],
risk_score=10
)
findings.append(finding)
except Exception as e:
print(f"Error scanning image: {e}", file=sys.stderr)
return findings
def scan_email_content(eml_content: str) -> QuishingAnalysis:
"""Analyze email content for quishing indicators."""
analysis = QuishingAnalysis()
body_lower = eml_content.lower()
# Check for quishing email patterns
for pattern in QUISHING_EMAIL_PATTERNS:
if re.search(pattern, body_lower):
analysis.email_indicators.append(f"Quishing language pattern: {pattern}")
# Check for image-heavy email with minimal text
text_content = re.sub(r'<[^>]+>', '', eml_content)
text_content = re.sub(r'\s+', ' ', text_content).strip()
has_images = bool(re.search(r'<img|Content-Type:\s*image/', eml_content, re.IGNORECASE))
text_words = len(text_content.split())
if has_images and text_words < 50:
analysis.email_indicators.append(
"Image-heavy email with minimal text (common quishing pattern)"
)
# Check for base64-encoded images
b64_images = re.findall(
r'Content-Type:\s*image/\w+.*?Content-Transfer-Encoding:\s*base64\s*\n\n([\w+/=\n]+)',
eml_content, re.DOTALL | re.IGNORECASE
)
if b64_images and HAS_PIL and HAS_PYZBAR:
for i, b64_data in enumerate(b64_images):
try:
clean_data = b64_data.replace('\n', '')
img_bytes = base64.b64decode(clean_data)
import io
img = Image.open(io.BytesIO(img_bytes))
decoded = qr_decode(img)
for obj in decoded:
data = obj.data.decode('utf-8', errors='replace')
if data.startswith(('http://', 'https://')):
finding = analyze_url(data)
finding.source = f"embedded_image_{i}"
analysis.findings.append(finding)
analysis.qr_codes_found += 1
except Exception:
continue
# Calculate overall risk
indicator_count = len(analysis.email_indicators)
has_suspicious_qr = any(f.is_suspicious for f in analysis.findings)
if has_suspicious_qr and indicator_count >= 2:
analysis.overall_risk = "critical"
analysis.recommended_action = "BLOCK and alert SOC"
elif has_suspicious_qr or indicator_count >= 3:
analysis.overall_risk = "high"
analysis.recommended_action = "QUARANTINE for manual review"
elif indicator_count >= 1:
analysis.overall_risk = "medium"
analysis.recommended_action = "TAG with QR phishing warning banner"
else:
analysis.overall_risk = "low"
analysis.recommended_action = "DELIVER normally"
return analysis
def format_report(analysis: QuishingAnalysis) -> str:
"""Format analysis as readable report."""
lines = []
lines.append("=" * 60)
lines.append(" QR CODE PHISHING (QUISHING) ANALYSIS REPORT")
lines.append("=" * 60)
lines.append(f" QR Codes Found: {analysis.qr_codes_found}")
lines.append(f" Overall Risk: {analysis.overall_risk.upper()}")
lines.append(f" Action: {analysis.recommended_action}")
if analysis.email_indicators:
lines.append(f"\n [EMAIL INDICATORS] ({len(analysis.email_indicators)})")
for i, ind in enumerate(analysis.email_indicators, 1):
lines.append(f" {i}. {ind}")
if analysis.findings:
lines.append(f"\n [QR CODE FINDINGS] ({len(analysis.findings)})")
for i, finding in enumerate(analysis.findings, 1):
lines.append(f" {i}. URL: {finding.decoded_url}")
lines.append(f" Domain: {finding.domain}")
lines.append(f" Risk Score: {finding.risk_score}/100")
lines.append(f" Suspicious: {'YES' if finding.is_suspicious else 'No'}")
for ind in finding.indicators:
lines.append(f" - {ind}")
lines.append("=" * 60)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="QR Code Phishing Detection")
subparsers = parser.add_subparsers(dest="command")
img_parser = subparsers.add_parser("scan-image", help="Scan image for QR codes")
img_parser.add_argument("--image", required=True)
eml_parser = subparsers.add_parser("scan-email", help="Scan email for quishing")
eml_parser.add_argument("--eml-file", required=True)
url_parser = subparsers.add_parser("check-url", help="Check URL from QR code")
url_parser.add_argument("--url", required=True)
parser.add_argument("--json", action="store_true")
args = parser.parse_args()
if args.command == "scan-image":
findings = scan_image_for_qr(args.image)
analysis = QuishingAnalysis(
source_file=args.image,
qr_codes_found=len(findings),
findings=findings
)
if args.json:
print(json.dumps(asdict(analysis), indent=2))
else:
print(format_report(analysis))
elif args.command == "scan-email":
with open(args.eml_file, 'r', errors='replace') as f:
content = f.read()
analysis = scan_email_content(content)
analysis.source_file = args.eml_file
if args.json:
print(json.dumps(asdict(analysis), indent=2))
else:
print(format_report(analysis))
elif args.command == "check-url":
finding = analyze_url(args.url)
if args.json:
print(json.dumps(asdict(finding), indent=2))
else:
print(f"URL: {finding.decoded_url}")
print(f"Domain: {finding.domain}")
print(f"Risk Score: {finding.risk_score}/100")
print(f"Suspicious: {'YES' if finding.is_suspicious else 'No'}")
for ind in finding.indicators:
print(f" - {ind}")
else:
parser.print_help()
if __name__ == "__main__":
main()