Skip to content

qroolecom/qr-security-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 

Repository files navigation

πŸ” QR Code Security Guide

A practical guide to QR code risks, real-world attacks, and safe scanning practices for developers, security researchers, and everyday users.

Awesome License: CC0 PRs Welcome Stars


πŸ“– Table of Contents


⚠️ What is QRishing?

QRishing (QR + phishing) is an attack where malicious actors replace or overlay legitimate QR codes with ones that redirect users to fraudulent websites, trigger malware downloads, or steal credentials.

Unlike traditional phishing links, QR codes are opaque to the human eye β€” you cannot inspect the destination URL before scanning.

Legitimate QR Code         Malicious QR Code
      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
   β–ˆβ–ˆ      β–ˆβ–ˆ                 β–ˆβ–ˆ      β–ˆβ–ˆ
   β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ      β†’          β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ
   β–ˆβ–ˆ      β–ˆβ–ˆ                 β–ˆβ–ˆ      β–ˆβ–ˆ
      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ                     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ
  β†’ https://bank.com         β†’ https://bank-login.ru

🎯 Attack Vectors

1. Physical Sticker Replacement

Attackers print malicious QR codes on stickers and place them over legitimate ones in public spaces β€” restaurants, parking meters, bike-sharing stations.

Documented locations: Restaurant tables, EV charging stations, parking payment terminals, public transit posters.

2. Email & Document QRishing

QR codes embedded in phishing emails to bypass URL scanners. Security filters check links, not images β€” a QR code in a PDF attachment is invisible to most scanners.

3. Adversary-in-the-Middle (AiTM) via QR

Used in Microsoft 365 credential theft campaigns. The attacker generates a real-time QR code that encodes a session-hijacking URL, bypassing MFA.

4. Quishing in Corporate Environments

Fake IT department emails with QR codes claiming to be "multi-factor authentication setup" or "VPN reconfiguration" instructions.

5. Malicious WiFi QR Codes

QR codes encoding WIFI:T:WPA;S:<SSID>;P:<password>;; can silently connect a user's device to a rogue access point.

WIFI:T:WPA;S:FreeAirport_WiFi;P:;H:false;;
         ↑ No password β€” rogue hotspot

6. Deep Link Exploitation

