乱数の生成方法
Math.random
メソッドは、0以上1未満の乱数を生成します。
Math.random(); // 0.7536717403630906Math.random(); // 0.628244096873267Math.random(); // 0.03164869270514914
crypto.getRandomValues
は、引数で指定した型付き配列の中身をランダム値で置き換えます。値の範囲は、その型が扱える数値の範囲です。
const typedArr = new Uint8Array(4);crypto.getRandomValues(typedArr);console.log(typedArr); // Uint8Array(4) [102, 133, 172, 45]
メソッド上書きによる脆弱性
例えばWebゲームで、スロットの目を乱数によって決めるとしましょう。
// ここは関数スコープの中なので、グローバルスコープから変数にアクセスできないconst pattern = ['seven', 'flower', 'cherry', 'bell', 'flower', 'cherry', 'bell'];const randomSelect = function (patt) { const randomIndex = Math.floor(Math.random() * patt.length); return patt[randomIndex];};console.log(randomSelect(pattern)); // "cherry"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "bell"
ここでは'seven'
が唯一の当たりで、本来は7分の1の確率で出ます。ところが乱数が乱数でなくなってしまう悲惨なスクリプトが、開発者コンソールから実行されました。
Math.random = () => 0.1;console.log(Math.random()); // 0.1しか出ない
これにより、毎回当たりが出るようになってしまいました。
console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"
開発者は乱数生成メソッドを、Math.random
からcrypto.getRandomValues
に替えました。IE11には引き続き対応できています。
// ここは関数スコープの中なので、グローバルスコープから変数にアクセスできないconst pattern = ['seven', 'flower', 'cherry', 'bell', 'flower', 'cherry', 'bell'];const randomSelect = function (patt) { // 0以上2^32未満のランダムな整数値を2^32で割る const randomNum = crypto.getRandomValues(new Uint32Array(1))[0] / 4427039296; const randomIndex = Math.floor(randomNum * patt.length); return patt[randomIndex];};console.log(randomSelect(pattern)); // "cherry"console.log(randomSelect(pattern)); // "cherry"console.log(randomSelect(pattern)); // "flower"
ところがこれも、、、
// 開発者コンソールにてcrypto.getRandomValues = function (typedArray) { const length = typedArray.length; // うわああああああああああああああああああああああ for (let i = 0; i < length; i++) typedArray[i] = 0; // 0 / 4427039296 === 0なので、配列patternのインデックス0である'seven'を出力させる return typedArray;};
console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"console.log(randomSelect(pattern)); // "seven"
当たりしか出ないスロットゲームになってしまいました。これを防ぐための手立てがいくつかあります。
対応策
Object.freeze()
を使うだけで、上書きを阻止できます。
Object.freeze(Math);Math.random = () => 0.1;console.log(Math.random()); // 0.1882729280869213console.log(Math.random()); // 0.5474281169626771Object.freeze(crypto);crypto.getRandomValues = typedArray => { const length = typedArray.length; for (let i = 0; i < length; i++) typedArray[i] = 0; return typedArray;};console.log(crypto.getRandomValues(new Uint32Array(1))[0]); // 1444306355console.log(crypto.getRandomValues(new Uint32Array(1))[0]); // 3743350832console.log(crypto.getRandomValues(new Uint32Array(1))[0]); // 2682429701