Insecure Storage of Application Data
Almacenamiento inseguro de datos de la aplicación
Descripción
La vulnerabilidad de almacenamiento inseguro en aplicaciones móviles se refiere a un fallo de seguridad en el que datos sensibles, como credenciales de usuario, información personal u otros datos confidenciales, se almacenan en el dispositivo en ubicaciones de lectura pública (world-readable). Esto puede dejar los datos vulnerables al acceso no autorizado por parte de otras aplicaciones en el dispositivo o por un actor malicioso con acceso físico al dispositivo.
El almacenamiento inseguro no solo ocurre cuando la aplicación escribe en ubicaciones de lectura pública, sino también cuando la aplicación carga datos, como archivos de configuración, desde ubicaciones de escritura pública (world-writable), donde un actor malicioso puede alterar los archivos de configuración y, consecuentemente, manipular la funcionalidad de la aplicación.
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');
}
}
}
Recomendación
- Use Secure Storage: Almacene datos sensibles, como tokens de autenticación o credenciales, de forma segura. Utilice los mecanismos de almacenamiento seguro proporcionados por la plataforma, como Keychain para iOS y SharedPreferences con cifrado para Android.
- Data Encryption: Cifre los datos sensibles en reposo utilizando algoritmos de cifrado robustos para proteger los datos almacenados en el dispositivo del usuario.
- Avoid Hardcoding Sensitive Information: En lugar de codificar de forma rígida (hardcoding) información sensible como nombres de usuario, contraseñas o tokens directamente en su código, considere los mecanismos de almacenamiento seguro sugeridos anteriormente.
- Avoid Permissive File Permissions: Asegúrese de que los permisos de archivo estén configurados adecuadamente para restringir el acceso a archivos y directorios sensibles. En Android, por ejemplo, evite utilizar
MODE_WORLD_READABLEoMODE_WORLD_WRITEABLEal crear archivos, ya que otorgan un amplio acceso de lectura o escritura a todas las aplicaciones. Siga siempre el principio de mínimo privilegio al establecer los permisos de archivo y limite el acceso únicamente a lo necesario para la funcionalidad de la aplicación. - Input Validation: Al cargar datos desde ubicaciones de almacenamiento público, asegúrese siempre de validarlos y sanearlos.
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.');
}
}
Enlaces
- OWASP Mobile Top 10
- CWE-200: Information Exposure
- CWE-359: Exposure of Private Information ("Privacy Violation")
Estándares
- 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