メモリ不足からリモートコード実行
YukiChen(@guhe120)Qihoo360VulcanTeam
CVE-2014-0290,CVE-2014-0321,CVE-2014-1753,CVE-2014-1769,CVE-2014-1782,CVE-2014-1804,CVE-2014-2768,CVE-2014-2802,CVE-2014-2803,CVE-2014-2824,CVE-2014-4057,CVE-2014-4092,CVE-2014-4091,CVE-2014-4095,CVE-2014-4096,CVE-2014-4097,CVE-2014-4082,CVE-2014-4105,CVE-2014-4129,CVE-2014-6369,CVE-2015-0029,CVE-2015-1745,CVE-2015-1743,CVE-2015-3134,CVE-2015-3135,CVE-2015-4431,CVE-2015-5552,CVE-2015-5553,CVE-2015-5559,CVE-2015-6682,CVE-2015-7635,CVE-2015-7636,CVE-2015-7637,CVE-2015-7638,CVE-2015-7639,CVE-2015-7640,CVE-2015-7641,CVE-2015-7642,CVE-2015-7643,CVE-2015-8454,CVE-2015-8059,CVE-2015-8058,CVE-2015-8055,CVE-2015-8057,CVE-2015-8056,CVE-2015-8061,CVE-2015-8067,CVE-2015-8066,CVE-2015-8062,CVE-2015-8068,CVE-2015-8064,CVE-2015-8065,CVE-2015-8063,CVE-2015-8405,CVE-2015-8404,CVE-2015-8402,CVE-2015-8403,CVE-2015-8071,CVE-2015-8401,CVE-2015-8406,CVE-2015-8069,CVE-2015-8070,CVE-2015-8440,CVE-2015-8409,CVE-2015-8047,CVE-2015-8455,CVE-2015-8045,CVE-2015-8441,CVE-2016-0980,CVE-2016-1015,CVE-2016-1016,CVE-2016-1017,CVE-2016-4120,CVE-2016-4160,CVE-2016-4161,CVE-2016-4162,CVE-2016-4163,CVE-2016-4185CVE-2016-4249,CVE-2016-4180,CVE-2016-4181,CVE-2016-4183,CVE-2016-4184,CVE-2016-4185,CVE-2016-4186,CVE-2016-4187,CVE-2016-4233,CVE-2016-4234,CVE-2016-4235,CVE-2016-4236,CVE-2016-4237,CVE-2016-4238,CVE-2016-4239,CVE-2016-4240,CVE-2016-4241,CVE-2016-4242,CVE-2016-4243,CVE-2016-4244,CVE-2016-4245,CVE-2016-4246,CVE-2016-4182,CVE-2016-3375,CVE-2017-3001,CVE-2017-3002,CVE-2017-3003,CVE-2017-0238,CVE-2017-0236,CVE-2017-8549,CVE-2017-8619
自己紹介バウンティハンター@360VulcanTeam
自己紹介
ハードコアなアニメ漫画ゲーム オタク
360VulcanTeamについて
ü Qihu360の セキュリティリサーチャー
ü Pwn2Ownの実績:ü Pwn2Own2015IE11ü Pwn2Own2016Google
Chrome,AdobeFlashü Pwn2Own2017Edge,Safari,
AdobeFlash,Win10,MacOSXü “MasterofPwn”Pwn2Own
2017
アジェンダ
• ブラウザのメモリ不足(OOM)例外• メモリ不足からリモートコード実行(RCE)• メモリ不足からASLRのバイパス• まとめ
Webブラウザのメモリ不足(OOM)例外
メモリ不足(Outofmemory)例外“十分なメモリが無い場合に発生する
ランタイム例外” �
ブラウザOOMの例1• IE8CAZrArrayの解放済みメモリの使用(use-a[er-free)(cve-2014-1753)• ファジングにより発見
function f() { var size = 0x8000000; var s = "AAAA"; while (s.length < (size - 2)/2) s += s;
var x = document.createElement("frame"); x.setAttribute("prop2", s.substring(0, (size - 2)/2)); var node = document.body.appendChild(x);
for ( var y = 0; y < 20; ++ y ) { var z = node.cloneNode(); document.body.appendChild(z); }
}
CloneNode
属性をひとつずつ複製
新しい属性配列の作成
属性複製失敗? �
属性配列の解放
属性配列にアクセス(UAF:use-a[er-free)
コピーする属性が非常に大きい文字列である場合、その属性を複製するとメモリ不足を発生させることができる。最終的に、解放済みメモリの使用(use-a+er-free)が発生する �
ブラウザOOMの例2• IEJscirptJSON.parseOOMメモリ破損
var chunksize = 0x2000000; var json = '[' for ( var i = 0; i < chunksize/0x10; ++ i ) json += '1,' json += '1]'; var arr = new Array(); try {
for ( var i = 0; i < 0x1000; ++ i ) arr.push(json.substr(0, (chunksize-2)/2));
} catch (e) {} //大量のメモリを割り当てることにより IEを低メモリ状態に強制する
while (true) { JSON.parse(json); } �
jscript!JSONParser::ParseObject+0x3fb: 65b4e9da mov eax,dword ptr [esi+14h] // length of the json array 65b4e9dd mov dword ptr [esp+14h],eax 65b4e9e1 shl eax,4 // alloc size = arr_length * 0x10 65b4e9e4 push eax 65b4e9e5 mov dword ptr [esp+20h],eax 65b4e9e9 call dword ptr [jscript!_imp__malloc (65b740fc)] // もしメモリが十分でない場合、mallocを失敗できる// mallocの失敗をチェックしていないかコンテンツを直接アドレスにコピーする [NULL + arr_size] 65b4ea2fa5movsdwordptres:[edi],dwordptr[esi]es:002b:00777fc0=????????ds:002b:03eb8398=00000003
OOMバグ –異なるタイプ
• 対処済み/未対処• 制御可能/制御不可能• 32ビット/64ビット• 継続可能/継続不可能
対処済み/対処無し
• 対処されたOOM– 開発者はコード内にOMMの可能性を意識している– しかし正しく対処されていない(例えば、IECAZrArrayUAFのケース)
• 対処されていないOOM– 開発者はコード内にOMMの可能性を意識しておらず
ノーアイデア(例えば、JSONのケース)– 予期しない実行パスの変更(アーリーリターン、例外)
を引き起こし、悪用可能な状態を引き起こすことができる
制御可能/制御不可能
• 制御可能– いつでもOOMの例外を確実に発生できる場合
• 大きな割り当て(largeallocadon)の制御• 低メモリ(low-memory)状態の制御
• 制御不可能– ランダムに発生するため制御できない場合
• 小さな割り当て(Smallallocadons)• 制御できない低メモリ(low-memory)状態
32ビット/64ビット
• 通常、信頼性の高いOOMを見つけるのは64ビットよりも32ビットをターゲットにする方が簡単
• 32ビットのターゲットに対して、プロセスを強制的に低メモリ状態にする方が簡単だから– ブルートフォースアロケーションを使う
継続可能/継続不可能
• 継続可能– プログラムがOOMの後も実行を継続できる場合– エクスプロイト可能
• 継続不可能– プログラムがOOMの後に実行を継続できない場合
• 悪用できないメモリ破損のために即クラッシュしてしまう(例えば、nullポインタデリファレンス)
• 失敗しない割り当てでもクラッシュしてしまう• ブラウザのメモリ制限により、メモリの制限を超えた場合にクラッ
シュしてしまう
– エクスプロイトできない、DOSのみ L
制御可能または継続可能なOOM例外のみ見つける/フォーカスすること
バグハンターへ:
OOMのバグの探し方
• 普通のファジング– ランダム値によるファジングで時々OOMが発生する
ことがある
• 低メモリ状態のファジング– ApplicadonVerifierのようなツール– アロケーションAPIのフック– 一部のブラウザにはメモリ不足のシミュレーション用
のテストインターフェースがある(例えば、FireFox)
• コードの監査
メモリ不足(OOM)からリモートコード実行(RCE)
Microso[EdgeのOOMバグを発見するまで
• Edgeの制御可能なOOMを見つける• 不整合な配列の状態を作るためにトランザク
ション処理をブレイクする• メモリ破損状態を作り出す• 成功
Edgeの制御可能なOOMを見つける
• OOM例外を確実に発生させる必要がある
• JavaScript配列セグメントの割り当ては良い着眼点
配列セグメント
• IE/EdgeのJavaScript配列– 配列セグメントのリンクリスト
長さ
ヘッド
配列
ヘッドセグメント
レフト(le[)
長さ(length)
サイズ(size)
次の位置(next)
要素(element)[0]
要素(element)[N]
…
セグメント1
レフト(le[)
長さ(length)
サイズ(size)
次の位置(next)
要素(element)[0]
要素(element)[N]
…
セグメントN…
vararr=newArray();arr[0]=1;arr[1]=2;arr[2000]=3;arr[5000]=4;
ヘッドセグメント
arr
le#=0length=2element[0]=1element[1]=2
セグメント1
セグメント2
le#=2000length=1element[0]=3
le#=5000length=1element[0]=4
配列セグメント(.cont)
• JavaScript配列の要素はセグメントに格納される– 配列セグメントを割り当てるときに、配列内の要
素にメモリ空間が割り当てられる
• 新しい要素を配列に追加するときに既存の領域が無い場合は、新しいセグメントを割り当てるか既存のセグメントを拡大する
32ビット版Edgedでの配列セグメントの割り当てによるOOM
• 配列セグメントを割り当てるとき、十分なメモリが無い場合は、OOM例外をスローする
try{for(vari=0x10000000;i<0x18000000;++i)
a[i]=0x0d0d0d0d;}catch(e){}try{
while(true){arr_ab.push(newArrayBuffer(0x02c9dbec*4));//Step1:大容量
のメモリを割り当てることにより、ブラウザを低メモリ状態にする}
}catch(e){}try{
a.reverse();//Step2:配列セグメントの割り当てによって OMM例外をスローする
}catch(e){alert(e);}
32ビット版EdgeでのOOMの発生–例
32ビット版EdgeでのOOMの発生–例(.cont)
64ビット版Edgeでの配列セグメントの割り当てによるOOM
• 64ビット版Edgeを強制的にメモリ不足にすることはできない– 64ビットプロセスのメモリ空間が大きいため
• 64ビット版EdgeでOOM例外を発生させるチャンスはまだ残っている– もしアロケーションサイズがオーバーフローした
場合
アロケーションサイズオーバーフロー時のOOM
Varaaa=newArray();For(varI=0;I<arrSize.length;++i){
aaa.unshi[.apply(aaa,args);//Step1:大体オーバーフローするまで配列セグメントのサイズを大きくする
aaa[arrSize[i]–1]=1;} try{
aaa.unshi[.apply(aaa,args);//Step2:サイズがオーバーフローしそうな場所で別のセグメントサイズを増やす}catch(e){//Error:OutofMemory}
64ビット版EdgeでのOOMの発生–例
64ビット版EdgeでのOOMの発生–例(.cont)
これで32ビット/64ビット版のEdgeで制御可能なOOMができた。次は?
配列トランザクションの操作
• 配列にはいくつかのインポートフィールドがある– 配列:配列タイプ、長さ、ヘッド、キャッシュされた最後
に使われたセグメント– セグメント:レフト、長さ、サイズ、要素
• フィールドの1つを変更する場合、配列を有効に保つため他のフィールドも変更する必要がある– 例えば、int配列をfloat配列に変換するとき、セグメン
トのint要素もfloatに変更する必要がある
配列トランザクション操作(.cont)
• 多くのJavaScript配列のAPIは配列を変更する– shi[、unshi[、splice…– そのようなコードのコア部分では原子性と一貫性
が求められる– データベースのトランザクションと同じように、配
列トランザクション操作と呼んでいる– 配列トランザクション操作の中断は問題を引き起
こすことができる
配列トランザクション操作–例
• NadveInt配列からNadveFloat配列への変換
配列内のForeachセグメント:segment->ChangeElementsToFloat()
SetArrayType(NaCveFloatArray)
もし繰り返しの途中でコードが予期せず返ってきた場合、いくつかのFloatセグメントを持つNaHveIntArrayを取得する
配列トランザクション操作の中断
• トランザクション操作のコールバック– Edgeのバグの共通パターン
• コードのフローを中断する例外– メモリ不足(OOM)例外J
レッツ パーティ!
リモートコード実行のケース:Array.unshi[
nElemntsToUnshiG=UnshiGする要素の数配列のForeachセグメント:
segment->leG+=nElemntsToUnshiG (1)SetUnshiGElementsToHeadSegment(); (2)Array->length+=nElemntsToUnshiG; (3)
Array.unshi[のトランザクション操作
nElemntsToUnshiG=UnshiGする要素の数配列のForeachセグメント:
segment->leG+=nElemntsToUnshiG (1)SetUnshiGElementsToHeadSegment(); (2)Array->length+=nElemntsToUnshiG; (3)
中断�
要素をヘッドセグメントに設定すると、ヘッドセグメントの再割り当てが発生し、メモリ不足例外をスローすることができる �
リモートコード実行のケース:Array.unshi[
• Array.unshi[トランザクション操作を中断することで– 一貫性の無い配列ができる:
array->lastSegment->le[+array->lastSegment->length
>array->length
– 配列の操作コードと思い込ませる:array->lastSegment->le[+array->lastSegment->length
<=array->length
リモートコード実行Case:Array.unshi[
• 一貫性のない配列に対してArray.unshi[を再度呼び出す:arr->lastSegment->length>arr->lastSegment->size
• 長さがサイズよりも大きい配列セグメントは、さまざまな場所でヒープオーバーフローを引き起こすことができる– JavaScriptArray::DirectSetElementAtとか
リモートコード実行のケース:Array.unshi[
• 配列セグメントのヒープオーバーフローは比較的簡単にエクスプロイトできる
• オーバーフローされるセグメントの後に別の配列セグメントを割り当てるだけで、次のセグメントの長さとサイズを上書きすることができる
レフト 長さ サイズ
オーバーフロー
0xffffffff 0xffffffff
オーバーフローされるセグメント
次のセグメント
リモートコード実行のケース:Array.unshi[
• Pwn2Own2017でEdgeを攻略するために使用• CVE-2017-0238として修正
デモ
リモートコード実行のケース:JavascriptNadveIntArray::ToNadveFloatArray
• この関数は、int配列をfloat配列に変換する
配列のForeachセグメント:seg->size>>=1 (1)if(seg->length>(seg->size>>=1)) seg=AllocateNewSegment (2) Seg->ChangeElementToFloat() (3)Adjust(seg->length) (4)
JavascriptNadveIntArray::ToNadveFloatArrayのトランザクション操作
配列のForeachセグメント:seg->size>>=1 (1)if(seg->length>(seg->size>>=1)) seg=AllocateNewSegment (2) Seg->ChangeElementToFloat() (3)Adjust(seg->length) (4)
中断�
新しい配列セグメントを割り当てると、メモリ不足例外(OOM)をスローすることができる。この時点で、seg->sizeは2で除算されるが、seg->lengthは変更されない �
• ToNadveFloatArrayトランザクション操作を中断することで– 配列セグメントができるSeg->length>Seg->size
• Array.unshi[と同じような方法でリモートコード実行が
できる– 2017年3月のPwn2Own2017に挑んだときに予備として
持っていたバグの一つ
– 我々はバックアップとして複数の同じようなバグを準備していた(例えば、 Array.splice)
リモートコード実行のケース:JavascriptNadveIntArray::ToNadveFloatArray
パッチの時間–4月修正
• このPwn2OwnのバグをMicroso[は4月に修正
• この修正に少し驚いた– OOMの例外は修正されていなかった– 代わりに我々が使ったエクスプロイトテクニックを
防ごうとしていた• 新しい関数“CheckLengthVsSize”の追加• “segment->length>segment->size”によって引き起こさ
れるヒープオーバーフローを回避するため
CheckLengthVsSizeもし“segment->length>segment->size”を検出した場合、すぐにプロセスをクラッシュさせる
この4月の修正の問題
• これにより我々のいくつかのOOMのエクスプロイトを防ぐことができる
• しかし根本原因はまだ修正されていない– 根本原因:OOM例外は配列トランザクション操作
を中断させる
• また、他のOOMの脆弱性は、 “seg->length>seg->size”を悪用する必要が無い
パーティーを続けよう
OOMのバグであと10か月は戦える
OOMバグはあと10ヶ月は戦える!
リモートコード実行のケース:Array.reverseセグメント
解放済みメモリの使用(UseA[erFree)
セグメントリスト全体を反転させる (1)ヘッドがリーフセグメントであれば:
head=ReallocateNonLeafSegment (2)
Array.reverseのトランザクション操作
リーフセグメント
• リーフセグメント– 純粋なデータセグメント– GCの次のセグメントはスキャンされない
• 非リーフセグメント– GCの次のセグメントはスキャンされる
非リーフ 非リーフ リーフ
リーフ 非リーフ 非リーフ
ü
X最後の2つのセグメントはGCによってスキャンされずに予期せず解放されて、usea[erfreeの原因となる
リモートコード実行のケース:Array.reverseセグメント
解放済みメモリの使用(UseA[erFree)
非リーフ
Step1:セグメントリストを逆転する
Step2:リーフセグメントの場合はヘッドを再割り当て
非リーフ リーフ
リーフ 非リーフ 非リーフ
非リーフ 非リーフ 非リーフ
中断�
非リーフセグメントを再割り当てすると、メモリ不足(OOM)の例外を引き起こす
リモートコード実行のケース:Array.reverseセグメント
解放済みメモリの使用(UseA[erFree)• Array.reverseのトランザクション操作の中断に
より、 すでに解放されている配列セグメントにアクセスできる
• 完全なリモートコード実行が簡単にできる– 配列セグメントの解放されたメモリを再利用– 偽の配列セグメントを取得(OOMへのアクセスの
実現、型の取り違え、…)
リモートコード実行のケース:Array.reverseセグメント
解放済みメモリの使用(UseA[erFree)
• 6月のCPUでCVE-2017-8549として修正
• Edgeのバグバウンティで$15,000ゲット– Microso[に感謝 J
リモートコード実行のケース:ConvertToVarArray
バッファオーバーフロー• JavascriptNadveFloatArray::ConvertToVarArrayのバッファオーバーフロー
配列のForeachセグメント:segがリーフセグメントであれば: seg=ReallocateNonLeafSegment()(1)
seg->size*=2 (2)seg>ChangeIntElementsToVar() (3)
Array->ChangeTypeToVarArray() (4)
Seg->size*=2?
• 32ビット版Edge、sizeof(Var)=4,sizeof(Float)=8• したがって、floatセグメントをvarセグメントに変換
する場合、セグメントの容量(サイズ)を2倍にすることができる
Float Float
var var var var
配列のForeachセグメント:segがリーフセグメントであれば: seg=ReallocateNonLeafSegment()(1)
seg->size*=2 (2)seg>ChangeIntElementsToVar() (3)
Array->ChangeTypeToVarArray() (4)
中断�
非リーフセグメントを再割り当てすると、メモリ不足の例外(OOM)が発生することがある。繰り返しの途中(1)で壊れた場合、一部のセグメントのサイズは倍になっているが、倍になったサイズは元に戻されない �
リモートコード実行の場合:ConvertToVarArrayの型の取り違え
• JavascriptNadveFloatArray::ConvertToVarArrayのトランザクション操作の中断により– いくつかの倍のサイズのセグメントを持つfloatの
配列ができる
• セグメントの境界を直接読み書きできる– あとはエクスプロイトを完成させるだけ
パッチの時間(再び)
• ようやくMicroso[は根本原因の修正を開始– おそらく、我々がPwn2Ownの後もOOMのバグを
報告し続けたからだろう
• 修正内容– 特定の関数でOOMの例外を検出した場合にプロ
セスをクラッシュさせる
AutoDisableInterrupt• コード領域を保護するためのクラス• 保護されたコード領域で例外をスローされる
とプロセスをクラッシュさせる• 根本原因は解決– unshi[、splice、配列変換などの多くのインポート
機能が追加された…– たぶん、いくつかの関数を忘れてる?
リモートコード実行のケース:Array.reverse(再び)
• AutoDisableInterruptのパッチの後も– Array.reverseは保護されなかった
• CVE-2017-8619でOOMセグメントUAFの問題は修正されたと報告
• その後、別のOOMの問題を同じ関数で発見– CVE-2017-8753– 無効なlastUsedSegmentによって型の取り違えを
引き起こされる
lastUsedSegment
• JavaScript配列は最後に使用された配列セグメントをキャッシュし、配列アクセスのスピードアップをしている
• したがって、セグメントが配列から削除されると、lastUsedSegmentも更新される必要がある。そうでない場合、問題が引き起こされる
リモートコード実行のケース:Array.reverse(再び)
lastUsedSegment=head; (1)head=AllocateNewHead(); (2)最後のセグメントがリーフでない場合:
ReallocateLastSegmentToNonLeaf();(3)lastUsedSegment=head; (4)
中断�
非リーフセグメントを再割り当てすると、メモリ不足(OOM)の例外が発生することがある。もし(3)で中断した場合、lastUsedSegmentは無効なセグメントを指している �
無効なlastUsedSegmentの悪用
• このようなバグの悪用方法について我々のチームメンバ@LiuLongに感謝
• 悪用方法– 配列の型を変更(例えば、int配列->float配列)– 配列の型の変更ではlastUsedSegmentは更新さ
れない– その後でlastUsedSegmentの要素にアクセスする
と、型の取り違えができる
まだ終わらない…
• CVE-2017-8753が9月に修正• EdgeのOOMの悪用は終わり?• さあ確認してみようJ• たぶん、デモの時間
メモリ不足(OOM)からASLRのバイパス
64ビット版Edgeのメモリを使い切る?
• 通常はできない– メモリを使い切る前にブラウザ(およびシステム
全体)が遅くなったりフリーズする– あまりにも多くのメモリをコミットしてしまうため
• 64ビットブラウザの興味深い機能が見つからない限り
高速配列バッファ
• 64ビット版Edgeでは、サイズが0x10000(64KB)より大きい配列バッファを割り当てると、それは“高速配列バッファ(fastarraybuffer)”となる– ab=newArrayBuffer(0x10000);//仮想の配列
バッファを作成
• Edgeは高速配列バッファごとに0x100000000(4GB)バイトを予約する
バッファごとに4GB?
どういう意味か
• 64KBの高速配列バッファが割り当てられたとき、実際にコミットされるメモリサイズは64KB
• しかし、Edgeは4GBの仮想メモリ空間を確保する
• したがって、64KBのメモリをコミットするだけで4GBのメモリを占有することができる
64KBのコミット、4GBの確保
64ビット版Edgeのユーザーモードのメモリ空間
• Windows10はユーザーモードのメモリとして48ビットを使用
• ユーザーヒープアドレスは常に0x800000000000以下
• したがって、ユーザーモードのメモリ空間を使い切るには、0x8000未満の配列バッファをスプレーする必要がある
64ビット版Edgeのメモリを使い切るための5行
vararr=newArray(0x10000/2);try{ for(i=0;i<arr.length;i++) arr[i]=newArrayBuffer(0x10000);}catch(e){//outofmemoryexcepdon}
スプレーの最後に
rcx=0000000800000000rcx=0000000700000000rcx=0000000600000000rcx=0000000500000000rcx=0000000400000000rcx=0000000300000000rcx=0000000200000000rcx=0000000100000000
驚いたことに、ほとんどの配列バッファのスプレーが終わると、予測可能なアドレスにメモリを割り当てはじめる
固定のアドレスで固定のコンテンツを取得
• スプレーが終了した後、高速配列バッファの1つが固定のアドレス(例えば、0x200000000)に割り当てられる
• その配列バッファを解放して、興味深いオブジェクト(例えば、JavaScript配列)をスプレーすると、これらのオブジェクトがその固定のアドレスに割り当てられることが分かり、我々はASLRをバイパスすることができた
0x10000000
0x20000000
0x30000000
…
高速配列バッファ
高速配列バッファ
高速配列バッファ
高速配列バッファ
高速配列バッファ
0
オブジェクトのスプレー
デモ
効果
• 64ビット版Edgeで、制御できるコンテンツを制御できるアドレスに配置できる
• 特定のバグの悪用を容易にする– 任意の場所への書き込み(Write-to-anyonce)– 解放済みメモリの使用(Usea[erfree)– 型の取り違え(Typeconfusion)
制限
• スプレー終了までに時間がかかる– ~300秒、私のラップトップでは
• 実際の攻撃には適していない• Pwn2Ownのようなコンテストで使うには良い
オプションJ
ASLRのバイパスの先に
• 64ビットのメモリ空間を使い切った後に、32ビットプロセスと同様に制御可能なOOMができる
• 32ビットプロセスのみで悪用可能なOOMの脆弱性は、この問題と組み合わせると64ビットでも悪用できる
まとめ
• ブラウザのメモリ不足(Out-of-Memory)の例外は、よく 開発者/バグハンターから無視されていた
• 最新のブラウザで制御可能なものに焦点を当てれば、悪用可能なものを見つけることはまだ可能
• OOMの問題を真剣に受け止める必要がある
ありがとうございました!