LoginSignup
378
406

More than 1 year has passed since last update.

iOS/Android セキュリティガイドライン

Last updated at Posted at 2018-11-20
  • 必須: 基本的に対応が必要。何らかの要因で対応できない場合は関係者への同意をとる。
  • できれば:状況的に困難でなければなるべく対応する。
  • 基本対応なし:特別な要件がなければ対応しない。

直近修正履歴(2018/11/21)

  • [OS共通] ログ出力の対策にproguardによる方法を追記
  • [iOS] 通信データのキャッシュの対策に一部問題があったため修正

[OS共通] 通信の暗号化

対応要否:必須

リスクの詳細

HTTPで通信を行うと通信データが平文でネットワークに流れるため、情報漏洩の危険性がある。

対策

通信を行う場合はHTTPS(SSL/TLS)通信を利用する

[OS共通] SSL証明書のチェック

対応要否:必須

リスクの詳細

通信やWebViewで行われるSSL証明書の正当性検証を無効化すると、ネットワーク管理者などにより通信経路上のデータ改竄、傍受が可能になる。

対策

試験環境で証明書の正当性検証を無効化する必要がある場合は、Build Target(iOS)、Flavor/BuildType(Android)にて処理を切り替え、試験環境向けビルドのみ無効化する。

[OS共通] 重要情報の保存

対応要否:必須

リスクの詳細

端末に保存された情報はバックアップ機能を用いて取得することができるため、パスワードやユーザーネームなどの重要情報を平文で端末に保存すると、攻撃者に端末を操作された場合に重要情報を奪取される可能性がある。

対策

パスワードやユーザーネームなどの重要情報を保存する場合は暗号化して保存する。

iOS

キーチェーンに保存する。暗号化はOSにより行われるためアルゴリズムを意識する必要はない。

キーチェーン読み書き例
    /// キーチェーンにデータを保存する
    @discardableResult
    func saveData(key: String, data: Data) -> Bool {
        var query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: Bundle.main.bundleIdentifier ?? "DefaultService",
            kSecAttrAccount: key
        ]
        
        if SecItemCopyMatching(query as CFDictionary, nil) == noErr {
            return SecItemUpdate(query as CFDictionary, [kSecValueData: data] as CFDictionary) == noErr
        } else {
            query[kSecValueData] = data
            return SecItemAdd(query as CFDictionary, nil) == noErr
        }
    }

    /// キーチェーンからデータを読み込む
    func loadData(key: String) -> Data? {
        let query: [CFString: Any] = [
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: Bundle.main.bundleIdentifier ?? "DefaultService",
            kSecAttrAccount: key,
            kSecReturnData: kCFBooleanTrue
        ]
        
        var dataTypeRef: CFTypeRef?
        if SecItemCopyMatching(query as CFDictionary, &dataTypeRef) == noErr {
            return dataTypeRef as? Data
        } else {
            return nil
        }
    }

Android4.3以上

キーストアから取得した鍵で暗号化した上、SharedPreferencesなどを用いて端末に保存する。

Android4.3未満

キーストアは使用できないため、ランダムで生成した鍵を使って暗号化した上、SharedPreferencesなどを用いて端末に保存する。ランダム生成した鍵は復号化時に必要になるため、合わせて端末に保存する。
※暗号化にIVが必要な場合は固定値を使用せず、ランダム生成する

[OS共通] ログ出力

対応要否:必須

リスクの詳細

一部のログ出力はリリースビルドでも閲覧できるため、攻撃者に有益なシステム上の情報を与える可能性がある。

Android

android.util.Logクラスによるログ出力はリリースビルドでもLogCatで閲覧できる。

iOS

NSLogによるログ出力はリリースビルドでもXcodeで閲覧できる。

対策

Android

android.util.Logクラスをラップしたカスタムのログクラスでログを出力する。

簡単なカスタムログクラスの例
public class Log {
    public static void d(String tag, String message) {
        if (BuildConfig.DEBUG) android.util.Log.d(tag, message);
    }
}

また、Proguardを利用してログ関数の呼び出しを削除することもできる。
Proguardの'assumenosideeffects'オプションは、返り値が利用されていないメソッドの呼び出しを削除するため、proguard-rules.proに以下の指定をすると、この特性を利用してログ関数を削除できる。

-assumenosideeffects public class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
    public static *** wtf(...);
}

