generated at
canvasにCORS許可なく他のオリジンから読み込んだ画像を描画することで画像のダウンロードを防ぐ

Mallory 画像をグリッド状に分割して難読化したサイトの画像ダウンロード方法は2種類ある
1. 難読化した画像はダウンロードできるから直接解く
画像が送られてきているのだから、パズルのように解いてしまえばいい。でもちょっと面倒くさいナ
Aliceこれをやられたらお手上げだよぉ。何読化は解かれない前提
2. 何読化された画像がデコードされたらその画像をいただク♠
Aliceこっちをなるべく防ぎたいなぁ
...そうだ!
Alice canvasCORS汚染するためにCORSで許可していないimage(1x1pxの小さな透明な画像)をimgタグで読み込んでしまおう
sample.ts
const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); if (ctx === null) { throw new Error('Unable to get 2D context.'); } // 画像Aを読み込む const imgA = new Image(); imgA.src = 'http://example1.com/imageA.jpg'; // 例:ユーザーに見せたい画像。これ自体は難読化されている imgA.onload = () => { ctx.drawImage(imgA, 0, 0); // 画像Bを読み込む const imgB = new Image(); imgB.src = 'http://example2.com/imageB.jpg'; // 例:透明な画像 imgB.onload = () => { ctx.drawImage(imgB, 50, 50); // この時点でcanvasは「汚染」された try { const dataUrl = canvas.toDataURL(); // エラー! } catch (err) { console.error(err); } }; };
元の画像をimgタグで埋め込むとDev toolでimageがわかってしまうのでなるべくバレないようにしよう
Mallory document.querySelector("canvas").toDataURL("image/jpeg") でダウンロードできなイ
1. 画像をアップロードしたら画像を返すサーバーを立てる
2. クライアントからimgA, imgBを1のサーバーにおくる
3. クライアントでdrawImageしなおした上で document.querySelector("canvas").toDataURL("image/jpeg")
をやるのは面倒ダ


Alice.iconが行った対策の原理

canvasを汚染するとcanvasの画像を書き出せなくなる仕様がある
>CORS による許可なしに他のoriginから読み込んだ何らかのデータをキャンバスに描画すると、キャンバスは汚染 (taint) されてしまいます。汚染されたキャンバスは安全とみなされなくなり、そのキャンバスから画像データを取得しようとすると、例外が発生するでしょう。
>汚染されたキャンバスで以下の呼び出しを行うと、エラーが発生します。
> キャンバスのコンテキストに対する getImageData() (en-US) の呼び出し
> toBlob()、toDataURL()、captureStream() の <canvas> 要素自身に対する呼び出し
これを逆手にとって、canvasを意図的に汚染することによってスクレイピングを防ぐ
>@a4lg: 循環論法ではあるのですが、実際の所、大手の配信サイトがこの手法をコピー防止に採用しているのを実際に見たことがあります。
>そのサイトでは割と巧妙な画像難読化と併用されてたので、頑張るものだなとは思いました。
少年ジャンプ+が実装している

やり方
CORSで許可していないimageをimgタグで読み込む
ここで画像を配信しなければいけなくなるが、その画像は画像をグリッド状に分割して難読化しておく
これはdevtoolではimageとしては表示されない
content-typeはimage/jpeg
canvasにimgタグから画像を入れる

ただしchrome dev toolでcapture node screenshotをすることでcanvasの画像全体を保存できてしまう