読者です 読者をやめる 読者になる 読者になる

C#でFirebaseを使ってみよう!(5) APIキーが攻撃された場合を考えてみる

あすかです。

前回は、Databaseでファイヤー!しました。

kmycode.hatenablog.jp

今回は、ファイヤー!APIキーとセキュリティについて、ちょっと考えてみます。

前提条件

今回は、ファイヤー!のAPIキーが攻撃者に漏れているという前提でお話します。
そもそもファイヤー!は、REST APIだけでなく、JavaScript向けのSDKも提供されています。
ということは、APIキーもJavaScriptの中に埋め込むことができるということです。

ええ、JavaScriptなんて使わないしC#だけで十分っしょ、という方が読者の中には多いと思います。
でも、もしかしたらexeファイルからAPIキーを抜く手段があるんじゃないか、と勝手に心配しています。



どうやってC#プログラムの実行ファイルからAPIキーを抜くのか

2つ、方法が考えられます。

exeの中身から抜く方法

私もあまり詳しくないのですが、C#コンパイルしたコードは、ILという中間言語に変換されて、exeファイルの中に入ります。
このILというのが、アセンブリを擬似的に再現したコードです。JVMを想像してもらうとやりやすいです。
PE/COFFを勉強されたことのある方なら、感覚的に理解できると思います。

簡単のため、このようなコンソールアプリケーションを、Releaseでビルドして、exeファイルを作ります。
できた実行ファイルを、バイナリエディタで開いてみます。

f:id:kmynews:20170305111608p:plain

MZというシグネチャWindowsの前身であるMS-DOS向けのコードが見えるあたり、exeファイル自体はWindowsネイティブアプリとして出力されています。
codeセクションはおそらくこのあたりですね、最初のところは無論Intelコードでしょうけど、ここからILを動かすための処理が始まっているはずです。

f:id:kmynews:20170305111749p:plain

私も数年前にちょっと勉強しただけでほとんど詳しくないのですが、Windowsのexeファイルというのは、セクションというものに分かれています。
MASMでいう、codeセクション、dataセクション、data?セクションとかがそれですね。
C#が吐いたexeファイルもWindowsネイティブアプリの体裁を持っている以上その例外ではありません。

codeセクションがIntelコードをまとめて置く場所ならば、dataセクションは、Intelコードの実行に必要なリテラルの置き場所です。
そうしないと(codeとdataを混ぜてしまうと)Intelの1つあたりの命令の長さ(バイト数)を命令ごとに固定の値にできませんからね。
私が何をいいたいかといいますと、ILでもこのdataセクションのような場所がある可能性が高いということです。

f:id:kmynews:20170305112339p:plain

