脆弱性"&'<<>\ Advent Calendar 2016の19日目の記事です。
吉里吉里2/KAG3のセーブデータに危険なスクリプトが含まれていないかをチェックするツールを作った。
吉里吉里2/KAG3とは
NScripterのようなノベルゲームエンジン。今一番使われているんじゃないかと思う。
「吉里吉里製ゲーム」という言い方を良くされるけれど、吉里吉里2自体はより汎用的で、ノベルゲーム専用のエンジンというわけではない。吉里吉里2はTJS2というスクリプト言語の実行環境で、その上でKAG3というノベルゲームエンジンが動いている。
脆弱性(?)の詳細
下記のサイトに書いてあるとおり。
楓 software: 吉里吉里2/Zに脆弱性があると言う話
KAG3のセーブデータの読み込み処理は以下の部分。loadBookMarkFromFile
とcopyBookMark
にも同様の処理がある。
function loadSystemVariables()
{
// システム変数の読み込み
try
{
var fn = saveDataLocation + "/" + dataName +
"sc.ksd";
if(Storages.isExistentStorage(fn))
{
scflags = Scripts.evalStorage(fn);
scflags = %[] if scflags === void;
}
else
{
scflags = %[];
}
var fn = saveDataLocation + "/" + dataName +
"su.ksd";
if(Storages.isExistentStorage(fn))
{
sflags = Scripts.evalStorage(fn);
sflags = %[] if sflags === void;
}
else
{
sflags = %[];
}
}
catch(e)
{
throw new Exception("システム変数データを読み込めないか、"
"あるいはシステム変数データが壊れています(" + e.message + ")");
}
}
Scripts.evalStorageは引数のファイルをTJS2式として評価するメソッドなので、ここに危険なスクリプトが書かれていればそのまま動く。
これは脆弱性なのだろうか?
ほとんどのゲームでは、ユーザーが外部からセーブデーターを持ってくるという使い方を想定していないはず。KAG3には、セーブデータを「ファイルを保存する」ダイアログで保存する機能や、セーブデータをサムネイルのBMPに保存する機能があるけれど、使っているゲームを見たことがない。弄ることを想定していないデータを弄ってスクリプトが動いても知らんがな( ´・ω・)という気はする。吉里吉里2/KAG3を使ってゲームを作る人が、吉里吉里2本体を修正することはあまり無いと思うけど、KAG3を編集することは良くあるはずで、この部分を見ている人にとってはevalされるのは想定された仕様かもしれない。
一方で、ユーザーにしてみれば、ゲームのセーブデータを開くというのはテキストエディタでテキストファイルを開く感覚で、危険なスクリプトが含まれていた場合にそのまま実行されるとは思わないのかもしれない。実際はスクリプトが動くのに、ユーザーはスクリプトが動かないと思っている認識の齟齬がある状況は危険。例えばpatch.xp3(KAG3にはpatch.xp3の中にファイルがあれば優先して読むというパッチ当てのための機能がある)ならば、ユーザーもスクリプトが動くと想像するだろうから、問題無さそう。
IPAには脆弱性として受理された。情報非開示依頼は取り下げ済み。
チェックツールについて
ゲームの一ユーザーとしては、昔のゲームのシーンをふと見たくなったときに、どこからかセーブデータを持ってきて使いたい。他にもそういうことをしたい人はいると思い、チェックするツールを作った。
怪しげなセーブデータをチェックするのに、怪しげなツールをOS上で動かすのでは意味が無いので、JavaScritpでブラウザ上で動くようにした。今どきのブラウザならファイルのドラッグ&ドロップもできるし、数十MB程度のファイルなら余裕で処理できるし、OS上で動くツールとたいして使い勝手は変わらなかった。感動。
Bootstrapは見飽きた → Material-UIがかっこいい → Reactというのが必要らしい → npmというのでインストールするらしい
という流れで、今風な感じの環境で作った。npmに置いておけば、WindowdでもMacでもLinuxでもnpm install -g <name>
でインストールして、そのまま動かせるというのも便利。一応、このツールのCLI版もkrkrsvchk
という名前で置いてある。
Reactの部分はこれ。各コンポーネントはデータの変化などを考えずに、渡されたデータをただレンダリングすれば良いというのは楽。でも、ファイルを受け取って結果を表示するだけのシンプルなページには、ちょっと堅すぎる気もする。
チェックの本体は↓のスクリプト。セーブデータをパースしながら読み込み、データ以外のものが含まれていないかを調べている。ついでに、保存されたKAG3のマクロも弾くようにした(マクロがセーブデータに保存されうることはConfig.~new
内のドキュメントに記載がある)。
セーブデータに独自の暗号化を施しているゲームもありそうで、個々のゲームには対応しきれないけれど、素の吉里吉里2/KAG3には対応したい。普通に保存したセーブデータでエラーになったり、逆にこのチェックをすり抜ける方法があったりしたら教えてほしい。