QR Codes You Shouldn't Scan

Aug 6, 2025

8 mins read

Number 3 may surprise you!

I’m kidding of course, blatant web-based phishing attacks are boring. This blog isn’t about those. Most of these examples will probably surprise you in some way. This blog is about the spiraling mess of URI handlers, auth flows, proof-of-presence, and the complete lack of fuzzed code coverage of the ecosystem as a whole.

Ahead of DefCon 2024 there was a usual hubbub of “do/don’t scan QR codes at DefCon/BlackHat!” and I figured it was probably time to stop trusting the same regurgitated word slop we’ve heard repeated for years regarding the subject and have a lil look-see for myself, because you can in fact just do things. I attempted to enumerate the current 2D barcode ecosystem across Windows, iOS, and Android and reverse engineered the related apps. I then put together a few demos of attack paths, sanitized them, and promptly got bored, dumping a few safe formats on Twitter and moving on. It’s been a year, let’s take a look back at some of the things you might have missed.

“Pfffft. QR codes are just for web phishing and I’m too smart to fall for that! Besides, no one is burning an iOS 0-day at DefCon!”

Sound like anyone you know? Send them this blog, point them to Exhibit A, and additonally remind them that DefCon has a thriving sticker culture.

Regardless, at the end of the day you’re probably generally safe to scan barcodes. However, it’s a particularly dumb thing to do at BlackHat/DefCon, especially if you use your phone for work.

You are a human. You can’t read 2D barcodes. You can read text. If you are going to scan 2D barcodes, use a dedicated app that only parses the data as-is and presents it as plain text. Don’t use the built-in camera apps.

Preface

On iOS, the built-in camera.app supports reading multiple formats of 2D barcodes: QR and AppClip (AC). You’re probably never heard of the latter, which is funny, because if an AC code appears in the camera viewport, it takes priority over the discovery of a QR code when using camera.app. You can literally slap a junk AC code next to any QR code and the other QR code becomes functionally useless to all iOS users. This of course opens up some interesting attack vectors if the AC code is placed near… a parking terminal where you pay by QR code. Who needs deception when you can force a 100% success rate?

AppClip Comes First

AC QR

I did the above analysis many iOS versions ago. Today (iOS 18.5), the behavior has improved, but is still quite wonky unless you directly point the camera at the QR code.

On Android, as best as I can tell the built-in camera.apk doesn’t support any 2D barcode formats. Your phone vendor (Samsung, HTC, Google, Huawei, etc…) bundles a camera2.apk into their Android build, and that’s the “camera” you know of and use every day. Well, at this point, suffice to after getting really annoyed reverse engineering the camera2.apk from multiple vendors: Most vendors use zxing and support all of the 2D barcodes the zxing library supports such as UPC-A, if only to embed analytics ad-tracking about every physical 2D barcode you scan. Super fun right?

Point blank: I spent most of my time looking at iOS simply because it wasn’t a sprawling vendor specific hellscape to analyze. There’s some obvious problems even before we start discussing the data in the 2D barcodes, but let’s press onward.

The Data

2D barcodes contain data! Often times that data is URIs, such as http://example.com, which are handled by an app (web browser), enabling some sort of deceptive phishing attacks on the dangerous thing called the world wide web! As stated before, Android is a vendor specific hellscape to analyze. I’ll focus on the context of iOS, but rest assured that Android has a ballpark of 3-5x force multiplier on whatever badness I discuss further.

There’s a lot of URIs that are handled by apps on iOS that you probably don’t think about much, such as:

  • HomeKit X-HM://123456789ABC

homekit

  • Registering an eSIM x-esim:// / LPA:1$<SM-DP_ADDRESS>$<MATCHING_ID>

esim

  • WiFi Networks WIFI:S:NetworkName;T:WPA2-EAP;P:password123;E:WAPI_CERT;PH2:MSCHAP;A:anonIdentity;I:username;H:false

wifi

  • WebAuthn / OTP FIDO:/ / otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example

passcodes

passkey

Yes! There’s multiple formats that dispatch multiple events to multiple handlers. How many potentially un-fuzzed handlers exactly you might ask?

I don’t immediately recall, and I’m trying to write this blog as a one-shot but my notes show I ran these commands and then moved on due to the sheer number. I remember there being a LOT.

7zz x iPhone10_3_iPhone10_6_16.0.2_20A380_Restore.ipsw 098-13722-001.dmg
7zz x -oAPFS 098-13722-001.dmg
plistutil -i System/Library/PrivateFrameworks/WorkflowKit.framework/ICApps.plist

And hey, URIs are fun but let’s not forget about the classics vEvent and vCard (calendar and contacts respectively) formats that are old enough to have their own workflows!

  • vCard
