Personally Identifiable Information (PII) Leakage
個人を特定できる情報(PII)の漏洩
説明
個人を特定できる情報(PII)とは、NIST Special Publication 800-122によると、名前、社会保障番号、生年月日、出生地、母親の旧姓、生体認証記録など、個人のアイデンティティを区別または追跡するために使用できるすべての情報の総称です。また、医療、教育、財務、雇用の情報など、個人に関連付けられている、または関連付けることができるその他の情報も含まれます。
モバイルセキュリティのコンテキストにおいて、PIIの漏洩は、プレーンテキストのPII情報がアプリケーションログや誰でも読み取り可能なファイルに記録され、ユーザーデバイス上のすべてのアプリケーションからアクセス可能になる場合に発生します。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// レイアウトにユーザー名とパスワード用のEditTextフィールドがあると仮定します
// それぞれのIDは'usernameEditText'と'passwordEditText'です
loginButton.setOnClickListener {
val username = usernameEditText.text.toString()
val password = passwordEditText.text.toString()
// ユーザーの認証情報を記録します
Log.i("UserCredentials", "Username: $username, Password: $password")
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にEditTextフィールドをクリアします
usernameEditText.text.clear()
passwordEditText.text.clear()
}
}
}
import UIKit
import os.log
class ViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// ビューの読み込み後に追加のセットアップを実行します。
}
@IBAction func loginButtonTapped(_ sender: UIButton) {
guard let username = usernameTextField.text, !username.isEmpty,
let password = passwordTextField.text, !password.isEmpty else {
// ユーザー名またはパスワードが空の場合の処理をします
return
}
// ユーザーの認証情報を記録します
os_log_info("Username: %@, Password: %@", log: .default, type: .info, username, password)
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にテキストフィールドをクリアします
usernameTextField.text = ""
passwordTextField.text = ""
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logger/logger.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoginPage(),
);
}
}
class LoginPage extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final logger = Logger(
printer: PrettyPrinter(),
output: FileOutput('login_log.txt'),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Username',
),
),
SizedBox(height: 10.0),
TextField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
_login(context);
},
child: Text('Login'),
),
],
),
),
);
}
void _login(BuildContext context) {
final username = _usernameController.text;
final password = _passwordController.text;
// ユーザーの認証情報を情報として記録します
logger.i('Username: $username, Password: $password');
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にテキストフィールドをクリアします
_usernameController.clear();
_passwordController.clear();
// オプションで、ログイン成功後に別の画面に移動したり、他のアクションを実行したりできます
}
}
推奨事項
- PII/PHIをローカルに保存する場合は、それらが暗号化され、暗号化キーがKeyChainに保存されていることを確認してください。
- PII情報へのアクセスは生体認証によって保護されるべきです。
- 該当する場合、PII情報をローカルにキャッシュすることは避け、代わりにバックエンドサーバーからクエリを実行してください。
- デバイス上の保持に業務上の必要性がなくなった場合は、PII/PHIを安全に削除してください。
- デバイスに保存する前に、PHI/PIIデータの暗号化またはハッシュ化を検討してください。
- PHI/PIIデータを保持することに対する同意を撤回する方法をユーザーに提供してください。
- PII/PHIデータをキャッシュしないでください。
- ユーザーデータにアクセスするサードパーティライブラリおよびAPIの使用を最小限に抑えてください。
- 一部の管轄区域では、個人情報へのアクセスに関するプライバシーポリシーの提供が義務付けられている場合があります。
- PII情報のログ記録が必要な場合は、PIIが本番環境のアプリケーションログに出力されないように、ログレベルをdebugに設定してください。
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// レイアウトにユーザー名とパスワード用のEditTextフィールドがあると仮定します
// それぞれのIDは'usernameEditText'と'passwordEditText'です
loginButton.setOnClickListener {
val username = usernameEditText.text.toString()
val password = passwordEditText.text.toString()
// ユーザーの認証情報を記録します
Log.d("UserCredentials", "Username: $username, Password: $password")
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にEditTextフィールドをクリアします
usernameEditText.text.clear()
passwordEditText.text.clear()
}
}
}
import UIKit
import os.log
class ViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// ビューの読み込み後に追加のセットアップを実行します。
}
@IBAction func loginButtonTapped(_ sender: UIButton) {
guard let username = usernameTextField.text, !username.isEmpty,
let password = passwordTextField.text, !password.isEmpty else {
// ユーザー名またはパスワードが空の場合の処理をします
return
}
// ユーザーの認証情報を記録します
os_log_debug("Username: %@, Password: %@", log: .default, type: .info, username, password)
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にテキストフィールドをクリアします
usernameTextField.text = ""
passwordTextField.text = ""
}
}
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:logger/logger.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoginPage(),
);
}
}
class LoginPage extends StatelessWidget {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
final logger = Logger(
printer: PrettyPrinter(),
output: FileOutput('login_log.txt'),
);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Padding(
padding: EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
TextField(
controller: _usernameController,
decoration: InputDecoration(
labelText: 'Username',
),
),
SizedBox(height: 10.0),
TextField(
controller: _passwordController,
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
),
SizedBox(height: 20.0),
ElevatedButton(
onPressed: () {
_login(context);
},
child: Text('Login'),
),
],
),
),
);
}
void _login(BuildContext context) {
final username = _usernameController.text;
final password = _passwordController.text;
// ユーザーの認証情報を情報として記録します
logger.d('Username: $username, Password: $password');
// ここでログインロジックを実行します
performLogin(username, password)
// ログイン後にテキストフィールドをクリアします
_usernameController.clear();
_passwordController.clear();
// オプションで、ログイン成功後に別の画面に移動したり、他のアクションを実行したりできます
}
}
リンク
標準
- OWASP_MASVS_L1:
- MSTG_ARCH_12
- OWASP_MASVS_L2:
- MSTG_ARCH_12
- GDPR:
- ART_32
- ART_25
- PCI_STANDARDS:
- REQ_3_2
- REQ_3_3
- REQ_3_5
- REQ_3_6
- REQ_3_7
- REQ_4_2
- REQ_6_2
- OWASP_MASVS_v2_1:
- MASVS_STORAGE_1
- MASVS_STORAGE_2
- SOC2_CONTROLS:
- CC_2_1
- CC_4_1
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5
- HIPAA_CONTROLS:
- SECURITY251
- SECURITY212
- SECURITY213