Insecure Storage of Application Data
アプリケーションデータの安全でない保存
説明
モバイルアプリケーションにおける安全でない保存(Insecure storage)の脆弱性とは、ユーザーの認証情報、個人情報、またはその他の機密データなどの機密データが、デバイス上の誰でも読み取り可能な場所(world-readable)に保存されるというセキュリティ上の欠陥を指します。これにより、デバイス上の他のアプリケーション、またはデバイスへの物理的アクセスを持つ悪意のあるアクターによる不正アクセスに対して、データが脆弱な状態になる可能性があります。
安全でない保存は、アプリケーションが誰でも読み取り可能な場所に書き込む場合にのみ発生するわけではありません。アプリケーションが設定ファイルなどのデータを誰でも書き込み可能な場所(world-writable)から読み込む場合にも発生します。このような場所では、悪意のあるアクターが設定ファイルを改ざんし、結果としてアプリケーションの機能を改ざんする可能性があります。
import android.os.Bundle
import android.os.Environment
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStreamWriter
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val loginButton: Button = findViewById(R.id.login_button)
loginButton.setOnClickListener {
// Dummy authentication process
val token = authenticate("username", "password") // authentication logic
// Store token in public storage
storeTokenInPublicStorage(token)
}
}
private fun storeTokenInPublicStorage(token: String) {
val filePath = "/sdcard/insecure_app/jwt_config.txt"
val file = File(filePath)
try {
val outputStreamWriter = OutputStreamWriter(FileOutputStream(file))
outputStreamWriter.write(token)
outputStreamWriter.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let loginButton = UIButton(type: .system)
loginButton.setTitle("Login", for: .normal)
loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
view.addSubview(loginButton)
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc func loginButtonTapped() {
// Dummy authentication process
let token = authenticate(username: "username", password: "password")
// Store token in public storage
storeTokenInPublicStorage(token: token)
}
private func storeTokenInPublicStorage(token: String) {
let filePath = "/var/mobile/Media/insecure_app/jwt_config.txt"
let fileURL = URL(fileURLWithPath: filePath)
do {
try token.write(to: fileURL, atomically: true, encoding: .utf8)
print("Token stored successfully.")
} catch {
print("Failed to write token: \(error)")
}
}
}
import 'dart:io';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Insecure Storage'),
),
body: Center(
child: ElevatedButton(
onPressed: () {
// Dummy authentication process
String token = authenticate("username", "password");
// Store token in public storage
storeTokenInPublicStorage(token);
},
child: Text('Login'),
),
),
);
}
void storeTokenInPublicStorage(String token) {
final directory = Directory('/sdcard/insecure_app/');
directory.createSync(recursive: true);
final file = File('${directory.path}/jwt_config.txt');
try {
file.writeAsStringSync(token);
print('Token stored successfully.');
} catch (e) {
print('Failed to write token: $e');
}
}
}
推奨事項
- Use Secure Storage: 認証トークンや認証情報などの機密データを安全に保存します。iOS の Keychain や Android の暗号化された SharedPreferences など、プラットフォームによって提供される安全なストレージメカニズムを使用します。
- Data Encryption: ユーザーのデバイスに保存されているデータを保護するために、強力な暗号化アルゴリズムを使用して保存データ(Data at rest)を暗号化します。
- Avoid Hardcoding Sensitive Information: ユーザー名、パスワード、トークンなどの機密情報をコードに直接ハードコーディングする代わりに、上記で提案された安全なストレージメカニズムを検討してください。
- Avoid Permissive File Permissions: 機密性の高いファイルやディレクトリへのアクセスを制限するために、ファイルの権限が適切に設定されていることを確認してください。例えば Android では、ファイルを作成する際に
MODE_WORLD_READABLEやMODE_WORLD_WRITEABLEを使用することは避けてください。これらはすべてのアプリケーションに対して広範な読み取りまたは書き込みアクセスを許可するためです。ファイルの権限を設定する際は常に最小特権の原則に従い、アプリケーションの機能に必要なものだけにアクセスを制限してください。 - Input Validation: パブリックストレージの場所からデータを読み込む場合は、常にそのデータを検証(validate)および無害化(sanitize)するようにしてください。
import android.content.Context
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import java.io.IOException
class MainActivity : AppCompatActivity() {
private lateinit var encryptedSharedPreferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Initialize encrypted shared preferences
initializeEncryptedSharedPreferences()
val loginButton: Button = findViewById(R.id.login_button)
loginButton.setOnClickListener {
// Dummy authentication process
val token = authenticate("username", "password") // authentication logic
// Store token securely in encrypted shared preferences
storeTokenInSharedPreferences(token)
}
}
private fun initializeEncryptedSharedPreferences() {
try {
val masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC)
encryptedSharedPreferences = EncryptedSharedPreferences.create(
"secure_preferences",
masterKeyAlias,
applicationContext,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
} catch (e: IOException) {
e.printStackTrace()
}
}
private fun storeTokenInSharedPreferences(token: String) {
encryptedSharedPreferences.edit().putString("jwt_token", token).apply()
Toast.makeText(this, "Token stored securely.", Toast.LENGTH_SHORT).show()
}
}
import UIKit
import Security
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let loginButton = UIButton(type: .system)
loginButton.setTitle("Login", for: .normal)
loginButton.addTarget(self, action: #selector(loginButtonTapped), for: .touchUpInside)
view.addSubview(loginButton)
loginButton.translatesAutoresizingMaskIntoConstraints = false
loginButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
loginButton.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
@objc func loginButtonTapped() {
// Dummy authentication process
let token = authenticate(username: "username", password: "password")
// Store token securely in Keychain
storeTokenInKeychain(token: token)
}
private func storeTokenInKeychain(token: String) {
let keychainQuery: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrAccount as String: "jwtToken",
kSecValueData as String: token.data(using: .utf8)!,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(keychainQuery as CFDictionary, nil)
if status == errSecSuccess {
print("Token stored securely in Keychain.")
} else {
print("Failed to store token in Keychain: \(status)")
}
}
}
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
final storage = FlutterSecureStorage();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Secure Storage Example'),
),
body: Center(
child: ElevatedButton(
onPressed: () async {
// Dummy authentication process
String token = await authenticate("username", "password");
// Store token securely
await storeTokenInSecureStorage(token);
},
child: Text('Login'),
),
),
);
}
Future<void> storeTokenInSecureStorage(String token) async {
await storage.write(key: 'jwtToken', value: token);
print('Token stored securely.');
}
}
リンク
- OWASP Mobile Top 10
- CWE-200: Information Exposure
- CWE-359: Exposure of Private Information ("Privacy Violation")
標準
- OWASP_MASVS_L1:
- MSTG_ARCH_4
- MSTG_ARCH_12
- OWASP_MASVS_L2:
- MSTG_ARCH_4
- 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_6_1
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5
- CNIL_FOR_EDITORS:
- EDITORS_4_3_3
- HIPAA_CONTROLS:
- SECURITY251
- SECURITY212
- SECURITY213