Privacy Tools Guide

QR codes have become ubiquitous in modern workflows—payments, authentication, product tracking, and event check-ins all rely on them. However, this convenience has attracted threat actors who exploit QR codes for phishing attacks, a technique called “quishing.” This guide provides developers and power users with practical strategies to identify, prevent, and respond to QR code phishing attempts.

Understanding Quishing Attacks

Quishing combines “QR code” with “phishing.” Attackers embed malicious URLs in QR codes that redirect users to credential-harvesting pages, malware download sites, or fake login portals. The attack succeeds because QR codes bypass traditional email filters and appear innocuous when printed on physical materials.

Attack vectors include:

For developers building applications that generate or scan QR codes, understanding these attack surfaces is essential for building secure systems.

How QR Codes Enable Attacks

A QR code simply encodes text—typically an URL. When scanned, the phone’s camera or a dedicated app interprets this text and acts on it. The critical security gap: users cannot determine a QR code’s destination without scanning it first.

Consider this example of a QR code that appears legitimate but leads to a malicious site:

# A simple Python script to generate a QR code
import qrcode

# This QR code looks innocent but points to attacker-controlled domain
malicious_data = "https://example-login.com.phishing-website.xyz/reset-password"
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(malicious_data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save("qr_code.png")

The URL uses a technique called “typosquatting”—replacing legitimate characters with similar-looking ones to deceive users.

Detecting Malicious QR Codes

Several techniques help identify potentially dangerous QR codes before scanning:

URL Analysis

Before visiting any URL from a QR code, extract and analyze it:

from urllib.parse import urlparse
import qrcode
from PIL import Image
from pyzbar.pyzbar import decode

def extract_qr_url(image_path):
    """Extract URL from a QR code image."""
    decoded_objects = decode(Image.open(image_path))
    for obj in decoded_objects:
        if obj.type == 'QRCODE':
            return obj.data.decode('utf-8')
    return None

def analyze_url(url):
    """Analyze URL for suspicious characteristics."""
    parsed = urlparse(url)

    warnings = []

    # Check for URL shorteners
    shorteners = ['bit.ly', 'tinyurl.com', 'goo.gl', 't.co', 'is.gd']
    if any(shortener in parsed.netloc for shortener in shorteners):
        warnings.append("URL uses a shortening service")

    # Check for suspicious TLDs
    suspicious_tlds = ['.xyz', '.top', '.gq', '.tk', '.ml', '.cf']
    if any(parsed.netloc.endswith(tld) for tld in suspicious_tlds):
        warnings.append("Unusual top-level domain")

    # Check for excessive subdomains
    if parsed.netloc.count('.') > 3:
        warnings.append("Excessive subdomain depth")

    # Check for IP address usage
    import re
    if re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', parsed.netloc):
        warnings.append("URL uses IP address instead of domain")

    return warnings

# Example usage
url = extract_qr_url("sample_qr.png")
if url:
    warnings = analyze_url(url)
    for warning in warnings:
        print(f"Warning: {warning}")

Visual Inspection

Physical QR codes often show signs of tampering:

Building QR Security into Applications

For developers creating applications that handle QR codes, implement these defensive measures:

URL Preview Screens

Always show users the destination URL before opening it:

// React component for safe QR code navigation
import { useState } from 'react';

function QRNavigator({ extractedUrl }) {
  const [showWarning, setShowWarning] = useState(false);
  const [url, setUrl] = useState(extractedUrl);

  const analyzeUrl = (urlString) => {
    const warnings = [];

    // Check for URL shorteners
    const shorteners = ['bit.ly', 'tinyurl.com', 'goo.gl'];
    if (shorteners.some(s => urlString.includes(s))) {
      warnings.push('URL uses a shortening service');
    }

    // Check for IP addresses
    if (/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(new URL(urlString).hostname)) {
      warnings.push('Destination uses an IP address');
    }

    // Check for suspicious TLDs
    const suspiciousTlds = ['.xyz', '.top', '.gq', '.tk'];
    if (suspiciousTlds.some(tld => urlString.endsWith(tld))) {
      warnings.push('Unusual domain extension');
    }

    return warnings;
  };

  const warnings = analyzeUrl(url);

  return (
    <div className="qr-navigator">
      <p>Destination: <strong>{url}</strong></p>

      {warnings.length > 0 ? (
        <div className="warning-box">
          <h3>⚠️ Security Warnings</h3>
          <ul>
            {warnings.map((w, i) => <li key={i}>{w}</li>)}
          </ul>
          <button onClick={() => window.open(url, '_blank')}>
            Proceed Anyway
          </button>
        </div>
      ) : (
        <button onClick={() => window.open(url, '_blank')}>
          Open URL
        </button>
      )}
    </div>
  );
}

URL Unwrapping Services

For enterprise environments, implement URL unwrapping to reveal the true destination:

import requests

def unwrap_url(shortened_url, timeout=5):
    """Follow redirects to get the final URL."""
    try:
        response = requests.head(
            shortened_url,
            allow_redirects=True,
            timeout=timeout,
            headers={'User-Agent': 'SecurityScanner/1.0'}
        )
        return response.url
    except requests.RequestException as e:
        return f"Error: {str(e)}"

# Usage
final_url = unwrap_url("https://bit.ly/3xY7z8A")
print(f"Actual destination: {final_url}")

This technique reveals shortened URLs but introduces privacy considerations—ensure your implementation complies with applicable privacy regulations and organizational policies.

Protecting Your Organization

Beyond individual awareness, organizations should implement these measures:

Employee Training: Conduct regular security awareness sessions specifically covering quishing. Show examples of physical and digital quishing attempts targeting your industry.

Technical Controls: Deploy email security gateways that analyze QR codes within emails. Implement network-level URL filtering to block known malicious domains.

Reporting Mechanisms: Create clear channels for employees to report suspicious QR codes. Quick reporting enables faster response to emerging threats.

Physical Security: Regularly audit physical spaces for tampered QR codes. Include QR code inspection in security checklists for offices, retail locations, and public areas.

Responding to Quishing Incidents

If you or your organization encounters a quishing attempt:

  1. Document the source: Note where the QR code was found, whether physical or digital
  2. Analyze the URL: Use tools like the Python script above to examine the destination
  3. Report to authorities: File reports with relevantCERT teams (US-CERT, CISA, or local equivalents)
  4. Notify affected users: If the QR code reached multiple people, warn potential victims
  5. Block malicious domains: Add identified malicious URLs to blocklists

Built by theluckystrike — More at zovo.one