Proguardでログ出力を削除する
ProGuardマニュアル(5) 最適化オプション

iOS

■Objective-Cの場合
NSLogでログ出力を行い、リリースビルドではプリプロセッサマクロによりNSLogを無効化する

#ifdef PRODUCT
#define NSLog(...)
#endif

■Swiftの場合
print関数でログ出力を行う。
print関数はリリースビルドではXcodeで閲覧できるログは出力できないが、リリースビルドのみprint関数を無効化することもできる。

#if PRODUCT
func print(_ items: Any..., separator: String = " ", terminator: String = "\n") {}
#endif

[OS共通] クラッシュレポート

対応要否:必須

リスクの詳細

Crashlyticsなどのクラッシュレポート機能が導入されていないと、本番環境で発生したクラッシュが把握しづらく、重大な問題の発覚が遅延する可能性がある。
また、クラッシュする問題が発覚した場合、クラッシュレポートがないと詳細な情報を得られず原因の調査が困難になる。

対策

Crashlytics、Firebaseなどのクラッシュレポート機能を導入する

参考情報

iOS: Crashlyticsでレポートを送るまで
Android: 【Android】Firebase Crashlyticsを最速で実装する

[共通] 不適切な数値型の使用

対応要否:必須

リスクの詳細

計算にInt型を使用すると、最大値が小さいため桁あふれを起こす可能性が高い。
また、計算にDouble、Floatを使用すると誤差を生じる危険性がある。

対策

金額など正確な計算が必要な場合は、Int、Double、Floatを使用しない。
代わりに、数値計算用クラス(iOS:NSDecimalNumber、Android:BigDecimal)か、桁あふれしないことが担保できるのであれば64bitの整数型(iOS:Int64、Android: Long)を使用する。

[OS共通] プライバシーポリシーの表示

対応要否:必須

リスクの詳細

個人情報を取得する場合、利用目的や第三者への提供有無を明示しないと、ユーザーにサービスに対する不信感を抱かせてしまう可能性がある。

対策

ユーザーから個人情報を取得する場合は、取得する情報の種類、利用目的、第三者への提供有無等を記載したプライバシーポリシーを表示する。

参考情報

プライバシーポリシーの書き方とテンプレート|記載義務を解説!

[OS共通] 一時データの削除

対応要否:必須

リスクの詳細

一時的に作成したファイルを削除せず残しておくと、意図せぬ情報の漏洩に繋がる危険性がある。
また、一時ファイルが削除されず増え続けると端末のストレージを圧迫してしまう危険性がある。

対策

一時的に作成したファイルはアプリ終了時など適切なタイミングで削除する

[OS共通] 強制アップデート機能

対応要否:必須

リスクの詳細

アプリが強制バージョンアップの機能を持っていないと、致命的な不具合やセキュリティ上の欠陥が見つかった場合に迅速なアプリのアップデートを行えず、被害を拡大させる可能性がある。

対策

アプリのレジューム時やログイン時、API通信時など適切なタイミングでバージョンチェックを行い、バージョンアップが必要な場合は告知を行うとともに、問題のあるバージョンのアプリには利用制限をかける。

参考情報

[iOS]アプリに強制アップデート機能を導入すべき理由と、簡単に実装する方法

[OS共通] ハッシュのアルゴリズム

対応要否:必須

リスクの詳細

ハッシュアルゴリズムMD5やSHA1は衝突耐性に問題が見つかっているため利用しない。

対策

ハッシュの生成にはSHA-256アルゴリズムを使用する。

参考情報

SHA-1の安全性低下について

[OS共通] パスワードのマスキング表示

対応要否:必須

リスクの詳細

パスワードなどの秘密情報を画面上に表示すると、のぞき見により秘密情報を奪取される危険性がある。

対策

パスワードなどの秘密情報を入力や表示する際には、●などによるマスキング表示を行う。

[OS共通] ユーザー入力値のエスケープ

対応要否:必須

リスクの詳細

ユーザーからの入力値をアプリの出力として用いる場合、適切なエスケープなどを行わないと以下のようなインジェクションの危険性がある。

  • SQLインジェクション
  • JavaScriptインジェクション
  • OSコマンドインジェクション

対策

ネイティブアプリで該当するケースは少ないが、ユーザーからの入力値を以下に使用する場合は、適切なエスケープやバリデーション、プレースホルダの使用などによりインジェクションを防ぐ。

  • SQL
  • HTML
  • OSコマンド