実行時型情報を生成する時の助けになるであろうクラス名やメソッド名の定義群の最後にやっぱりありました、プログラムに書いていたAPIキー。
(ちなみにそれぞれの文字が1バイトずつ離れているのは、C#の文字列が内部でワイド文字列として扱われているからです)

ファイヤー!APIキーをプログラムの中に通常のリテラルとして埋め込んでいたら、たとえコードが1万行だろうが10万行だろうが、exeファイルのどこかに必ずあります。
骨の折れる作業でしょうし、攻撃者がアプリ制作者によほどの恨みを持っていない限り抜かれることはないと思いますが、やっぱり抜かれる時は抜かれます。断定します。

メモリの中を覗き見する方法

もう1つの方法ですが、exeファイルの中に暗号化した状態で埋め込んでも無駄だということをお話したいです。
さっきのexeファイルを起動してみます。
ここで、メモリの中身を覗き見することができるアプリを使ってみます。うさみみハリケーンです。

f:id:kmynews:20170305113646p:plain

画像あげましたが、見たい場所はここではありません。
アプリを実行する時、exeの中身をいったんメモリに書き出します。上の画像はその部分ですので、みなさんが自分で探す時は注意してください。
本当に見たい場所は、以下になります。

f:id:kmynews:20170305113247p:plain

ヒープ領域に確保されたメモリ上に、APIキーが書き出されているのがわかりますか。
C#のstringはclassであり、ヒープ領域に確保されます。(structは御存知の通りスタック領域です)
ゆえに、ヒープ領域を漁れば、必ず出てくるはずです。exeファイルの中身を見る時と同様、かなり根性のいる作業ではありますが。
むろん、C#コードの中に暗号化した状態で埋め込んで、実行時に復号したとしても、その結果がメモリに残っていれば無意味です。

ついでに言うと、Androidはroot化、iOSは脱獄すれば、それぞれapkとipaが読めます。
ここまでいくとさすがに被害妄想かもしれませんが。

ということで、ここからはAPIキーが抜かれているという前提でお話します。
(10万する高価なグラフィックソフトと違って、個人制作の無料アプリから抜く手間に対するメリットはあまりないかもしれないし、やや非現実的な話かもしれませんが、ここではそういうのは考えないことにします。
 そもそも本物の人は、これのほかにデバッガというものを併用します。長めのコードなら数日はかかるでしょうが、比較的楽にAPIキーを抜き出せると思います)

攻撃のやり方を考えてみる

認証した状態でデータベース・ストレージにアクセスする

JavaScriptAPIから認証を行う時は、URIをあらかじめファイヤー!コンソールに登録しなければならないことになっています。
でも、REST APIではそういうのは必要ありません。

他のユーザで認証するということはさすがに難しいですが、自分で作成したユーザになら、いくらでもアクセスし放題ですね。

そして、ファイヤー!のデフォルトのセキュリティ設定では、「認証した人であれば、データベースのどこにでもアクセスできるぴょん!」となっています。
ストレージも同様です。
これでは、攻撃者にAPIキーを抜かれたら、攻撃者は各ユーザのデータを覗き見し放題ですね。

ファイヤー!側でこれを制限する方法があります。
認証の設定(ルール)に、Uidというものを使えばいいんです。

ファイヤー!では、各ユーザにUidというものが割り当てられています。
今ログインしているユーザと、データベースのキー名やストレージのフォルダ名を照らし合わせればいいんです。
詳しくはこちらへどうぞ。「ユーザー」の節が大変参考になります。

qiita.com

結論から言うと、適切なセキュリティルールを設定していれば、攻撃者はたとえAPIキーを持っていても既存の情報にアクセスすることは不可能です。

攻撃者が作ったユーザアカウントから、ひたすら大量のデータをうpしまくる

これ、本当に厄介です。
ルールで特定のフォルダ名しかアクセス出来ないようにしても、攻撃者はそんなの知ったことではありません。自分で解析してしまえばいいだけの話です。
つまるところ、攻撃者は攻撃用のアカウントを新たにファイヤー!内に作って、データベースやストレージにどんどん新しいデータを置き放題です。
それがどんなに大きなデータや、日本・アメリカの法律に違反した画像(例:ミッキー)であってもです。

こうなると、コンプライアンスの他にも、クラウドの利用料金が心配になります。
1人のユーザが大量に使ってしまうと、ファイヤー!の無料枠が極端に圧迫され、他のユーザが不利益を被ります。
ファイヤー!にはBLAZEというプランがあって、従量課金を柔軟にすることができるのですが、BLAZEにしてしまうと、攻撃者によって大量のお金を支払わなければいけません。
しかも、攻撃者はAPIキーを抜いているので、C#のアプリ経由ではなく、APIキーから直接ファイヤー!します。制限することができません。
サービスの存続が難しくなります。
つまり、サービスを潰すことを目的とした攻撃を防ぐ手段はありません。

AzureではAPIキーの再発行とかができるのでそれで対応できるかもしれませんが、あいにくファイヤー!にはそれがないです。
金とともに燃え尽きろということでしょうか。

対策

理想は、自前のREST APIを提供するサーバを、自分自身で構築することです。
APIキーをこのサーバの中に入れていれば、攻撃者がファイヤー!APIキーを知る手段はなくなります。
そのかわり、攻撃者はそのサーバを攻撃することになります。

意図しない場所へのアクセス、アプリ専用形式データと偽った画像のアップロード、極端に大きなデータのアップロードや大量の操作命令はプログラムレベルで防げるかもしれませんが、DOS・SYNCフラットなどのサイバー攻撃は、それ相応の知識がないとだめです。

サイバー攻撃にも耐えられるサーバを、となったら、例えばMicrosoft Azureは、Azure Functionsというサービスを提供しています。これを使う手があります。サーバが攻撃されてもMSのせいにできますし。
また、Azure Functionsはnugetを利用することができます。ファイヤー!のnugetパッケージも利用できるということです。
ファイヤー!をプログラムから柔軟に触ることができないのと、ファイヤー!へのアクセスに中継が入るから遅くなるのを勘案しても、いいんじゃないかと思います。
でも、Azure Functionsも有償です。Azure Functionsは、機能の呼び出し回数が月100万回を超えると課金‥‥

対策できない(´・ω・`)

こういうことは一度考え出すとキリがないです。
攻撃者にはいくらでも攻撃する方法というのはありますし、それ全部防ごうとすると、大きな代償が返ってくるかもしれません。

あすかは自分で作ったアプリにファイヤー!導入を考えていましたが、根本的な解決策が見つからないため、いまだに導入を躊躇している感じです。
そもそも個人制作で完全無料のアプリのバイナリからAPIキーを抜くという事案自体が現実に起こる蓋然性があるかどうかは、議論しなければいけないかもしれないですが。

どこかで妥協しなければいけないところは、あると思います。