Clear text HTTP request
Solicitud HTTP en texto claro
Descripción
El tráfico HTTP en texto claro se refiere a los datos transmitidos entre una aplicación móvil y su servidor backend sin ningún cifrado, lo que lo hace fácilmente legible y susceptible de ser interceptado por actores malintencionados. Esta falta de cifrado plantea importantes riesgos de seguridad para las aplicaciones móviles. Cuando se envía información confidencial, como credenciales de inicio de sesión, datos personales o detalles financieros, a través de conexiones HTTP en texto claro, se vuelve vulnerable a las escuchas clandestinas y a los ataques de intermediario (man-in-the-middle).
Uno de los principales riesgos asociados con el tráfico HTTP en texto claro en las aplicaciones móviles es la posible exposición de los datos del usuario. Los atacantes pueden interceptar y extraer información valiosa, lo que lleva al robo de identidad, acceso no autorizado a cuentas y uso indebido de datos personales. Además, los atacantes pueden explotar estas vulnerabilidades para alterar el contenido de la aplicación o inyectar código malicioso en el canal de comunicación.
Además, el tráfico HTTP en texto claro puede comprometer la privacidad del usuario. Las aplicaciones móviles a menudo recopilan y transmiten datos de los usuarios con fines analíticos o de publicidad dirigida. Sin cifrado, estos datos son fácilmente accesibles a terceros, comprometiendo la confidencialidad de los usuarios y potencialmente dando lugar a violaciones de la privacidad.
Las aplicaciones móviles que no implementan protocolos de comunicación seguros también enfrentan el riesgo de ataques de suplantación de identidad. Los atacantes pueden crear redes Wi-Fi falsas o utilizar otras técnicas para interceptar el tráfico, haciéndose pasar por el servidor backend legítimo. Los usuarios envían sus datos sin saberlo al atacante, quien luego puede usarlos indebidamente con fines maliciosos.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(LoginApp());
class LoginApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login App',
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
String _loginStatus = '';
void _performLogin() async {
final String username = _usernameController.text.trim();
final String password = _passwordController.text.trim();
// Simulate an HTTP request for login.
final String apiUrl = 'http://example.com/login';
final Map<String, dynamic> requestBody = {
'username': username,
'password': password,
};
final http.Response response = await http.post(
Uri.parse(apiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode(requestBody),
);
if (response.statusCode == 200) {
// Successful login
setState(() {
_loginStatus = 'Login successful!';
});
} else {
// Failed login
setState(() {
_loginStatus = 'Login failed. Please try again.';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _performLogin,
child: Text('Login'),
),
SizedBox(height: 10),
Text(_loginStatus, textAlign: TextAlign.center),
],
),
),
);
}
}
import UIKit
class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginStatusLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func loginButtonTapped(_ sender: UIButton) {
guard let username = usernameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines),
let password = passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return
}
// Simulate an HTTP request for login.
let apiUrl = "http://example.com/login"
let requestBody: [String: Any] = [
"username": username,
"password": password
]
var request = URLRequest(url: URL(string: apiUrl)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
} catch {
print("Error creating JSON data: \(error)")
return
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login failed. Please try again."
}
return
}
guard let data = data,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login failed. Please try again."
}
return
}
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login successful!"
}
}
task.resume()
}
}
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_login.*
import org.json.JSONObject
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginButton.setOnClickListener {
val username = usernameEditText.text.trim().toString()
val password = passwordEditText.text.trim().toString()
// Simulate an HTTP request for login.
val apiUrl = "https://example.com/login"
val requestBody = JSONObject().apply {
put("username", username)
put("password", password)
}.toString()
Thread {
performLogin(apiUrl, requestBody)
}.start()
}
}
private fun performLogin(apiUrl: String, requestBody: String) {
try {
val url = URL(apiUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.doOutput = true
val outputStreamWriter = OutputStreamWriter(connection.outputStream)
outputStreamWriter.write(requestBody)
outputStreamWriter.flush()
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
runOnUiThread {
loginStatusLabel.text = "Login successful!"
}
} else {
runOnUiThread {
loginStatusLabel.text = "Login failed. Please try again."
}
}
} catch (e: Exception) {
Log.e("LoginActivity", "Error: ${e.message}")
runOnUiThread {
loginStatusLabel.text = "Login failed. Please try again."
}
}
}
}
Recomendación
Para mitigar los riesgos asociados con el tráfico HTTP en texto claro en aplicaciones móviles, es imperativo implementar protocolos de comunicación seguros y mejores prácticas. A continuación se presentan algunas recomendaciones detalladas para mejorar la seguridad de las comunicaciones de las aplicaciones móviles:
-
Adoptar cifrado HTTPS: El paso más fundamental es utilizar HTTPS (HTTP seguro) para toda la comunicación entre la aplicación móvil y el servidor backend. HTTPS cifra los datos durante la transmisión, garantizando que la información confidencial permanezca confidencial y segura. Obtenga un certificado SSL/TLS de una Autoridad de Certificación (CA) de confianza y exija conexiones HTTPS para todos los puntos finales de API y transferencias de datos.
-
Fijación de certificados (Certificate Pinning): Implemente la fijación de certificados para mejorar la seguridad de las conexiones HTTPS. Esto implica codificar (hardcode) el certificado SSL del servidor o su clave pública dentro de la aplicación móvil. Al hacerlo, la aplicación se asegura de que solo se comunique con el servidor previsto, lo que evita ataques de intermediarios en los que los atacantes intentan utilizar sus propios certificados.
-
Habilitar HSTS (HTTP Strict Transport Security): Utilice encabezados HSTS para indicar al navegador web de la aplicación que utilice siempre conexiones HTTPS, incluso si el usuario escribe por error "http://" en la URL. Esto evita cualquier posible ataque de degradación y garantiza que todas las comunicaciones estén cifradas.
-
Utilice la última versión de TLS: Asegúrese de que la aplicación móvil utilice la última versión del protocolo TLS (Seguridad de la capa de transporte) para aprovechar las características de seguridad y los algoritmos de cifrado más sólidos.
-
Implementar verificación de certificados: Configure la aplicación para verificar si hay certificados SSL revocados o vencidos para evitar interacciones con servidores potencialmente comprometidos.
-
Cifrado de datos confidenciales: Cifre los datos confidenciales almacenados en el dispositivo o transmitidos al servidor. Utilice algoritmos de cifrado sólidos y administre de forma segura las claves de cifrado, esto puede agregar una capa adicional de seguridad a la transmisión de datos.
-
Transmisión segura de datos para todas las API: Evalúe todas las API utilizadas por la aplicación y asegúrese de que también utilicen HTTPS para la transmisión de datos. Esto incluye API y servicios de terceros integrados en la aplicación.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() => runApp(LoginApp());
class LoginApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Login App',
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
String _loginStatus = '';
void _performLogin() async {
final String username = _usernameController.text.trim();
final String password = _passwordController.text.trim();
// Simulate a secure HTTPS request for login (Replace this with your actual secure API endpoint).
final String apiUrl = 'https://www.example.com/login';
final Map<String, dynamic> requestBody = {
'username': username,
'password': password,
};
final http.Response response = await http.post(
Uri.parse(apiUrl),
headers: {'Content-Type': 'application/json'},
body: json.encode(requestBody),
);
if (response.statusCode == 200) {
// Successful login
setState(() {
_loginStatus = 'Login successful!';
});
} else {
// Failed login
setState(() {
_loginStatus = 'Login failed. Please try again.';
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login Page'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
TextFormField(
controller: _usernameController,
decoration: InputDecoration(labelText: 'Username'),
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(labelText: 'Password'),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _performLogin,
child: Text('Login'),
),
SizedBox(height: 10),
Text(_loginStatus, textAlign: TextAlign.center),
],
),
),
);
}
}
import UIKit
class LoginViewController: UIViewController {
@IBOutlet weak var usernameTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginStatusLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func loginButtonTapped(_ sender: UIButton) {
guard let username = usernameTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines),
let password = passwordTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return
}
// Simulate a secure HTTPS request for login (Replace this with your actual secure API endpoint).
let apiUrl = "https://www.example.com/login"
let requestBody: [String: Any] = [
"username": username,
"password": password
]
var request = URLRequest(url: URL(string: apiUrl)!)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: requestBody, options: [])
} catch {
print("Error creating JSON data: \(error)")
return
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Error: \(error)")
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login failed. Please try again."
}
return
}
guard let data = data,
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login failed. Please try again."
}
return
}
DispatchQueue.main.async {
self.loginStatusLabel.text = "Login successful!"
}
}
task.resume()
}
}
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_login.*
import org.json.JSONObject
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
class LoginActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginButton.setOnClickListener {
val username = usernameEditText.text.trim().toString()
val password = passwordEditText.text.trim().toString()
// Simulate a secure HTTPS request for login (Replace this with your actual secure API endpoint).
val apiUrl = "https://www.example.com/login"
val requestBody = JSONObject().apply {
put("username", username)
put("password", password)
}.toString()
Thread {
performLogin(apiUrl, requestBody)
}.start()
}
}
private fun performLogin(apiUrl: String, requestBody: String) {
try {
val url = URL(apiUrl)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.doOutput = true
val outputStreamWriter = OutputStreamWriter(connection.outputStream)
outputStreamWriter.write(requestBody)
outputStreamWriter.flush()
val responseCode = connection.responseCode
if (responseCode == HttpURLConnection.HTTP_OK) {
runOnUiThread {
loginStatusLabel.text = "Login successful!"
}
} else {
runOnUiThread {
loginStatusLabel.text = "Login failed. Please try again."
}
}
} catch (e: Exception) {
Log.e("LoginActivity", "Error: ${e.message}")
runOnUiThread {
loginStatusLabel.text = "Login failed. Please try again."
}
}
}
}
Enlaces
- Missing Encryption of Sensitive Data (CWE-311)
- Testing for Weak SSL/TLS Ciphers, Insufficient Transport Layer Protection
- Top 10 2013-A6-Sensitive Data Exposure
- Insufficient Transport Layer
- Cleartext Transmission of Sensitive Information (CWE-319)
Estándares
- OWASP_ASVS_L1:
- V9_1_1
- OWASP_ASVS_L2:
- V1_9_1
- V9_1_1
- OWASP_ASVS_L3:
- V1_9_1
- V9_1_1
- PCI_STANDARDS:
- REQ_2_2
- REQ_4_2
- REQ_6_2
- REQ_6_3
- REQ_6_4
- REQ_11_3
- SOC2_CONTROLS:
- CC_2_1
- CC_4_1
- CC_6_7
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5
- HIPAA_CONTROLS:
- SECURITY252
- SECURITY212
- SECURITY213