参考情報

SQLインジェクションとは?Webサイトのセキュリティ対策を学ぼう
スクリプトインジェクション入門

[OS共通] 証明書の情報

対応要否:必須

リスクの詳細

アプリの署名(※)に用いる証明書の発行者情報は公開される可能性があるため、個人名や個人のメールアドレスなどを記載していると、ソーシャルハッキングの標的になる可能性がある。
(※)Androidのapkの署名やiOSのProvisioning Profile

対策

証明書の発行者には会社名や専用の代表メールアドレスなどを記載し、特段の要件がなければ開発者の個人情報を記載しない。

[iOS] App Transport Security(ATS)

対応要否:できれば

リスクの詳細

App Transport Security(ATS)を無効にすると、平文通信など安全でない通信を許容してしまう。
また、ATSは将来的にAppleにより必須化される可能性がある。

対策

サーバー側が対応可能であれば、App Transport Security(ATS)を有効にする。

[Android] Intentの送信方法

対応要否:できれば

リスクの詳細

アプリ連携時にIntentで情報を受け渡す際、Intentのパラメータは標準ログに出力されるため、受け渡したパラメータを他のアプリから取得される危険性がある。

対策

アプリ連携時にIntentで情報を受け渡す際パラメータではなくputExtraを使用すると標準ログへ出力されないため、重要情報をIntentで受け渡す場合はputExtraを利用する。

参考情報

要注意! 本当は怖い出力データ

[iOS] 通信データのキャッシュ

対応要否:必須

リスクの詳細

URLSessionなどで通信を行うと、OSにより通信データが自動で端末内のファイル(cache.db)に保存されてしまう。
このキャッシュはHTTPS通信でも平文で内容が保存されるため、攻撃者に有益なシステム上の情報を与える可能性がある。

対策

URLSessionを作成する際、URLSessionConfigurationのurlCacheにnilを指定することでキャッシュの保存を防ぐことができる。

let config = URLSessionConfiguration.default
config.urlCache = nil
let session = URLSession(configuration: config)

また、URLSessionConfiguration.ephemeralを使用した場合もキャッシュの保存を防ぐことができるが、この場合はURLSessionを破棄するとCookieや認証情報も破棄されてしまうため、セッション管理などにCookieを使用する場合は問題がある。

let config = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: config)

ちなみに、URLRequestのcachePolicyにreloadIgnoringCacheDataなどを指定してもcache.dbへの保存を防ぐことはできない。

参考情報

重要情報の漏えいにつながるスマホアプリのキャッシュ問題と対策

[Android] 連打とマルチタップ

対応要否:必須

リスクの詳細

AndroidはタップイベントからUIの変化までに若干の遅れがあるため、ボタンタップ時に画面をロックするインジケーターを表示しても、連打やボタンの同時押しにより複数回タップイベントを発生させることができる。
そのため、重複処理が発生してしまう可能性がある。
例)「注文」ボタン押下時に画面をロックするインジケーターを表示している場合でも、ボタンの連打により二重発注が発生する可能性がある

対策

短時間での連続処理を許可しない機能では、処理中にインジケーターを表示して画面をタップできなくするとともに、最初のボタンタップから0.2秒ほど、ボタンが反応しなくする。
また複数のボタンの親のViewGroupにsplitMotionEventsに"false"を設定すると、ViewGroup内のボタンの同時押しを禁止することができる。

[OS共通] 重要情報を保存しない

対応要否:できれば

リスクの詳細

ユーザーネームやパスワードなどの重要情報を端末に保存すると、通信傍受や端末の閲覧などにより重要情報が奪取される可能性が上がる。

対策

ユーザーネームやパスワードなどの永続的な重要情報は極力端末への保存を避け、代わりにトークンなどの一時的な認証情報を保存する。
※システムの仕様や設計によって実現できない場合もあるため、その場合は「重要情報の保存」に記載の方針に沿って情報を保存する。

[OS共通] 利用者への告知機能

対応要否:できれば

リスクの詳細

アプリが利用者への告知機能を持っていないと、致命的な不具合やセキュリティ上の欠陥が見つかった場合に告知が行えず、被害を拡大させる可能性がある。

対策

PUSHやアプリ起動、復帰時のチェックで利用者への告知が行えるようにし、問題が発生した場合は利用者に迅速な告知を行い被害を最小限に留める。

[OS共通] Pinning

