QRコードに注意 — クイッシング対策。開く前に確かめる方法

QRコード詐欺と事前確認ツール — タビLab. アイキャッチ タビLab.(技術ブログ)

QRコードは、人の目では何が記載されているか読めません。文字で書かれたURLなら、開く前に「このドメインは知っているか」「短縮URLではないか」と目で判断できます。QRコードはその判断を飛ばして、読み取った瞬間にリンクになります。この一点が、攻撃に使われる理由のほぼすべてです。前半では、その手口と、開く前に確かめるべき条件を整理します。後半では、QRコードを開く前に読み取るツールを作成してみます。(ツールだけ使いたい方はこちら)このツールは配布資料のPDFにQRコードしか載っていないときにも使えます。

QRコードが狙われる理由 — URLを人が確認できない

2024年8月、フィッシング対策協議会が「QRコードから誘導するフィッシング」として緊急情報を出しました。クレジットカード会社をかたる「【重要】不正利用監視通知」という件名のメールに、リンクの代わりにQRコードが貼られている。読み取ると偽のログイン画面に誘導され、会員番号、パスワード、カード番号、有効期限、セキュリティコードを入力させようとする。行き先には短縮URLや、見慣れない .buzz や .top といったドメインが使われていました。

攻撃する側から見ると、QRコードには三つの都合の良さがあります。一つ目は、メールのリンク検査をすり抜けやすいこと。文字のURLは機械的に検査されますが、画像の中のURLは見落とされやすい。二つ目は、読み取る端末がスマホに限られること。小さな画面ではURLの細かな違いに気づきにくく、PCで開いていた資料からスマホへ、確認の弱い経路に誘導できる。三つ目は、そもそも人が行き先を読まないこと。読み取った直後にカメラが出す小さな通知を、多くの人はそのままタップします。この手口はQRコードとフィッシングを合わせて「クイッシング」と呼ばれています。

紙の世界でも同じです。駐車場の精算機や飲食店のメニューに貼られたQRコードの上に、別のQRコードのシールを重ねる手口が海外で報告されています。読み取った先の画面は本物そっくりに作られているので、画面を見て見抜くのは難しい。見抜けるとしたら、開く前の「行き先」の段階です。

開く前に確かめる条件 — 判定をコードにする

人が目で見て怪しいと感じる条件は、そのまま判定関数に書けます。短縮URLのドメイン(bit.ly、t.co、tinyurl.com、x.gd、ur0.jp など。正規のサービスも使うので「URLが隠れている」という注意に留めます)、暗号化されていない http://、ドメイン名ではなくIPアドレスの直書き、URLに @ が含まれる(@より前は本当の行き先ではありません)、そして xn-- で始まる国際化ドメイン(見た目の似た別サイトに使われることがあります)。