On mobile, QR codes can trigger deep links (myapp://action?token=...) that bypass browser security and directly invoke app actions.


πŸ“° Real-World Incidents

Year Incident Impact
2022 FBI warning on malicious QR codes at parking meters across the US Nationwide alert issued
2022 FTX bankruptcy scammers replaced official QR codes in TV interviews Thousands of dollars stolen
2023 Microsoft 365 QR phishing campaign targeting energy sector 1,000+ corporate emails targeted
2023 QR codes in physical mail impersonating IRS and FedEx High click-through rate due to perceived legitimacy
2024 EV charging station QR replacement attacks in UK and EU Payment credential theft

πŸ”¬ QR Code Anatomy

Understanding the structure helps developers build safer scanners.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ  β–ˆβ–ˆ β”‚  ← Quiet zone (4 modules)
β”‚ β–ˆβ–ˆ β”Œβ”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β” β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β”‚ β–ˆ β–ˆ β–ˆ β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ”‚ β–ˆ β–ˆ β–ˆ β”‚ β–ˆβ–ˆ β”‚  ← Finder patterns (3 corners)
β”‚ β–ˆβ–ˆ β”‚ β–ˆ   β–ˆ β”‚     β”‚ β–ˆ   β–ˆ β”‚ β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β”‚ β–ˆ β–ˆ β–ˆ β”‚     β”‚ β–ˆ β–ˆ β–ˆ β”‚ β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β””β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”˜ β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ  ← Timing pattern β†’      β–ˆβ–ˆ β”‚  ← Alignment + timing modules
β”‚ β–ˆβ–ˆ     [DATA CODEWORDS]      β–ˆβ–ˆ β”‚  ← Encoded payload
β”‚ β–ˆβ–ˆ β”Œβ”€β”€β”€β”€β”€β”€β”€β”                β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β”‚ β–ˆ β–ˆ β–ˆ β”‚  Format info   β–ˆβ–ˆ β”‚  ← Error correction level
β”‚ β–ˆβ–ˆ β”‚ β–ˆ   β–ˆ β”‚                β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β”‚ β–ˆ β–ˆ β–ˆ β”‚                β–ˆβ–ˆ β”‚
β”‚ β–ˆβ–ˆ β””β”€β”€β”€β”€β”€β”€β”€β”˜                β–ˆβ–ˆ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Key facts:

  • QR codes can encode up to 4,296 alphanumeric characters or 7,089 numeric digits
  • Error correction levels: L (7%), M (15%), Q (25%), H (30%) β€” higher = more redundancy
  • Version 1 (21Γ—21) to Version 40 (177Γ—177) modules
  • The data payload is XOR-masked with one of 8 mask patterns to avoid visual patterns

βœ… Safe Scanning Checklist

For Users

  • Preview the URL before opening β€” use a scanner that shows the decoded URL before navigating
  • Check if the domain matches the expected organization
  • Be suspicious of QR codes in unexpected emails, even from known senders
  • Look for physical tampering β€” stickers placed over original QR codes
  • Never scan QR codes that claim to "fix" a security problem
  • On mobile, disable automatic link-following in your QR scanner

For Organizations

  • Use tamper-evident materials for printed QR codes
  • Add your domain/logo inside the QR code (logo embedding)
  • Implement QR code monitoring (track scan events server-side)
  • Include the destination URL in plain text alongside the QR code
  • Rotate QR codes periodically for sensitive operations

πŸ‘¨β€πŸ’» For Developers

URL Validation Before Navigation

Always validate the decoded URL before opening it. Never auto-navigate.

function isSafeUrl(decoded) {
  try {
    const url = new URL(decoded);
    // Only allow http/https
    if (!['http:', 'https:'].includes(url.protocol)) return false;
    // Check against known malicious TLDs (example)
    const suspiciousTLDs = ['.ru', '.cn', '.tk', '.ml', '.ga', '.cf'];
    if (suspiciousTLDs.some(tld => url.hostname.endsWith(tld))) {
      return 'warn'; // Warn, don't block
    }
    return true;
  } catch {
    return false; // Not a URL
  }
}

Detecting QR Code Type

function detectQRType(data) {
  if (/^https?:\/\//i.test(data))   return { type: 'URL',      risk: 'medium' };
  if (/^WIFI:/i.test(data))          return { type: 'WiFi',     risk: 'high'   };
  if (/^mailto:/i.test(data))        return { type: 'Email',    risk: 'medium' };
  if (/^tel:/i.test(data))           return { type: 'Phone',    risk: 'low'    };
  if (/^BEGIN:VCARD/i.test(data))    return { type: 'vCard',    risk: 'low'    };
  if (/^smsto?:/i.test(data))        return { type: 'SMS',      risk: 'medium' };
  if (/^geo:/i.test(data))           return { type: 'Location', risk: 'low'    };
  if (/^[a-z][a-z0-9+\-.]*:\/\//i.test(data)) return { type: 'DeepLink', risk: 'high' };
  return { type: 'Text', risk: 'low' };
}

Checking Redirect Chains (Node.js)

async function resolveRedirects(url, maxHops = 5) {
  const chain = [url];
  let current = url;
  for (let i = 0; i < maxHops; i++) {
    const res = await fetch(current, { method: 'HEAD', redirect: 'manual' });
    if (res.status >= 300 && res.status < 400) {
      current = res.headers.get('location');
      chain.push(current);
    } else break;
  }
  return chain; // Show the full redirect chain to the user
}

Scanning QR Codes in the Browser (jsQR)

<script src="https://cdn.jsdelivr.net/npm/jsqr/dist/jsQR.js"></script>
<script>
async function scanFromFile(file) {
  const img = await createImageBitmap(file);
  const canvas = document.createElement('canvas');
  canvas.width = img.width;
  canvas.height = img.height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(img, 0, 0);
  const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
  const result = jsQR(imageData.data, canvas.width, canvas.height);
  return result?.data ?? null;
}
</script>

πŸ›‘οΈ Detection Techniques

Heuristic Risk Scoring

def score_qr_url(url: str) -> dict:
    import re
    from urllib.parse import urlparse

    score = 0
    flags = []
    parsed = urlparse(url)

    # Suspicious patterns
    if re.search(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}', parsed.netloc):
        score += 30; flags.append('IP address instead of domain')
    if len(parsed.netloc) > 50:
        score += 20; flags.append('Unusually long domain')
    if parsed.netloc.count('.') > 4:
        score += 15; flags.append('Excessive subdomains')
    if re.search(r'(login|signin|verify|account|secure|update)', parsed.path, re.I):
        score += 25; flags.append('Sensitive keyword in path')
    if re.search(r'(paypal|amazon|microsoft|apple|google)', parsed.netloc, re.I):
        if not parsed.netloc.endswith(('.paypal.com','.amazon.com','.microsoft.com')):
            score += 40; flags.append('Brand name in non-official domain')

    return {
        'score': min(score, 100),
        'risk': 'high' if score > 60 else 'medium' if score > 30 else 'low',
        'flags': flags
    }

πŸ”§ Tools & Resources

Open-Source QR Scanners

Tool Platform Privacy Notes
Qroole QR Code Reader Chrome, Firefox, Edge βœ… 100% local 4 scan modes, history, 10 languages
ZXing Java/Android βœ… Local Industry-standard library
jsQR JavaScript βœ… Local Pure JS, no dependencies
pyzbar Python βœ… Local Wraps ZBar library
QR Scanner (iOS) iOS βœ… Local Built into Camera app since iOS 11

QR Code Generation

Library Language License
qrcode Python BSD
qrcode.js JavaScript MIT
QRCoder C# MIT
rust-qrcode Rust MIT/Apache

Security Research & References

Browser Extensions for Safe QR Scanning


🀝 Contributing

Contributions are welcome! Please:

  1. Fork this repository
  2. Add your resource, fix, or improvement
  3. Submit a pull request with a clear description

What we're looking for:

  • New real-world QRishing incidents (with sources)
  • Code examples in additional languages
  • Detection techniques and heuristics
  • Tool additions (must be open-source or privacy-respecting)

πŸ“„ License

CC0

This work is dedicated to the public domain under CC0 1.0. You may copy, modify, and distribute without permission.


Made with ❀️ by Qroole β€” Privacy-first QR tools for the web

About

A practical guide to QR code security risks, real-world attacks & safe scanning

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors