こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

SECCON 2018 Quals - QRChecker

問題文

QR Checker
http://qrchecker.pwn.seccon.jp/

f:id:graneed:20181028022425p:plain

ソースコードが公開されている。

#!/usr/bin/env python3
import sys, io, cgi, os
from PIL import Image
import zbarlight
print("Content-Type: text/html")
print("")
codes = set()
sizes = [500, 250, 100, 50]
print('<html><body>')
print('<form action="' + os.path.basename(__file__) + '" method="post" enctype="multipart/form-data">')
print('<input type="file" name="uploadFile"/>')
print('<input type="submit" value="submit"/>')
print('</form>')
print('<pre>')
try:
    form = cgi.FieldStorage()
    data = form["uploadFile"].file.read(1024 * 256)
    image= Image.open(io.BytesIO(data))
    for sz in sizes:
        image = image.resize((sz, sz))
        result= zbarlight.scan_codes('qrcode', image)
        if result == None:
            break
        if 1 < len(result):
            break
        codes.add(result[0])
    for c in sorted(list(codes)):
        print(c.decode())
    if 1 < len(codes):
        print("SECCON{" + open("flag").read().rstrip() + "}")
except:
    pass
print('</pre>')
print('</body></html>')

writeup

毎年お馴染みらしい、QRコードの問題。

ソースコードを読み解くと、1つのQRコード画像を500x500、250x250、100x100、50x50にリサイズし、2種類以上の読み取り結果が得られたらフラグをゲットできるようだ。

大きな画像を縮小すると、画像が粗くなったりつぶれたりするので、その現象を使用するのだろうか。

ここで、18年6月くらいにQRコード脆弱性という記事を読んだことを思い出す。

『きまぐれQRコード』ができます!?【続報】気を付けろ!QRコードに脆弱性?(森井昌克) - 個人 - Yahoo!ニュース

QRコードの誤り訂正の領域に、グレーのセルがある。このセルが白と判定される場合と黒と判定される場合とで、読み取り結果が変わる。

こちらの記事のQRコード画像を拝借して読み取らせてみる。
まずは、ローカルで実行可能な環境を用意するため、ライブラリをインストール。

root@kali:~# sudo apt-get install libzbar-dev
root@kali:~# pip install zbarlight

ローカル実行するためのコードはこちら。

#!/usr/bin/env python3
import sys, io, cgi, os
from PIL import Image
import zbarlight
codes = set()
sizes = [500, 250, 100, 50]

f = open(sys.argv[1], 'rb')
data = f.read(1024 * 256)
image= Image.open(io.BytesIO(data))
for sz in sizes:
    image = image.resize((sz, sz))
    #image.save('qr_{}.png'.format(str(sz)))
    result= zbarlight.scan_codes('qrcode', image)
    if result == None:
        break
    if 1 < len(result):
        break
    print(sz)
    print(result[0])
    codes.add(result[0])
for c in sorted(list(codes)):
    print(c.decode())
if 1 < len(codes):
    print("SECCON{SUCCESS!!}")

QRコード画像
f:id:graneed:20181028023931p:plain

実行結果

root@kali:~# python qr.py qr_org.png
500
b'http://srv.prof-morii.net/~lab'
250
b'http://srv.prof-morii.net/~lab'
100
b'http://srv.prof-morii.net/~lab'
50
b'http://srv.prof-morii.net/~lab'
http://srv.prof-morii.net/~lab

そのままでは1種類の読み取り結果しか得られなかった。
そこで、画像サイズを変えたり、グレーの部分をGIMPで適当に塗ったりして調整し、Try&Errorしてみる。

最終的に以下の画像で成功した。

f:id:graneed:20181028024353p:plain

root@kali:~# python qr.py qr_success.png
500
b'http://srv.prof-morii.net/~lob'
250
b'http://srv.prof-morii.net/~lob'
100
b'http://srv.prof-morii.net/~lob'
50
b'http://srv.prof-morii.net/~lab'
http://srv.prof-morii.net/~lab
http://srv.prof-morii.net/~lob
SECCON{SUCCESS!!}

画像をアップロードする。

f:id:graneed:20181028024534p:plain

フラグゲット
SECCON{50d7bc7542b5837a7c5b94cf2446b848}