function checkUrl(u){
  var host; try{ host=new URL(u).hostname; }catch(e){ return ['アドレスの形が正しくない']; }
  var warn=[];
  if(/^http:\/\//i.test(u)) warn.push('暗号化されていない http://');
  if(isShortener(host)) warn.push('短縮URL(本当のURLが隠れている)');
  if(host.indexOf('xn--')>=0) warn.push('国際化ドメイン(見た目の似た別サイトの可能性)');
  if(/^\d+\.\d+\.\d+\.\d+$/.test(host)) warn.push('ドメイン名でなくIPアドレス');
  if(/@/.test(u.split('?')[0].replace(/^https?:\/\//,''))) warn.push('@ を含む(@より前は行き先ではない)');
  return warn;
}

大事なのは、この判定を「ページを開く前」に走らせて、結果を人が確認することです。判定が黒でも白でも、最後に開くかどうかを決めるのは人で、コードは材料を並べるだけ。短縮URLに注意が出たからといって必ず危険というわけではないので、止めるのではなく見せる、という線引きにしています。

そういえば、配布資料のPDFにQRコードしかないとき

また、セミナーの配布資料や案内のPDFに、申し込み先がQRコードでしか載っていないことがあります。PCの画面で見ているので、そのままでは読めません。スマホを取り出して画面にかざせば読めますが、開くのはスマホの側で、いま作業しているPCには届かない。モニターのモアレで読めないこともあります。

そこで、QRコードの部分をスクリーンショットで撮って貼り付けると読める、というツールを作りました。Windowsは Win+Shift+S、Macは Cmd+Shift+4(Ctrlを添えるとクリップボードに入ります)でスクリーンショットを撮って、下の枠をクリックしてから Ctrl+V(Macは Cmd+V)。読み取った中身は文字で表示し、URLなら前半の判定をかけて、行き先のドメインと注意を先に見せます。開くのは「新しいタブで開く」を押したときだけです。画像も読み取った内容も、ブラウザの外には出しません。

▼ 以下でQRコードの読み取りができますスクリーンショットを貼り付けるだけ。読み取った内容は開かずに、URLと注意点を先に表示します。
ここをクリックしてから、Ctrl+V(Macは Cmd+V)で貼り付け
ドラッグ&ドロップや、下の「ファイルを選択」でも読み込めます。
スクショの撮り方:Windowsは Win+Shift+S、Macは Cmd+Shift+4(Ctrlを添えるとクリップボードへ)
対応:PNG・JPEG・WebP・GIF(1枚の画像に複数のQRがあってもOK)

読み取りは、すべてこのブラウザの中で行います。画像も読み取った内容も、サーバーには送りません。

ツールの実装 — BarcodeDetector と jsQR の二段構え

読み取りには二つの経路を用意しました。一つはブラウザに備わっている BarcodeDetector API です。Chrome と Edge(バージョン83以降)で使え、1枚の画像に複数のQRコードがあってもまとめて返してくれます。ただし対応は環境依存で、Safari は既定で無効、Firefox は未対応です(2026年9月時点、MDN と Can I use の記載による)。Chrome でも、OSによっては結果が空になることがあります。

そのため、BarcodeDetector が無いか、あっても結果が空だったときは、jsQR(MIT ライセンスの純JavaScript実装)を jsDelivr からその場で読み込んで使います。最初から jsQR を読み込まないのは、対応環境では約250KBのスクリプトが不要だからです。

async function detectAll(bitmap){
  if('BarcodeDetector' in window){
    try{
      var det=new BarcodeDetector({formats:['qr_code']});
      var list=await det.detect(bitmap);   // 複数のQRをまとめて返す
      if(list.length) return list.map(b=>b.rawValue);
    }catch(e){ /* 対応していない環境ではここに落ちる */ }
  }
  var jsQR=await loadJsQR();            // 無ければ jsQR をその場で読み込む
  return detectWithJsQR(jsQR,bitmap);
}

小さなQRコードが取れない — タイル分割で解決

jsQR で一つ引っかかりました。QRコードだけを撮った画像は問題なく読めるのに、資料のページごと撮った画像(罫線や文字の中に小さなQRコードが2つある)を渡すと、何も見つからない。画像全体に対して1回だけ位置検出をかける作りなので、周囲の画像ノイズに邪魔されるようです。

対処として、画像を400ピクセル角のタイルに切り、200ピクセルずつずらしながら jsQR をかけていきます。タイルの中ではQRコードが相対的に大きくなり、周囲のノイズも減るので、同じ画像から2つとも取れました。同じ内容を重複して拾うので、読み取った文字列で重複を除きます。

function scanTiles(jsQR,ctx,W,H){
  var T=400, S=200, found=[], seen={};
  for(var y=0;y<H;y+=S){
    for(var x=0;x<W;x+=S){
      var w=Math.min(T,W-x), h=Math.min(T,H-y);
      if(w<120||h<120) continue;
      var img=ctx.getImageData(x,y,w,h);
      var r=jsQR(img.data,w,h,{inversionAttempts:'dontInvert'});
      if(r&&r.data&&!seen[r.data]){ seen[r.data]=true; found.push(r.data); }
    }
  }
  return found;
}

順番は、まず画像全体で試し、駄目ならタイル走査、それでも駄目なら縮小してもう一度、としています。手元の試験では、QRコード単体、資料ページ、Wi‑Fi接続用、連絡先(vCard)のいずれも読めました。

勝手に開かない設計 — 前半の条件をそのまま組み込む

ツールを作るとき、いちばん簡単なのは「読めたら即座に開く」ことです。ですが前半で見たとおり、QRコードの弱点は行き先を人が確認しないことにあります。ツール側で、行き先のドメインを太字で示し、判定の結果を注意として並べ、開くボタンを人に押させる。それだけで、前半の手口の多くは入口で止まります。

道具の使い方としては、「読んだら、URLを見て、それから開く」の順番を守るだけです。判定に引っかかったときは、そのQRコードからは開かず、公式アプリやブックマークから入り直せば同じ手続きができます。

まとめ

QRコードはURLの見えないリンクで、その見えなさがフィッシングに使われています。開く前に確かめる条件は、短縮URL・http・IPアドレス直書き・@の混入・国際化ドメインの五つで、コードにすれば十数行です。PCの画面のQRコードは、スクリーンショットを貼れば BarcodeDetector か jsQR で読めます。小さなQRコードはタイル分割で取れます。ツールに「開く前に見せる」一手間を足すと、道具そのものが対策になります。

出典・参考:フィッシング対策協議会「QR コードから誘導するフィッシング」(2024年8月28日)トレンドマイクロ「QRコードを悪用した詐欺手口と対策を解説」(2022年9月28日)MDN「Barcode Detection API」Can I use「BarcodeDetector API」jsQR(cozmo・MITライセンス)(いずれも2026年9月7日時点)

タイトルとURLをコピーしました