QR Chaos

by Edward Miro (a.k.a. c1ph0r)

Introduction

Malicious QR codes are not a new concept.  They're built into the Social Engineering Toolkit (SET) and QRLJacking is going to be seen more and more with the ever growing use of "login with QR Code" on IoT devices, mobile apps, and Smart TVs.

Despite the inherent risk, the convenience of QR codes seems to be winning that proverbial struggle with security.

I live and teach cybersecurity for a community college in California and I was curious how easy it would be to get people in my town to scan QR codes in a completely unsolicited way.  And in a way anyone can try themselves.

In the following article, I will present a write-up of my methodology and hopefully deliver it in a way to make it repeatable, interesting and informative.

Preparation

My plan for this experiment was to generate a trackable batch of QR codes with no text:

For this experiment, I will seek to establish a baseline by using unsolicited blank codes.  It seems intuitive that coupling phrases such as "Free Beer!", "Scan Me!", "Hot Singles!", etc. would naturally increase the scan rate and hopefully this article will inspire further study and repeat experiments in this area.

I used Python3 to generate a batch of 100 ten-character random strings using only uppercase and lowercase alphabetic characters:

# python3
# qr.py
import string
import random
N = 10
for i in range (100):
    qr = ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase, k = N))
    print(qr, end = "\n")
    #print()

Saved that as qr.py, chmod +x, then run:

$ python3 qr.py > out.txt

Looking at out.txt, we see that it created a list of strings:

$ cat out.txt
...
LaQAULXkir
GIMSZmUwst
xtguldmsKF
kyYJdEDolc
...

Now we run:

$ sed -e 's#^#https://mirolabs.info/qrchaos.php?loc=#' out.txt > out2.txt

This will take each of our 100 random strings and prefix a URL to a very simple web page with a basic PHP script to create a log of interactions:

$ cat out2.txt
...
https://mirolabs.info/qrchaos.php?loc=LaQAULXkir
https://mirolabs.info/qrchaos.php?loc=GIMSZmUwst
https://mirolabs.info/qrchaos.php?loc=xtguldmsKF
https://mirolabs.info/qrchaos.php?loc=kyYJdEDolc
...

The PHP code utilized on the back-end was:

<?php
$ip = $_SERVER['REMOTE_ADDR']; $browser = $_SERVER['HTTP_USER_AGENT'];
$referrer = $_SERVER['HTTP_REFERER'];
$date = new DateTime('now');
$timestamp = $date->format('Y-m-d\TH:i:s.u');

if ($referrer == "")
	$referrer = "Location(http://{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']})";
	error_log("$timestamp\nVisitor IP address: $ip\nBrowser: $browser\nReferrer: $referrer\n\n", 3, "2510522188994354");
?>

Nothing too crazy here.  Just collecting the time stamp, device's IP address, device's browser, and the GET variable which is loc = (one of our random strings).

I pasted them into qrexplore.com/generate and then printed them in sheets with the filename added:

Now I have 100 QR codes that I cut into stacks of ten, and precut the filename so whoever placed the code can tear it off.  That way, later on the details of placement can be logged and then future scans will tell us which locations were successful.

I provided all my volunteers a shared Google Sheet that they could use to log placements:

Execution

I personally placed 30 codes, mostly on campus and a few other public parks and public places.  I passed out the remaining seven ten-packs to students in the computer science club on campus and to a few close friends.  I encouraged them to place them in publicly accessible areas and didn't give them too much guidance.  In hindsight, I'd be more specific due to some of my codes ending up on a WalMart shelf strip.

Of the remaining 70, only ten were logged on my sheet, and I found later on that I didn't make the logging necessity and procedure as clear as I should have to many of my volunteers.  If you repeat or expand this experiment with your students or cybersecurity club, I recommend walking all the participants through the logging process and explaining the reasoning behind it.

That being said, the experiment immediately lost any scientific integrity because it's not really possible for us to know just how many were placed and where.  I'm happy to downgrade this article to a proof-of-concept in the spirit of accuracy.

I let the experiment run from November 1, 2019 to February 1, 2020 for a total of 92 days.  My original plan was to let it run until it had been 30 days since the last scan, expecting it to only last a month or so due to codes being lost or removed through natural process.

However, I kept getting scans all the way until January 31, 2020, so clearly my hypothesis that they would eventually be removed organically through maintenance or user interaction was not accurate.  Indeed, some of them are still out there and I removed the PHP script from the page and also added this message:

Results

Total logs: 16
Unique codes scanned: 7
Top performers:
poUw4fqXRG x 4
JEML2YzIWN x 3
MBxNRuigUK x 2
uZ77BL6nAl x 2
brpoOaYIOW x 2
HOedSV4fkm x 1
QvKBMyPMSt x 1
Scan x 1

For the curious, those location variables map to the following locations:

poUw4fqXRG= ButteCollegeMain/garden/gate
JEML2Yz1WN= ButteCollegeMain/MediaCenter/bulletin-board
MBxNRuigUK= BidwellParkTrailhead/bulletin-board
uZ77BL6nA1= ButteCollegeMain/PhysSciBldg/bulletin-board-1
brpoOaYI0W= ButteCollegeMain/PhysSc iBldg/bulletin-board-2
HOed5V4fkm= ButteCollegeChico/1stFloor/bulletin-boar d
QvKBMyPMSt= ChicoStateMain/GlennHall/bulletin-board
scan= [unknown]

Other Stats

Full Log

2019-11-01T17:58:40.756004
Visitor IP address: [REDACTED]
Browser: Mozilla/5.0 (Linux; Android 9; SAMSUNG SM-T380) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/10.1 Chrome/71.0.3578.99 Safari/537.36
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=poUw4fqXRG)

2019-11-02T17:19:45.275127
Visitor IP address: [REDACTED]
Browser: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=poUw4fqXRG)

2019-11-02T17:22:04.550677
Visitor IP address: [REDACTED]
Browser: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=poUw4fqXRG)

2019-11-02T20:59:51.173048
Visitor IP address: [REDACTED]
Browser: facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=poUw4fqXRG)

2019-11-12T22:27:43.615346
Visitor IP address: [REDACTED]
Browser: Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Snapchat/10.69.5.72 (iPhone12,1; iOS 13.1.3; gzip)
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=HOed5V4fkm )

2019-11-14T03:15:40.643039
Visitor IP address: [REDACTED]
Browser: Mozilla/5.0 (iPhone; CPU iPhone OS 13_1_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148
Referrer: Location(http://mirolabs.info/qrchaos.php?loc=MBxNRuigUK)

....

Conclusion

Of the 38 QR codes that were placed and logged, we had a 42 percent success rate.

This data supports my initial hypothesis that random people will scan an unsolicited and unmarked QR code in the wild.

Obviously, if an attacker used this vector and directed users' devices to phishing pages, malware, etc., we can expect this to have a high rate of success.

Ideas For Higher Scan Rates

I would predict a sufficiently motivated attacker could have a major impact.

Attacks could also be geographically targeted in a way that differs from other "-ishing" vectors.

Recommendations

The QR code attack vector will require utilizing some of the same techniques used in detecting attacks such as phishing:

Special Thanks: Butte College Computer Science Club students, Gary Adams, Kevin Johnson, NM.  If you have any questions or want to contact me: Edward Miro (a.k.a. c1ph0r, c1ph0r.github.io, @c1ph0r, c1ph0r@protonmail.com.

Code: qr.py

Return to $2600 Index