kuwabara tech note

OWASPによるモバイルアプリのセキュリティ基準を9つFlutterで実装する

はじめに

OWASPとは、Open Web Application Security Projectという非営利団体です。OWASPはモバイルアプリ用のセキュリティ基準 を設けています。 github.com 本記事ではFlutterに関わる部分の実装方法について説明します。

1. 機密データを格納するために、システムの資格情報保存機能を使用している。

flutter_secure_storageというパッケージを使います。

資格情報保存機能とは、

です。flutter_secure_storageでは、Keychainはデフォルトで使用されますが、EncryptedSharedPreferencesはパラメーターで指定する必要があります。

2.機密データを処理するテキスト入力では、キーボードキャッシュが無効にされている。

TextFieldのautocorrectをfalseに指定することで、キーボードキャッシュを無効にできます。

TextField(
    autocorrect: false
)
3.機密データは、ユーザーインタフェースを介して公開されていない。

TextFieldのobscureTextをtrueに指定することで、パスワード入力の際に画面にパスワードが表示されません。

TextField(
    obscureText: true
)
4. 機密データはモバイルオペレーティングシステムにより生成されるバックアップに含まれていない。

androidではデフォルトで自動バックアップをしてしまいますので、これをオフにします。 android/app/src/main/AndroidManifest.xml を下記ように編集します。

<manifest ... >
    ...
    <application android:allowBackup="false" ... >
    ...
</manifest>

iosに関してはここに詳しく書いてありますが、Flutterから逸脱してしまうので触れません。 github.com

5. バックグラウンドへ移動した際にアプリはビューから機密データを削除している。

バックグラウンドに移動したかの検知はWidgetsBindningObserverを継承することで可能になります。

テキストの削除はTextEditingControllerで行います。

class SampleView extends StatelessWidget with WidgetsBindingObserver {

  final _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance!.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if(state == fecycleState.paused):
    _controller.clear()
  }

  @override
  Widget build(BuildContext context) {
    ...

    TextField(
    controller: _controller
    ),
    ...
  }
}
6. アプリは必要以上に長くメモリ内に機密データを保持せず、使用後は明示的にメモリがクリアされている。

DartGC(Garbage Collection)を採用しているため、必要以上に長くメモリ内に機密データを保持することはありません。

自動でメモリがセキュアに割り当て、解放されることは大きなメリットです。 しかし、デメリットとして明示的にメモリをクリアできません。

javeなどでは、

  • nullを代入すること
  • primitive型変数に別の値を格納すること

で明示的にメモリを削除します。

ですがDartはnull safetyな言語ですし、primitive型はありません。(すべての型はObject型の派生です)

ただ、Providerで機密データを扱っていれば .autoDisposeを付けることを考えるべきです。.autoDisposeにより、参照されなくなったProviderのStateは破棄されます。

7. 明示的に必要でない限りWebViewでJavaScriptが無効化されている。

WebViewはアプリ内でWebページを開くWidgetです。

パラメーターでjavascriptをdisableに指定することができます。

WebView(
    initialUrl: url,
    javascriptMode: JavascriptMode.disable
)
8. WebViewを破棄する前にWebViewのキャッシュをクリアしている。

WebViewControllerを使ってキャッシュをクリアします。WebView Widget 内のonWebViewCreatedでコントローラを登録する。

import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';

class WebPageView extends StatelessWidget {

  final Completer<WebViewController> _controller =
  Completer<WebViewController>();

  @override
  Widget build(BuildContext context) {
    ...
    WebView(
        initialUrl: url,
        javascriptMode: JavascriptMode.disabled,
        onWebViewCreated: _controller.complete,
        onPageFinished: (String url) async{
            final controller = await _controller.future;
            controller.clearCache();
        },
    ),
    ...
  }
}
9. リバースエンジニアリング対策

難読化はビルドする際に下記のコマンドで行うことができます。

flutter build apk --obfuscate --split-debug-info=<project名>/android
flutter build ios  --obfuscate --split-debug-info=<project名>/ios

docs.flutter.dev

リバースエンジニアリング対策を含んだパッケージを使う手もあるようです。 pub.dev

おわりに

Flutter側からSwift側を扱えばより改善される部分もありそうですね。 以上です、ありがとうございました。