phishing defense

Detecting QR Code Phishing with Email Security

Detect and prevent QR code phishing (quishing) attacks that bypass traditional email security by embedding malicious URLs in QR code images within emails.

email-securityimage-analysismobile-securityocrphishingqr-codequishing
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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

  1. Bypasses URL Scanners: Traditional gateways scan text-based URLs but cannot decode image-embedded URLs
  2. Shifts to Unprotected Devices: Corporate email arrives on secured systems but QR scan occurs on personal mobile devices
  3. User Trust: QR codes are normalized in daily life (payments, menus, parking)
  4. 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%
Source materials

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 libzbar0

Core 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.xyz

Response 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 banner

Workflow 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 sharing

Workflow 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 review

Scripts 2

agent.py5.9 KB
Display-only source. This catalog never executes bundled scripts.
#!/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
Display-only source. This catalog never executes bundled scripts.
#!/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()

Assets 1

template.mdtext/markdown · 1.5 KB
Keep exploring