BEGIN:VCARD
VERSION:3.0
N:Lastname;Firstname
FN:Firstname Lastname
ORG:CompanyName
TITLE:JobTitle
ADR:;;123 Sesame St;SomeCity;CA;12345;USA
TEL;WORK;VOICE:1234567890
TEL;CELL:Mobile
TEL;FAX:
EMAIL;WORK;INTERNET:foo@email.com
URL:http://website.com
END:VCARD

vcard

  • vEvent
BEGIN:VEVENT
SUMMARY:lol
DTSTART:20240429T120000
DTEND:20240429T120000
END:VEVENT

vEVENT

The above examples are not exhaustive.

Exhibit A

As you may be aware, I was hacking around on bluetooth in the weeks leading up to DefCon while I was also looking into 2D barcodes. I noted the following to some friends in July 2024 (before DefCon):

“There’s some nastiness/weirdness/hella sus gaps I spotted when looking into QR code formats a while back RE: webauthn passkey QR codes and caBLE proximity based authentication. For Android, it appears they attempted to mitigate this at an OS level by simply allowing FIDO service’s to be inaccessible via API’s, however there’s nothing stopping you from using a client that you know… isn’t Android. I wont have enough time and also wont be at DC/BH, but there’s almost definitely something there if anyone wants to piss off a lot of people at those cons and pour gasoline on the fire of “don’t scan that QR code!”

/* Class: Lcom/android/bluetooth/gatt/GattService;
   Class Access Flags:
    ACC_PUBLIC
   
   Superclass: Lcom/android/bluetooth/btservice/ProfileService;
   Source File: GattService.java
   
   Method Signature: Z( Ljava/util/UUID;
     )
   Method Access Flags:
    ACC_PUBLIC
    ACC_FINAL
   
   Method Register Size: 3
   Method Incoming Size: 2
   Method Outgoing Size: 2
   Method Debug Info Offset: 0x207147
   Method ID Offset: 0x5ba1c
    */

boolean isRestrictedSrvcUuid(GattService this,UUID p1)

{
  boolean bVar1;
  
  bVar1 = this.isFidoSrvcUuid(p1);
  if (((bVar1 == false) && (bVar1 = this.isAndroidTvRemoteSrvcUuid(p1), bVar1 == false)) &&
     (bVar1 = this.isLeAudioSrvcUuid(p1), bVar1 == false)) {
    bVar1 = false;
  }
  else {
    bVar1 = true;
  }
  return bVar1;
}

Later (after DefCon) in October 2024, CVE-2024-9956 is published with a wonderful writeup fullowing it in February 2025.

TLDR An attacker within bluetooth range is able to trigger navigation to a FIDO:/ URI from an attacker controlled page on a mobile browser, allowing them to initiate a legitimate PassKeys authentication intent which will be received on the attacker’s device. This results in the attacker being able to “phish” PassKeys credentials, completely breaking this assumption that PassKeys are impossible to phish.

I’m not aware of any examples of this attack taking place (attributing PassKey hijacking to this vulnerability is nearly impossible anyways), but given the time span and DefCon’s thriving sticker culture, it’s probably sane to say you should have used a bit more caution in 2024. 😳

Exhibit B through (…)

I’m getting tired and there’s a lot of surface area to cover. Stop reading here if you believe me already, otherwise prepare for an infodump.

In 2022, iOS had a bug that would permanently disable WiFi if a network containing the %s format string in it was added as a network. Vaguely difficult to do manually, but here’s a QR code you can just scan on vulnerable devices.

WiFi DoS

In the same exact way that the web Notifications API is commonly abused to spam ads even when an app is in the background, you can similarly use the vEVENT QR code format to embed a recurring event directly in the calendar app.

Using a QR code to add a “hidden” WiFi network allows you to track an unlocked phone as fast as you can scan a QR code. “Hidden” WiFi networks do not beacon, so they do not show up in scans. To connect to one, the client (the phone) beacons that it’s looking to connect to the WiFi network. If you add a hidden WiFi network with SSID “THIS_IS_JOHN_DOE” to an unlocked phone, the phone OS will ensure it will beacon out clearly on the 2.4Ghz radio spectrum for eternity stating “THIS_IS_JOHN_DOE” until manually removed.

eSIM? Needs more research, by someone who isn’t me.

AppClip codes have even more reach in functionality.

There’s all kinds of fun UI bugs you can trigger with QR codes to hide what handler they will/did trigger.

There’s a ton more intents worthy of a deep review review in System/Library/PrivateFrameworks/WorkflowKit.framework/ICApps.plist on iOS.

Sharing is caring!