ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Appの保護:脅威のモデリングとアンチパターン
脆弱性や潜在的な脅威について考慮し、お持ちのAppのどこに安全対策をすべきかを認識することは、かつてないほど重要になっています。脅威のモデリングを通じて潜在的リスクを見つけ、共通のアンチパターンを回避する方法を理解しましょう。リスクを最小化し、あなたのAppを使用中のユーザーを保護するために、暗号化のテクニック、そしてプラットフォームによる保護を最大限に活用する方法を学びます。
リソース
関連ビデオ
WWDC20
-
ダウンロード
こんにちは WWDCへようこそ “アプリケーションの保護: 脅威のモデリングとアンチパターン” リチャード・クーパーです このセッションでは― ユーザーから託されたデータや リソースを保護しながら― アプリケーションを更に安全に 守る方法についてお話します Appleで大事なのは― アプリケーションがデータを正しく処理する事 ユーザーのプライバシーを尊重する事 そしてその努力が損なわれないよう ソフトウェアが― 攻撃に対して堅牢に 設計されている事です 私のチームはApple内のソフトウェアチームと ハードウェアチームと協力し― 開発中の製品の 安全性を向上させ― 既に完成した製品の問題を見つけられるよう 支援しています
いくつか役に立つテクニックを 紹介します このトークでは アプリケーションの コンテキストでセキュリティについて どのように考える事ができるかについて いくつかのアイデアを取り上げます
そのためここでは threat modelingや 信頼性のないデータの判別方法について そして犯しがちな一般的な間違いを お話しします 問題を分かりやすくするために― Loose Leafと呼ばれるシンプルな 架空のアプリケーションを使います Loose Leafには大きな期待を寄せていて― 熱いお茶 冷たいお茶 タピオカティーの どれを好む人にとっても― 主要なソーシャルプラットフォームになる事を 期待しています もしかするとコーヒー派にさえ…
それを目指して この素晴らしい機能をサポートします 私たちはソーシャルネットワーキングで あなたの活動を世界で共有したいのです カメラを使って写真を撮り 投稿するだけでなく― ライブアクションのビデオを 作る事もできます また 友人とだけ話す事ができる― プライベートなティークラブも サポートしたいと思っています 最後に ロング形式のレビュー ペアリングなどを― サポートする機能を付けます これはとても単純な例ですが― 一般のアプリケーションに共通する 多くの機能を備えています アプリケーションに注目した時 最初にしたい事は― 一歩下がって threat model について考える事です Threat modeling は セキュリティのコンテキストで― どんな問題が起こり得るかを考えるプロセスです どのようなリスクに対面するか それをどう軽減するかを― 考慮する事が重要です 特定の正式なプロセスは説明しません 多くのデベロッパにとって― それだけを学ぶ事は 現実的でないからです
その代わり threat modelingに使用でき セキュリティとリスクについて考えるのに役立つ いくつかの戦略を説明します どうやるのか? アプリケーションが持つ重要なものの1つは アセットです
Xcodeのアセットカタログで見つかるような アセットではなく ユーザーが価値があり機密であり プライベートであるとみなすものです カメラとマイク 一データ 連絡先情報 写真やファイル これらはアセットのいい例です 私たちが過去数年にわたり 懸命に働いてきた理由は これらの多くに プラットフォームレベルの 保護を提供するためです 一般的なルールとして― ユーザーがアクセスを許可する必要がある場合 それはアセットです
そうする事で 制御はあなたにゆだねられ― 保護する方法を考えるのは あなたの責任になります しかし他にもアセットを 表すものがあります
ユーザーがアプリケーションに 入力する意見やビューは― 非常に重要なアセットです プラットフォームの保護は役に立ちますが― それは特に注意して保護が 適用されている事を― 確認する必要があるアセットのクラスです さて 攻撃者がいなければ Threat modelは完成しません
これは明らかな攻撃者の例は ユーザーから盗む犯罪者や あなたのプラットフォームを 弱体化したい競合他社です ところがアクセスレベルが異なる可能性のある 他のクラスの攻撃者についても 検討の必要があります 恋人や家族がその例です
これらの攻撃者からどうやって身を守りますか?
これは重要です なぜなら物理的にも共有アカウントを介しても デバイスへのアクセスレベルが 大きく異なるからです 攻撃者がLoose Leafに興味を 持つ事はないと思うかもしれませんが― 便利なアセットにアクセスでき 意図したとおりに成功すれば― ターゲットにもなり得るのです 攻撃者は 定期的にソーシャルメディアや 銀行系 生産系 出会い系アプリケーション― 更にはゲームにまで攻撃するため 誰も免疫を持ちません
一般的に攻撃者が誰であるかより― 彼らがどんな能力を持ち それがどう影響してくるかの方が― より重要です 攻撃者がどのデータをコントロールでき アプリケーションのどこに行くか それを視覚化するために データフローダイアグラムをお勧めします まずシステムのデータシステムの 場所を引き出します
私たちの場合はウェブサイトでホストされている 情報がいくつかあります それを公開投稿や アカウント情報などに使用します
情報の一部はCloudKitにも 保存されているため― 分散型ティークラブや ローカルバージョンの―
投稿や画像を保存する キャッシュを作成したりできます 最終的にこれらは― ユーザーの機密情報を 保持する場所になります
次に 活用するシステムリソースを 追加する必要があります またデータをアプリケーションに取り込む― 他の方法を 考える必要があります
私たちの場合はリンクをパスする URLハンドラがあります
そして最後に― セキュリティ境界をシステムの どこに置くかを決めます これらの配置は非常に重要で 私たちが信頼する事と しない事について行う 全ての決定の基礎を提供します ここで自問してほしい重要な問いは― 攻撃者が影響を与える可能性のある アプリケーションの場所は? 攻撃者がコントロールしているデータは? それをどのように利用して アセットにアクセスできるのか? 今回のケースでは ここ― システムリソースと アプリケーションの間に境界を置きます
システムを信頼していますが コンテンツはコントロールされていません 例えばユーザーが選んだファイルは 信頼性のないソースからきているかもしれません ですからコンテンツを処理する場合 注意が必要です
これが起こり得るいい例は カスタム書類形式です
その他のローカルアプリケーションも 信頼性がないため― URLハンドラに セキュリティ境界線を置きます 提供されたデータに 悪意があるかもしれないからです
またアプリケーションと 全てのリモートアセットとの間に― 別の境界線を置きます リモートストアにデータを書き込むのは 私たちのコードですが― そうでない場合もあります 攻撃者が悪意のある投稿を作成する ソフトウェアを書いた可能性があるからです 攻撃者がシステムのどこに 影響を及ぼすかを理解したら― システムを保護するための アーキテクチャ的方法を考える必要があります これはアプリケーションと ウェブサイト間の接続に― 特に関連しています
ここAppleでは この接続を強化するため― App Transport Securityと呼ばれる 一般的なメカニズムを提供しています
ATSはアプリケーションがURLシステムを介して 行う全ての接続が― インターネット経由で転送されるユーザーの データを適切に保護する事を保証します ATSはデフォルトでオンになっていますが 利用するには― NSURLSessionのようなハイレベルのAPIを 確実に使用する必要があります
そのため開発ポリシーを作成して― 下位レベルのAPIで使用できるようにします
また出荷時に Info.plistでATS機能が 無効でない事を確認してください
同様に 考えなければならないのは データを保存するために使う― サーバーとローカル両方の形式です
一般的に選びたいのは 解析がシンプルで検証が簡単な形式です
plistやJSONはどちらも柔軟性とセキュリティの 間でバランスの取れたいい選択です ただしスキーマの検証を実行できる形式が 理想的です 強力なチェックと集中型のロジックを 提供するためです
また これらのストア内のユーザーデータを 保護する方法も検討します CloudKitの保護はティークラブにとって十分か? それともデータを手動で 暗号化する必要があるか? セキュリティ境界を敷いた時― ローカルキャッシュとアプリケーションの間には 置きませんでした なぜかと言うとアプリケーションに向かう途中で データを検証すべきだし― ローカルに保存されたコピーは 信頼できるからです iOSでは他のアプリケーションが ローカルファイルを変更できないため― これは強く強制される保証です ただしmacOSでは違います ですからmacOSをサポートするのであれば このデータを― どう保護するかを考えなければなりません サードパーティーの依存関係についても 考慮する必要があります どのライブラリを使うか セキュリティへのアプローチが 私たちのものと一致しているか またこれらの依存関係を素早くアップデートする 十分なテストを行っているか リスクをアーキテクチャ的に 軽減する方法を見つけたら― アプリケーションの実装を 見てみましょう このアプリケーションでは オブジェクトの3つの主要なクラスがあります 外部ソースからデータを取得する役割 そのデータを解析しモデルに変換する役割 そしてユーザーにデータを表示する役割
私たちがまず判別するのは どれがどれに当たるか― そしてどの領域が最もリスクが高いかです なぜならこれらが 攻撃者が制御するデータを― 直接操作しているため― 最も注意が 必要な部分だからです
リモートサーバーに保存されているデータは 最も高いリスクの1つです 攻撃者は私たちのサーバーに情報を作成し 保存できるため― 取り扱いには注意が必要です ですがティークラブのデータは メンバーのみ更新できます CloudKitがそれを保証します ここでは攻撃者セットが より制限されているため― リスクのレベルはどの攻撃者が― threat modelの一部だと 考慮するかによって異なります これによりシステム全体で このデータフローを追跡し― 対応するリスクプロフィールを取得できます データ転送を担当するコンポーネントには いくつかリスクがあります そしてこれが特に当てはまるのは― 信頼性のないエンティティと インタラクティブな接続がある場合です ですがデータを処理し その検証を担当するものが― 最も高いリスクを持ちます ここではサーバーを 除外している事にご注意ください サーバーはこのディスカッションの 範囲外のためです
アプリケーション全体でこのプロセスを実行し― リスクが存在する場所― 最もこだわらなければならない場所― そしてセキュリティ問題につながる 間違いを犯す可能性が 最も高い場所 これらのビューを取得できます まとめると システムに どのようなアセットがあるかを特定し― それらを保護するために どう設計できるか― 特に輸送中および保管中のデータを 保護する方法について考えます
攻撃者がアプリケーションに影響を与える 可能性のある場所を特定し― システムを介してそのデータを追跡する事で― リスクの高いコンポーネントの 場所を理解し― 実装中に起こり得る問題を 軽減する計画を立てる事ができます
それではライアンに 信頼性のないデータについて話してもらいます ありがとう クーパー さてアプリケーションの セキュリティを確保する前に― 信頼性のないデータがどう侵入してくるかを 特定できなくてはなりません それでは信頼性のないデータとは何か? 簡単な事です 制御できない限り その外部データは 信頼できないものだと 常に想定します 制御できるかどうか分からない場合は 最悪の場合を想定します 信頼性のないデータがどの様にして― アプリケーションに侵入できるか 簡単な例を見てみましょう 気づかないかもしれませんがアプリケーションが カスタムURL処理を使用している場合― URLは完全に信頼性がなく どこからでもやってきます 予想もしていなかった人が iMessageを介してURLを送ってきた場合でも 同様に カスタムドキュメント形式を使用している場合― 解析に脆弱性がありアプリケーションを 危険に晒す可能性があります
まずしなければならない事は 全てのデータ エントリポイントを把握する事です そうするとアプリケーションが どのように信頼性 のないデータを処理しているかが分かります データフローダイアグラムが完成したので― この情報を利用可能にします これは1つの例であり iOSおよびmacOSが アプリケーションに公開する― 全エントリポイントの 完全なリストではありません 一般的にアプリケーションは ある程度のネットワークを使用します これは信頼性のないデータを扱う上で 最も高い相対性のリスクを表します 攻撃者はユーザーのデバイスに 物理的に近接する事なく― ユーザーを 標的にできるからです これはHTTPSや ビデオ通話などのピアツーピア通信― または Bluetooth通信を介して サーバーと通信する形式で起こり得ます この例では Loose Leafにはネットワーク上の― 信頼性のないデータを処理する 機能がいくつかあります そして私たちにはティー狂信者の プライバシーとセキュリティが重要であるため― デバイス間のメッセージ全てに エンドツーエンドの暗号化を実装します ここではどうやってメッセージを暗号化するか 詳しく説明しませんが― セキュリティで保護されたアプリケーションを 開発する場合― これらの種類のセキュリティテクノロジーの 本質的な特性を理解する事が重要です 暗号化は メッセージに信用性があり 有効である事を意味しません 簡単に言うと― メッセージが友人からのものである事は 暗号が保証してくれますが― その友人が私をハッキングしないとは 限りません 暗号化されたメッセージのコンテンツも 信頼性のないデータなのです さて アプリケーションはネットワーク経由で メッセージを受け取りますね その後 通常私たちはここで メッセージ処理を行います これは例えば 暗号化されたシグネチャの復元や検証です 次にアプリケーションが― メッセージのコンテンツを 読むポイントまで到達します そこで私たちは これらのメッセージを見て― 信頼性のあるデータと ないデータを判別します そうすれば信頼性のないデータを どの様に使用しているかが分かります 従って プロトコルを確認すると― この送信者のeメールアドレスが 確認済みである事が分かります この例では これは暗号で検証されているため― なりすましや 予期できないものには なれません ですからこれは信頼できます
次にメッセージ識別子を調べると 部分的に制御可能である事が分かります 部分的に制御可能というのは つまり― 値の範囲または特定タイプに 縛られている事を意味します 例えばこれは UUIDである必要がありますが― 誰かが有効なUUIDを送信する 可能性があります
そこでメッセージのペイロードを調べ― 送信者が完全に制御している事を 確認します これは送信者が任意のメッセージ ペイロードを送信できる事を意味し― アプリケーションが想定する形式ではない 場合もあります ですから送信者が制御するものに関しては この信頼性のないデータを使用する前に― 検証しサニタイズする 必要があります いいですね ここまで信頼性のないデータの 識別および分類方法を見ました それでは信頼性のないデータの 誤った処理方法を見てみましょう 自分のアプリケーションにある 同様のバグを見つける方法を学びます
ここでは パストラバーサルバグを見ます 非常に一般的なタイプの セキュリティバグで― アプリケーションで見つかる事があります Loose Leafではティーラテアートの写真を― 友人に送る事ができます これがその処理をする コードのようです
この例ではダウンロードした 写真を撮っています ここでの目標はそれを一時 ディレクトリに移動して― システムがダウンロードした写真を 削除する前に使用できるようにします まずメソッドに何が入るかを調べます 信頼性のあるデータと ないデータを分け― 信頼性のないデータを どう処理しているかを見るのが目的です
ですからここでメソッドを見ると― 着信したリソースURLがランダムな ファイル名である事が分かります インターネットからダウンロードした時に― デバイスで ローカルに作られます これは送信者の影響を受けないため 信頼できます
そしてここでfromIDを確認します これは確認済みの送信者アドレスです この例では これは暗号で検証されているため― なりすましや予期できないものには なれません ですからこれは信頼できます
次にここで名前を確認します これは完全に送信者が制御しています ファイルの名前を伝えているのですが― 任意の文字列を ここで送信する事ができます どう使うか見てみましょう 名前を取って追加パスコンポーネントに渡し― テンポラリディレクトリに この宛先パスを作成します 最初にこれらのバックトラッキング コンポーネントを 含む文字列を送信するとどうなりますか? これでテンポラリディレクトリの外で トラバースできるようになりました ファイルをコピーすると 送信者から与えられた任意の場所に― このファイルがコピーされます これは明らかに かなりひどいです アプリケーションのコンテナにある アクセス可能な機密ファイルを― 上書きする可能性があるからです そこであなたはこう言います “オーケー それじゃあ lastPathComponentを使おう” この例ではそれでうまく行き 安全なファイル名が得られます ところが最後のコンポーネントが バックトラッキングだった場合もこれを返します ですからそれでもまだアプリケーションの機密 ファイルを上書きする可能性があります
lastPathComponentを使った場合に 起こり得るもう1つの問題は― URLWithStringを使用して URLを手動で作成した場合です なぜならlastPathComponentは スラッシュがパーセントエンコーディングで― エンコードされていた場合も これを返すからです URLを使用すると パーセントエンコーディングが― デコードされるため 意図したディレクトリの外を― トラバースする事ができます
これをする正しい方法とは何でしょう? lastPathComponentを ファイルネームに使用して― 実際にファイル名がそこに残っている事を 確認します 特別なパスコンポーネントと 等しくない事を確認します そうしないと離脱して ファイルの処理を拒否します そしてfileURLWithPathを 使用する事を確認します これによってパーセントエスケープが 更に増加し― この類のパーセントエンコーディング 攻撃に対して脆弱でなくなります これから何を学びましたか? ファイルパスでリモート制御可能なパラメータを 使用しない事 これは大変間違いやすいものです 必要な場合は 例に示したように― 追加検証とサニタイズを lastPathComponentと使用します
また 必ずfileURLWithPathを 使用してください それによりファイルパスに パーセントエスケープが追加され― 同じパーセントエスケープの攻撃に対して 脆弱でなくなります 通常はできる限りこのパターンは 避けてください ローカルで作成されたランダムなファイルネーム のテンポラリファイルパスを使用します 現在実行できるアクションは― 次のメソッドに渡されている 信頼できない 文字列の 全ての使用をチェックする事です 信頼性のないデータを 誤って処理する別の例は― 制御されていないフォーマット文字列です アプリケーションには 次のようなメソッドがある可能性があります このメソッドではデバイスから 要求メッセージを取得し― 応答を生成して それを送信します ここで同じ演習を行います メソッドを見て― 何が信頼できて何ができないかを 確認します そして信頼性のないデータを どう使うかを決定します それを見て― この名前がデバイスの ローカルユーザー名である事を確認します ローカルで生成され送信者には 影響されません ですからこれは信頼できます
ところがこの彼らが送信してくる要求は 送信者が制御するペイロードです 彼らはどのような任意の要求 メッセージでも送信してきます ですからこれは信頼できません このパラメータを どう使っているか見てみましょう まず 要求を受け取ります そしてこのローカライズされたフォーマット 文字列を要求から取得して― NSLocalizedStringに渡します ここでの目標は正しい応答を― デバイス上のユーザーローカル言語に 生成する事です
ただしこれは 任意の文字列です ローカライズできなくても 彼らはここに何でも送信できます これをフォーマット付きの文字列に渡すと― 文字列を正しくフォーマットするのに 十分な引数がここにはありません そこで何が起こるかと言うと― このスタックの コンテンツをリークし― 相手に送り返します これによりアプリケーションのメモリから 機密情報が― 漏洩する可能性があります 場合によっては 攻撃者が― アプリケーションの実行を 制御できる事になります これは絶対に避けたい事です これから学んだ事は? 信用できないデータから フォーマット文字列指定子を生成しない事です 正しいやり方は存在しないので できる限り避けるようにしてください また フォーマット指定子を 検証したりフィルタリングしたりしない事 おそらく誤った方法で行ってしまうでしょう
-Wformat-securityコンパイラフラグが 無効になっていな事を確認してください デフォルトでオンになっていますが― コードのセクションでは 無効にしないでください これがキャッチされない場合がある事に 注意が必要です 例えばたった今見た例では 実際にはコンパイラ警告が出ません なぜならフォーマット文字列が 文字列リテラルでない場合― コンパイラが文字列リテラルに関して 何も説明できない場合があるからです
Swiftのフォーマット文字列には 特に注意が必要です なぜならSwiftは CまたはObjective-Cのように フォーマット文字列の コンパイラ検証レベルがないからです ですからSwiftでは 可能な限り文字列補間を使用します フォーマット文字列を 使用する必要がある事もあります ローカライズされた文字列を 構築する時などです このようなバグが発生しないように 十分に気を付ける必要があります 現在実行できるアクションは フォーマット文字列として 次のメソッドにパスされた― 信頼性のない文字列の使用を 確認する事です これで2つの 一般的な セキュリティ問題について見てみました 何を探しているのかさえ分かっていれば 見つけやすいものです ですがいつも必ず 簡単なわけではありません 多くの場合 信頼性のないデータが コードを予期しない方に向けるために 利用できるロジックの問題を 見つけるでしょう そのうち最初に取り上げるのは 腐敗の問題です
Loose Leafでは ティークラブのメンバーと― ライブストリームで お茶会ができます これを行うために 招待ベースの セッションステートマシンをここで使います ここでお茶会に誰かを招待し― 受領されたら この接続された状態になり デバイスのカメラから ライブストリームをシェアできます ここでもう少し詳しく この状態で 何が移行するかを見てみましょう
誰かに招待状を送ると― 私は招待中の状態になり 相手は招待された状態になります 相手が招待を受けると 両者ともこの接続された状態になり― この接続済みソケットをデバイス間で シェアするかもしれません
もし私が誰かに招待を送り― 招待を受けるメッセージも送ったら どうなるでしょうか? 彼らに招待を受けてもらう事なく 強制的に 接続された状態にできるでしょうか? このためのコードを 見てみましょう
信頼性がある または信頼性のないデータの 差が既に分かるので― ここを素早く見て これが検証されたアドレスで― 信頼できる事が分かります これは送信者が制御しているメッセージで 信頼できません つまりここでは メッセージのタイプを分けて タイプごとにハンドラに ディスパッチしているのです ですがこれは送信者制御の ペイロードから来るので― どんなメッセージタイプでも 送る事ができます この場合 inviteAcceptメッセージを 見たのいので― ここのハンドルにジャンプします 送信者から送られたセッションIDに基づいて このセッションが存在する事を確認し― “接続済”状態に 設定しているようです このケースでは― 送信者は私たちを強制的に 接続された状態にできるようです おそらくここにチェックが足りません 私たちが実際には 招待中の状態にあり― 移行する前に“invite accept”のメッセージを 待っている状態である事を確認します それで十分でしょうか? いえ そうではないでしょう おそらく他にも確認事項があり 実行前に― TRUEでなければならない state invariant条件があります この場合 私たちを移行させる 受諾メッセージが― 実際に招待した人からである事を 確認したいのです これから何を学びましたか? 実行前にTRUEでなければならない 明白なstate invariantを定義します しかし本当に微妙であるため 間違いを起こしやすい事もあります この例では 正しい状態にあるかを 確認するだけでなく― 続行する前に検証する必要のある 他のプロパティがありました またこのチェックのいずれかが失敗した場合 早期に救済し続行を拒否します 状態を変更する可能性のある 予期しない方向に― コードを移動させたくないからです もう1つ見つける可能性のあるロジックのバグは 信頼性のないデータに便乗しています そこでアプリケーションでは 送信者から ネットワーク経由で提供された― JSONデータを取得し そこからリモート エントリを持つディクショナリを作成して― 多くのメッセージ処理を 行う必要があります
処理が終わったら このエントリを ディクショナリにピン止めします 次に このディクショナリを アプリケーションの他の場所に移動し― おそらくUI またはデータベースを更新します そしてアプリケーションにたどり着き それを使う時― 使用前にどれに信頼性があるのかを 調べる必要があります そこでディクショナリを見ます “これはリモートエントリ”で “これは送信者制御エントリ”だと分かります インターネット経由で送信されると 分かっているからです でも他のエントリは 信頼できるものでしょうか? 信頼できると思うかもしれません なぜならアプリケーションが ローカルに生成しているので― 送信者が制御しているものではないからです ところが蓋を開けてみると これは信頼できません なぜならこれは 送信者が 制御するメッセージから来ているためです このエントリも同様に提供されます そしてこの他のエントリが ピン止めされていない― 予想外のパスに アプリケーションを押しやると 実際にメッセージを使用する時 リモート送信者から来たのか ローカルで 生成されたのかが分からなくなります ですから信用できないものと 想定する必要があります
もう1つの例を見てみましょう 他の人のライブお茶会に 参加できる機能を追加し― リアクションの画像を表示して 考えを 表す事ができるようになりました JSONデータから このリアクションオブジェクトを 作成したようです 画像データが添付されている場合― メッセージ処理の間に ローカルファイルにURLを書き込んで リアクションオブジェクトに imageURLとして設定します それからViewControllerにパスして 表示します ここで何が起きるかを 見てみましょう imageURLがnon-nilの場合 それを表示します…
表示が終わったら ディスクに 書き込まれた画像を削除します ところが信頼性のないデータと一緒に 作成された同じオブジェクトから― imageURLを取っているため アプリケーションのコンテナから アクセスできる任意のファイルを― 攻撃者が削除できるようになってしまいました imageURLが 既に設定された状態で 誰かがこのメッセージを送信すると― 画像データが設定されていなかったため このコードブロックをスキップして― ここに直接表示します ところがimageURLは設定されているため ファイルパスにあるものを 表示しようとしますが 実際にはJSONで指定されたファイルを 全て削除してしまいます これから何を学びましたか? 信頼性のあるデータとないデータを 完全に異なるオブジェクトに分離する事です 1つのオブジェクトに 結合しようとしないでください それにより信頼性のあるパラメータと ないパラメータの間が 曖昧になってしまうからです どれに信頼性がないのかを いつも明白にして 信頼性がないデータの使用を監査し― 使用前は必ず実証され サニタイズされているようにします 次はクーパーによる NSCodingについてのトークです ありがとう ライアン NSCodingおよび より現代的な NSSecureCodingコンパニオンは― オブジェクトの複雑なグラフを シリアライズおよびデシリアライズする― 貴重な機能を 長い間提供してきました これは解析されたデータを ローカルキャッシュに格納したり― カスタムドキュメント形式に 使用したりするものです 今日はよく見る間違いや― 信頼性がないデータからアーカイブが来た場合に 特に危険なケースを紹介します
まず始めに NSUnarchiverの使用を 止めてください これは長い間 軽視され続けており― 信頼性がないソースからのデータには 適していません お勧めするのは NSKeyedUnarchiverです NSUnarchiverに似ていますが オブジェクトの グラフをパックやアンパックでき― 安全に使用できます
NSKeyedUnarchiverも 長い間使用されているため― 好ましくない使われ方もあります 多くが好ましくない方法で使用されており ソフトウェアを離れてユーザーが攻撃に 晒されている事に気が付いていません この一番上のメソッドが 使用していただきたいものです 底のものは既に遺産と化しているため 使用されている場合は― できるだけ早く 移行する必要があります この2つのネーミングは 非常に似ていますが― 非推奨のものは全て “from”ではなく“with”が付いています これらのメソッドを非推奨にした理由は― セキュアコーディングとの 互換性がないためです セキュアコーディングとは何か 完全にご存じないかもしれませんので― どのような働きをするか 見てみましょう
セキュアコーディングがないと― アーカイブを取り上げて unarchiveObjectWithDataを呼び出す時 オブジェクトグラフがアンパックされます この例では文字列の配列が 返される事を予期しています
ここでの問題は 作成されるオブジェクトの種類が アーカイブ内で指定されている事です 信頼性がないソースから来た場合 または 攻撃者が変更できる場合 NSCoding準拠オブジェクトを 返す事ができます
つまり攻撃者がアーカイブを変更し 全く予想もしていなかったクラスを WKWebViewのように― アンパックしてしまうという事です このオブジェクトは期待するインターフェイスと 適合しないかもしれず タイプチェックを実行したとしても― セキュリティの問題が残ります クラスのアーカイブ解除中に 攻撃者が達する事ができるバグが― 既にトリガされているからです セキュアコーディングでは互換性のあるクラスを 制限する事でこれらの問題を解決し セキュアコーディングプロトコルを実装する― 各呼び出しサイトで期待するクラスを 指定する必要があります
この情報はKeyedUnarchiverで 使用され― デコードが施行される前に 検証を実行します
こうする事で攻撃者が 利用できるクラス数が― 劇的に減少するため 攻撃対象領域も減少します それでも よくある間違いが まだまだあります いくつか見てみましょう
この簡単な例では― セキュアコーディングを使用して オブジェクト配列を取得しています
ここでの問題はallowedClassesセットで NSObjectを指定した事です
セキュアコーディングはプロトコルに準拠し allowedClassesセットまたは― 指定されたサブクラスのいずれかにある オブジェクトをデコードします 全てのオブジェクトは NSObjectから派生しているため 安全にコード化されたオブジェクトが デコードされ― セキュアコーディングによる 攻撃面の縮小が損なわれます これは見つかりやすい 簡単な例ですが― 複数のフレームワーク階層を通り― 最終的にKeyedUnarchiverメソッドに提供される より複雑なケースでも散見されます
より簡単なパターンをお見せします 文字列はアーカイブからデコードされます その後文字列はNSClassFromString経由で クラスの検索に使用され― 結果のクラスは オブジェクトを抽出するための allowedClassとして使用されます
ここでの問題は明らかです
classNameも その結果として許可された allowedClassも攻撃者の制御下にあります
そのため特定のクラスや NSObjectの名前を簡単に引き渡し― セキュアコーディングの 利点を回避します これらの問題をどう解決するか? 可能であれば 制限された 静的クラスのセットを使用し 大きな階層のベースとなるクラスを避け 攻撃者が利用できる クラス数を減らします 動的クラスをサポートするケースの 場合はどうでしょうか? これを一番よく見るのは プラグインのサポートを必要とする場所です
その場合 allowedClassリストを 信頼できるデータからのみ― 作成する事をお勧めします 一番簡単な方法は プラグインが プロトコルをサポートする場合です 従ってランタイムが認識している クラスを全て列挙し― プロトコルに準拠する クラスのセットを作成します
クラスリストは攻撃者の 制御外にあるため セキュアデコーディングで 安全に使用できます 要約すると 非奨励の全てのメソッドと クラスからコードを移行し― NSObjectを使用するか または過度に 一般的な基本クラスの使用を避けて― 静的なallowedClassリストを 可能な限り使用します それができない時は 信頼性のあるデータから作成します
もう1つできる事は それを完全に避けて― Swift Codable または柔軟性の少ない plistやJSONやprotobufのような― エンコーディング形式を使用します 信頼性のないデータを扱う領域で― これらのデータを使用するために コードベースを検索する事で この情報に直ちに対応できます 高リスクのコードを悩ませる もう1つの問題はメモリの破損です
Objective-C またはSwift's Unsafe-Pointerを 使用する時はいつでも― メモリの安全性の問題が 発生する可能性があります バッファ 文字列 または構造化された アーカイブからの― 信頼性のないデータを解析する時に 特によくあります
いくつか例を見てみましょう
既にお話ししたように 信頼性のないデータが― 信頼性のあるデータと間違えられた時 セキュリティ問題が起こります
この例では プライベートな ティークラブのために― CloudKitをアンパックします まず UUIDのバイトをiVarにコピーします
それぞれのエレメントを見ると CKRecordはCloudKitから生成されているため― 信頼性があります しかしそこから引き出したデータは 攻撃者の制御下にあります ですから ここに一定量のデータが 含まれているか信頼できません
ここでの問題は データの長さは 攻撃者の制御下にあるものの― UUID iVarのサイズは 固定されている事です
そのため提供されたNSDataが UUIDが保持できるより多い場合― 攻撃者の制御下にあるデータは 範囲外に書き込まれます
これが問題となる理由を確認するには 起こり得る事を調べる必要があります
UUIDを含むオブジェクトでは― 次のiVarが ローカルキャッシュへの パスになっているのが見えます このフィールドは CloudKit記録から 設定されていません そのため UUIDをオーバーフロー させる事で攻撃者は― 本来持つべきでないフィールドを 制御できるようになります memcpy により 攻撃者が制御できるデータが 意図したとおりにUUIDに書き込まれますが― サイズが大きすぎるため localCachePathにオーバーフローします
これによって グループのキャッシュを再構成し ファイルをダウンロードさせ― ユーザーのローカル設定を 上書きする事ができます 攻撃者は様々な手法や これらのバグを使って― アプリケーション内でコードを実行し それによってユーザーの データへのアクセス権と― 与えられた全ての リソースへのアクセス権を得ます これに対する修正は sizeof(_UUID)を使う事ですが― それによって更に別の問題が露呈します 例えば攻撃者が予期していたより 少ないデータを提供した場合― memcpy は“データ”の終わりを読み取って メモリにある他のユーザーの機密情報を UUIDフィールドに開示させます アプリケーションがその情報を サーバーに同期すると― ユーザーデータはエンコードされ 攻撃者に返されます
正しく検証しく修正するには そのオブジェクトがNSDataであると検証し 長さが正確に一致する事を 確認します これにより宛先の上書きと ソースの過剰な読み込みが両方防止されます
これはシンプルな例ですが― 信頼性のないデータを解析する時には いつもより注意が必要です 特にフォーマットが複雑であったり ネストしている場合は注意してください いつも必ず長さを確認してください C構造体をバッファに キャストするだけでも― データが不十分だと ユーザー情報が 開示されてしまいます
注意が必要な もう1つのケースは 整数を扱う場合です これはオーバーフローする傾向があるためです
この例では 同じメソッドを 更に掘り下げて― UNICHAR文字を含むNSDataを抽出し― 別のフィールドからカウントします 以前と同じように どのデータが信頼できるかを 確認する必要があります このケースでは 名前とカウントの両方に信頼性がありません なぜなら両方とも 攻撃者が 制御するデータから来ているためです
さて デベロッパが盲目的に カウントを信頼する危険を予見したので― データの長さに対して それを検証します ところがここに問題があります 攻撃者がこの比較の両サイドを制御し デベロッパが作った推定が もはや 正しくないという一連の値が存在します
この場合 攻撃者が “名前”に長さ0の文字列と― nameCountに8千万の16進数を渡すと 整数がオーバーフローし “if”ステートメントが失敗します
これにより“名前”の末尾から 非常に大きな読み取りが行われ― ユーザーデータが 開示される可能性があります これを修正するには 包括的な検証チェックを追加し― タイプと長さを確認します 一番大事なのは― os_overflowメソッドを利用して 演算を行う事です この場合 私たちはos_mul_overflowを使いました ご存じない方へ説明すると― os_overflowは数値を安全に処理し アンダーもしくはオーバーフローの状態を 確実に検出できる便利な関数のセットです 使うのは簡単です 算術演算を関連するos_overflowメソッドに 置き換えるだけです 私たちは 2つと3つの パラメータ両方を使用して― 加算 減算 乗算用パラメータを 用意しました
オーバーフロー時にはゼロ以外を返します これらを使う事で 攻撃者制御の値を使用して― 安全に計算できます つまり まとめると― 常に長さを検証し あらゆるケースを考慮する事 大きすぎるもの 小さすぎるもの 丁度よくなければなりません
数値を扱う際は 算術に注意してください os_overflow関数を 使用していないかのように― 間違いを犯すリスクが高いからです
最後に リスクの高いコードパスに Swiftを 使用する事を真剣に検討してください ただしUnsafePointerや その他 rawメモリを操作する― 機能の使用は避けてください 最後に― このトークでセキュリティについて 考える方法のいくつかのアイデアと― 試みて回避すべき一般的な落とし穴の例を お伝えできている事を願っています セキュリティは巨大なトピックで難しいものです これまでに見てきたように― ほんの些細なエラーでアプリケーション全体が 危険に晒される事もあります
ですが リスクがどこにあるかを理解して リスクを軽減するように設計し― 信頼性のないデータを扱う時に 注意を払えば 問題が発生する可能性を大幅に 減らす事ができます ご視聴 ありがとうございました
-
-
16:34 - Path traversal
func handleIncomingFile(_ incomingResourceURL: URL, with name: String, from fromID: String) { guard case let safeFileName = name.lastPathComponent, safeFileName.count > 0, safeFileName != "..", safeFileName != "." else { return } let destinationFileURL = URL(fileURLWithPath: NSTemporaryDirectory()) .appendingPathComponent(safeFileName) // Copy the file into a temporary directory try! FileManager.default.copyItem(at: incomingResourceURL, to: destinationFileURL) }
-
22:26 - State management
func handleSessionInviteAccepted(with message: RemoteMessage, from fromID: String) { guard session = sessionsByIdentifier[message.sessionIdentifier], session.state == .inviting, session.invitedFromIdentifiers.contains(fromID) else { return } session.state = .connected session.setupSocket(to: fromID) { socket in cameraController.send(to: socket) } }
-
30:56 - Safe dynamic allowedClasses
NSSet *classesWhichConformToProtocol(Protocol *protocol) { NSMutableSet *conformingClasses = [NSMutableSet set]; unsigned int classesCount = 0; Class *classes = objc_copyClassList(&classesCount); if (classes != NULL) { for (unsigned int i = 0; i < classesCount; i++) { if (class_conformsToProtocol(classes[i], protocol)) { [conformingClasses addObject: classes[i]]; } } free(classes); } return conformingClasses; }
-
34:23 - Buffer overflows
@implementation - (BOOL)unpackTeaClubRecord:(CKRecord *)record { ... NSData *data = [record objectForKey:@"uuid"]; if (data == nil || ![data isKindOfClass:[NSData class]] || data.length != sizeof(_uuid)) { return NO; } memcpy(&_uuid, data.bytes, data.length); ...
-
36:06 - Integer overflows
@implementation - (BOOL)unpackTeaClubRecord:(CKRecord *)record { ... NSData *name = [record objectForKey:@"name"]; int32_t count = [[record objectForKey:@"nameCount"] unsignedIntegerValue]; int32_t byteCount = 0; if (name == nil || ![name isKindOfClass:[NSData class]] || os_mul_overflow(count, sizeof(unichar), &byteCount) || name.length != byteCount) { return NO; } _name = [[NSString alloc] initWithCharacters:name.bytes length:count]; ...
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。