URL Manipulation
Manipulación de URL
Descripción
Cuando un atacante obtiene el control sobre la URL utilizada por una aplicación para recuperar contenido, se introduce un riesgo de seguridad crítico. Al manipular la URL, el atacante puede redirigir la aplicación a un servidor malicioso o inyectar su propio contenido en la respuesta. Esto puede dar lugar a diversas vulnerabilidades de seguridad y consecuencias potenciales. Por ejemplo:
- Ejecución de código: El atacante puede alterar la URL para que apunte a un servidor que controla, lo que le permite enviar código malicioso a la aplicación. Este código podría explotar vulnerabilidades en la aplicación, ejecutar comandos arbitrarios o instalar malware en el sistema del usuario.
- Robo de datos: Al redirigir la aplicación a un servidor fraudulento, el atacante puede engañar a los usuarios para que ingresen información confidencial, como credenciales de inicio de sesión, detalles de tarjetas de crédito o datos personales, que luego pueden ser capturados y utilizados indebidamente.
- Manipulación de contenido: El atacante puede modificar el contenido recuperado por la aplicación, alterando la información mostrada o inyectando scripts maliciosos. Esto puede generar diversas consecuencias, como mostrar información engañosa, desfigurar páginas web o realizar ataques de phishing.
- Ataques a la cadena de suministro: Si la aplicación recupera contenido de fuentes externas, como bibliotecas o complementos, controlar la URL puede permitir al atacante reemplazar recursos legítimos por versiones comprometidas o maliciosas. Esto puede comprometer la seguridad de todo el sistema.
A continuación se muestra un código de muestra de una aplicación que recupera entradas controladas por un atacante:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class MainActivity extends StatefulWidget {
final String url;
MainActivity({required this.url});
@override
_MainActivityState createState() => _MainActivityState();
}
class _MainActivityState extends State<MainActivity> {
String? content;
@override
void initState() {
super.initState();
fetchContent(widget.url);
}
Future<void> fetchContent(String url) async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
setState(() {
content = response.body;
});
// TODO: Process the fetched content here
} else {
// Handle error response
}
} catch (e) {
// Handle network or API call error
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fetching URL Content'),
),
body: Center(
child: content != null
? Text(content!)
: CircularProgressIndicator(),
),
);
}
}
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Handle deep link URL if the app was launched from a URL
if let url = launchOptions?[.url] as? URL {
handleDeepLink(url)
}
return true
}
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// Handle deep link URL when the app is already running
handleDeepLink(url)
return true
}
private func handleDeepLink(_ url: URL) {
// Check if the URL scheme is "ostorlab"
if url.scheme == "ostorlab" {
// Extract the URL from the deep link
if let deepLinkURL = URL(string: url.absoluteString.replacingOccurrences(of: "ostorlab://", with: "")) {
// Fetch the content from the provided URL
fetchContent(deepLinkURL)
}
}
}
private func fetchContent(_ url: URL) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error fetching content: \(error.localizedDescription)")
return
}
if let data = data, let content = String(data: data, encoding: .utf8) {
// Process the fetched content here
print("Fetched content: \(content)")
}
}.resume()
}
}
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get the URL from the intent
val intent: Intent = intent
val url: String? = intent.getStringExtra("url")
if (url != null) {
// Fetch the content from the provided URL
GlobalScope.launch(Dispatchers.IO) {
val content: String? = fetchContent(url)
// Process the fetched content as needed
if (content != null) {
// TODO: Process the fetched content here
}
}
}
}
private fun fetchContent(urlString: String): String? {
var connection: HttpURLConnection? = null
var reader: BufferedReader? = null
try {
val url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
// Check if the response code is successful
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = StringBuilder()
var line: String?
// Read the response line by line
while (reader.readLine().also { line = it } != null) {
response.append(line)
}
return response.toString()
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
// Close the connection and reader
connection?.disconnect()
reader?.close()
}
return null
}
}
Recomendación
Es fundamental tomar medidas proactivas para proteger sus aplicaciones contra los ataques de manipulación de URL. Considere las siguientes recomendaciones:
- Validación de entrada: Implemente mecanismos estrictos de validación de entrada para garantizar que las URL utilizadas para la recuperación de contenido tengan el formato adecuado y se adhieran a los patrones esperados. Esto puede incluir la comprobación de esquemas de URL válidos, la aplicación de nombres de dominio esperados y la validación de los parámetros de consulta.
- Enfoque de lista blanca: Mantenga una lista blanca de dominios o fuentes confiables de las cuales su aplicación recupera contenido. Permita únicamente las solicitudes a estas fuentes confiables para minimizar el riesgo de acceder a contenido malicioso o no autorizado.
- Comprobaciones de integridad del contenido: Implemente mecanismos para verificar la integridad del contenido recuperado. Calcule y compare los hash criptográficos o las firmas digitales del contenido recibido frente a los valores esperados para detectar cualquier modificación o alteración.
Al implementar estas recomendaciones, puede mejorar la seguridad de sus aplicaciones, proteger los datos del usuario y mitigar los riesgos asociados con los ataques de manipulación de URL.
A continuación, se muestra un código de muestra que implementa la validación de lista blanca:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class MainActivity extends StatefulWidget {
final String url;
final List<String> allowedDomains;
MainActivity({required this.url, required this.allowedDomains});
@override
_MainActivityState createState() => _MainActivityState();
}
class _MainActivityState extends State<MainActivity> {
String? content;
@override
void initState() {
super.initState();
if (isDomainAllowed(widget.url)) {
fetchContent(widget.url);
} else {
// Handle unauthorized domain
// TODO: Implement appropriate action for unauthorized domain
}
}
bool isDomainAllowed(String urlString) {
Uri uri = Uri.parse(urlString);
String domain = uri.host ?? '';
return widget.allowedDomains.contains(domain);
}
Future<void> fetchContent(String url) async {
try {
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
setState(() {
content = response.body;
});
// TODO: Process the fetched content here
} else {
// Handle error response
}
} catch (e) {
// Handle network or API call error
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Fetching URL Content'),
),
body: Center(
child: content != null
? Text(content!)
: CircularProgressIndicator(),
),
);
}
}
import UIKit
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let allowedDomains = ["example.com", "trusteddomain.com"] // Add your trusted domain names here
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Handle deep link URL if the app was launched from a URL
if let url = launchOptions?[.url] as? URL {
handleDeepLink(url)
}
return true
}
func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
// Handle deep link URL when the app is already running
handleDeepLink(url)
return true
}
private func handleDeepLink(_ url: URL) {
// Check if the URL scheme is "ostorlab"
if url.scheme == "ostorlab" {
// Extract the URL from the deep link
if let deepLinkURL = URL(string: url.absoluteString.replacingOccurrences(of: "ostorlab://", with: "")) {
// Verify the domain name against the whitelist
if isDomainAllowed(deepLinkURL) {
// Fetch the content from the provided URL
fetchContent(deepLinkURL)
} else {
// Handle unauthorized domain
// TODO: Implement appropriate action for unauthorized domain
}
}
}
}
private func isDomainAllowed(_ url: URL) -> Bool {
guard let host = url.host else {
return false
}
return allowedDomains.contains(host)
}
private func fetchContent(_ url: URL) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error fetching content: \(error.localizedDescription)")
return
}
if let data = data, let content = String(data: data, encoding: .utf8) {
// Process the fetched content here
print("Fetched content: \(content)")
}
}.resume()
}
}
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class MainActivity : AppCompatActivity() {
private val allowedDomains = listOf("example.com", "trusteddomain.com") // Add your trusted domain names here
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Get the URL from the intent
val intent: Intent = intent
val url: String? = intent.getStringExtra("url")
if (url != null) {
// Verify the domain name against the whitelist
if (isDomainAllowed(url)) {
// Fetch the content from the provided URL
GlobalScope.launch(Dispatchers.IO) {
val content: String? = fetchContent(url)
// Process the fetched content as needed
if (content != null) {
// TODO: Process the fetched content here
}
}
} else {
// Handle unauthorized domain
// TODO: Implement appropriate action for unauthorized domain
}
}
}
private fun isDomainAllowed(urlString: String): Boolean {
val url = URL(urlString)
val domain = url.host
return allowedDomains.contains(domain)
}
private fun fetchContent(urlString: String): String? {
var connection: HttpURLConnection? = null
var reader: BufferedReader? = null
try {
val url = URL(urlString)
connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
// Check if the response code is successful
if (connection.responseCode == HttpURLConnection.HTTP_OK) {
reader = BufferedReader(InputStreamReader(connection.inputStream))
val response = StringBuilder()
var line: String?
// Read the response line by line
while (reader.readLine().also { line = it } != null) {
response.append(line)
}
return response.toString()
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
// Close the connection and reader
connection?.disconnect()
reader?.close()
}
return null
}
}
Enlaces
Estándares
- OWASP_MASVS_L2:
- MSTG_NETWORK_5
- PCI_STANDARDS:
- REQ_6_2
- REQ_6_3
- REQ_11_3
- OWASP_MASVS_v2_1:
- MASVS_CODE_3
- SOC2_CONTROLS:
- CC_2_1
- CC_4_1
- CC_7_1
- CC_7_2
- CC_7_4
- CC_7_5
- HIPAA_CONTROLS:
- SECURITY212
- SECURITY213
- SECURITY221