バイブコーディング(AI)で作ったアプリ、動けばいいってもんじゃないって話
私は大学2年生の1月、バイブコーディング(AIにコードを書かせること)の破壊力を知り、早速Claudeを使って自作のアプリを作った。アプリの作成自体は破竹の勢いで終了し、Appleの審査も問題なく通過した。一見UI、UXも問題ないし、ユーザーも着実に増えていった。正直、その瞬間だけはこれから技術的な見識はAIに丸投げでいいんじゃないかとすら考えた。しかし、世の中はそんなに甘くなかった。
その後、とある記事を目にした。WIREDによると、AIツールで作られた5,000件以上のアプリが、認証もセキュリティもほぼ無い状態で公開されており、そのうち約40%が医療情報や個人情報などの機密データを実際に露出していたという。その見出しを見た時、ゾッとした。リリースすることに頭がいっぱいになっていて、セキュリティの視点が自分には全くなかったのだ。詳しく調べると、リリースしたアプリのバイナリを解析したり、JavaScriptを使うことで簡単に情報は取れてしまうとのことだった。もし、自分の機密情報や利用しているユーザーの個人情報が悪意のある第三者に渡ってしまったらどうしよう。私は何よりも優先して、自作アプリのセキュリティについて勉強し、対策を施した。この記事では、私がどんな対策を具体的に行ったかを書く。誰でも制作者になれる時代だからこそ、知らなかったでは済まないことなので、ぜひ読んでほしい。
UIで制限しても、セキュリティにはならない
私は今回アプリを作るにあたって、Flutter + Firebaseを利用した。Firebaseの利用にあたって最も気をつけないといけないことは、Firestoreルールの設定だ。ここで、私が見落としていた重大な視点がある。
アプリの中では「ログインした人」「課金した人」しかできない操作になっていても、それはアプリの画面上の話でしかない。Firestoreルールの設定が甘いと、技術のある人間にとっては絶好の狩場になってしまう。なぜこんなことが起きるのか。攻撃者はアプリの画面を使う必要がないからだ。アプリを介さず、データベースに直接「他人のデータをくれ」とリクエストを送れてしまう。導入で触れた「バイナリ解析」も、この直接アクセスの入口を見つけるための手段だ。
例えるなら、アプリ内の操作はお店の看板だと思ってほしい。看板に「会員のみ」と書いてあっても、施錠や対策をしていなければ、悪意があり技術のある人なら容易に内部へ突入できてしまう。この施錠や対策を管理するのが、Firestoreのセキュリティルールというイメージだ。
実際に自分のアプリを攻撃してみた
では、Firestoreルールの何を見ればいいのか。この記事では、実際に自分のアプリでチェックし、見つかった穴を攻撃して実証し、修正するまでを書いていく。
まずAIに書いてもらったFirestoreルールを確認してみた。アプリ内では指定された二人のみが読める状態だったので問題なしと思っていた。しかしFirestoreルールでは、ログインしていれば誰でも見れる状態になっていた。つまりアプリ内では問題なくても、とある手順を踏めば誰でも内容を見れてしまう設定になっていた。具体的には以下の状態であった。
match /chatRooms/{chatRoomId}/messages/{messageId} {
allow read: if request.auth != null; // ← ログインしてれば誰でも読める(ガチでやばい)
}
そこで、実際に自分のアプリを攻撃して情報を盗んでみた。ここで一つ留意してほしいことがある。それは、本番そのものではなく、本番と全く同じ状況を再現した環境を攻撃するということだ。本番を攻撃してしまうと、現在動いているアプリを破壊する可能性や、実際に利用しているユーザーの情報を漏洩させる恐れがある。そのため、仮想の環境を作ることが大切になる。今回はFirebase Emulatorを使って攻撃を再現した。具体的には下のコードを使った。
// 当事者ではない alice が、他人同士のチャットを読もうとする
test('部外者が他人の会話を読めてしまう', async () => {
await assertSucceeds(
getDoc(doc(alice(), 'chatRooms/room1/messages/msg1'))
);
});
その結果、案の定
✓ 部外者が他人の会話を読めてしまう (47 ms)
と、抜き取ることに成功してしまった。こうも簡単に情報を抜き取れてしまうのかと、かなり戦慄した。
どう対策したか
ここからが最も大切。どうやって対策していくか。単純に「ログインしているか」だけのチェックではなく、当事者であるかまでチェックさせていく。具体的には以下。
match /chatRooms/{chatRoomId}/messages/{messageId} {
// ログインしているか、ではなく
// 「自分がこのチャットの当事者か」を確認する
allow read: if request.auth != null
&& isParticipant(chatRoomId); // ← 当事者チェックを追加
}
その結果
✓ 部外者は他人の会話を読めない (39 ms)
と返ってきた。これで一安心。ただ、ここで満足してはいけないのが味噌だ。今度は「弾きすぎていないか」のチェックを忘れてはいけない。正当なユーザーまで弾いて満足度を下げてしまっては、本末転倒である。その後、当事者ならば問題なく読めることを確認し、一件落着というわけだ。
まとめ
この記事で話したのはreadの話だけだったが、これは一部分に過ぎない。他にも様々な対策を行ったが、全て話すと辞書くらいの分量になるため割愛する。
バイブコーディングは「AIの提案を高速に採用する」作業だ。そしてAIは「機能が動くこと」を優先して、CRUDやAPIを一気に作りがちで、操作ごとの認可チェックを抜かしやすい。今回の話で言うと、「ログインしているか(認証)」と「その操作をする権限があるか(認可)」は全くの別物だ、ということ。request.auth != null は前者しか見ていない。AIはこの「認可」のチェックを抜かすことが多い。だからこそ、最後は我々作り手が自分で確認する必要がある。誰でも作れる時代になったからこそ、制作者それぞれが意識を上げていかなくてはいけない。
AIで作った制作物のセキュリティについて、これからも書いていきます。感想や「うちのアプリも気になる」みたいな話があれば、気軽に声をかけてください。
Discussion