対応要否:基本対応なし

リスクの詳細

標準的なSSL証明書の正当性検証を行っていても、端末に偽のCA証明書をインストールすると通信経路上のデータ改竄、傍受が可能になる

対策

アプリに証明書や公開鍵の一致チェック(Pinning)を実装することにより本リスクを防げる。
しかし、Pinningはサーバーごとに次世代も含めた証明書の管理が必要になり運用コストが増大し、デバッグや試験のための通信内容確認ができなくなり保守コストも増大するなどデメリットが大きいため、特別に要件がある場合のみ対応する。

参考情報

SSL Pinningについて

[OS共通] WebViewネイティブ連携機能の制限

対応要否:できれば

リスクの詳細

WebViewは以下の機能を用いて、あらかじめ設定したネイティブコードをJavaScriptから呼び出すことができる。
iOS:WKScriptMessageHandler
Android: JavascriptInterface

この機能を有効にしたWebViewで任意のサイトに移動できると、任意のJavaScriptが実行できるため、予期せぬパラメーターで特定のネイティブコードが呼び出される危険性がある。

対策

WebView上のJavaScriptからネイティブコードの呼び出しができるようにする場合は、あらかじめ指定したドメイン以外ではネイティブコードの呼び出しができないようにする。
また、JavaScriptから呼び出し可能なネイティブコードは、想定外のパラメーターを破棄するようバリデーションを実装する。

[OS共通] バックアップ

対応要否:基本対応なし

リスクの詳細

アプリのバックアップを有効にしていると、Androidは端末にケーブルを接続することでadbツールによりバックアップ対象のデータを読み取ることができる。
また、iCloud、Googleのアカウントを奪取されると、クラウドに保存されたアプリデータを奪取される可能性がある。
ただしiCloud、Googleのアカウントを奪取される、端末を奪われるという条件付きリスクのため優先度は低め。

対策

バックアップはOS標準の機能であり、これを無効にすると機種変更時のデータ移行など一般的なユーザービリティーを損なうため基本的には無効にしない。
特別に要件があり、バックアップを無効にする場合は以下の実装を行う。

iOS

一般的にアプリのファイルはDocumentsディレクトリに保存され、またLibraryディレクトリにUserDefaultsのデータが保存されるため、この2つのディレクトリをバックアップ対象外とする以下のコードをアプリ起動時などに呼び出す。

func addSkipBackup() {
    addSkipBackupAttributeToItemAtURL(filePath: NSHomeDirectory() + "/Library")
    addSkipBackupAttributeToItemAtURL(filePath: NSHomeDirectory() + "/Documents")
}

func addSkipBackupAttributeToItemAtURL(filePath: String) {
    let url = NSURL.fileURL(withPath: filePath) as NSURL
    do {
        try url.setResourceValue(true, forKey: URLResourceKey.isExcludedFromBackupKey)
    } catch let error as NSError {
        print("Error excluding \(url.lastPathComponent ?? "") from backup \(error)")
    }
}

Android

AndroidManifest.xmlの<application>要素にandroid:allowBackup="false"を指定することでバックアップを無効化できる。
特定のファイルのみバックアップから除外したい場合は、<application>要素にandroid:fullBackupContent属性を設定して、バックアップ対象をxmlファイルにて指定する。

[OS共通] スナップショット機能による情報漏洩の可能性

対応要否:必須

リスクの詳細

重要情報の表示中にアプリをサスペンドすると、重要情報が表示されたサムネイル画像が作成される。
このサムネイルはタスク切換え画面に表示されるため、覗き見により重要情報を奪取される可能性がある。

対策

重要な個人情報を表示する場合は一部をマスクして表示する。

[Android] プログラムの難読化

対応要否:できれば

リスクの詳細

Androidはデコンパイルしてプログラムを解析できるため、攻撃者に有益なシステム上の情報を与える可能性がある。

対策

ソースコードの難読化を行うことによりプログラム解析の難易度を上げることができる。
しかし、難読化を行うことによりCrashlyticsなどのクラッシュログの詳細が不明になる弊害がある。
※アプリのビルド時に生成されるマッピングファイル(/proguard/mapping.txt)をアップロードすればクラッシュログの復元は可能。
また、難読化してもプログラム解析を完全に防げるわけではないので、要件がある場合のみ対応する。

参考情報

見せたくないなら「持たせない」が鉄則!

378
406
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
378
406