URL Manipulation
Manipulation d'URL
Description
Lorsqu'un attaquant parvient à contrôler l'URL utilisée par une application pour récupérer du contenu, cela introduit un risque de sécurité critique. En manipulant l'URL, l'attaquant peut rediriger l'application vers un serveur malveillant ou injecter son propre contenu dans la réponse. Cela peut entraîner diverses vulnérabilités de sécurité et des conséquences potentielles. Par exemple :
- Exécution de code : L'attaquant peut modifier l'URL pour pointer vers un serveur qu'il contrôle, lui permettant ainsi de diffuser du code malveillant à l'application. Ce code pourrait exploiter des vulnérabilités de l'application, exécuter des commandes arbitraires ou installer des logiciels malveillants sur le système de l'utilisateur.
- Vol de données : En redirigeant l'application vers un serveur frauduleux, l'attaquant peut tromper les utilisateurs et les inciter à saisir des informations sensibles telles que des identifiants de connexion, des détails de carte de crédit ou des données personnelles, qui peuvent ensuite être capturées et utilisées à des fins malveillantes.
- Manipulation de contenu : L'attaquant peut modifier le contenu récupéré par l'application, altérant ainsi les informations affichées ou injectant des scripts malveillants. Cela peut avoir diverses conséquences, telles que l'affichage d'informations trompeuses, la défiguration de pages Web ou la conduite d'attaques de phishing.
- Attaques de la chaîne d'approvisionnement : Si l'application récupère du contenu à partir de sources externes, telles que des bibliothèques ou des plugins, le contrôle de l'URL peut permettre à l'attaquant de remplacer des ressources légitimes par des versions compromises ou malveillantes. Cela peut compromettre la sécurité de l'ensemble du système.
Vous trouverez ci-dessous un exemple de code d'une application récupérant une entrée contrôlée par l'attaquant :
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
}
}
Recommandation
Il est crucial de prendre des mesures proactives pour protéger vos applications contre les attaques par manipulation d'URL. Considérez les recommandations suivantes :
- Validation des entrées : Mettez en œuvre des mécanismes stricts de validation des entrées pour vous assurer que les URL utilisées pour la récupération de contenu sont correctement formatées et respectent les modèles attendus. Cela peut inclure la vérification des schémas d'URL valides, l'application de noms de domaine attendus et la validation des paramètres de requête.
- Approche par liste blanche : Maintenez une liste blanche de domaines ou de sources de confiance à partir desquels votre application récupère du contenu. N'autorisez que les requêtes vers ces sources de confiance afin de minimiser le risque d'accès à du contenu malveillant ou non autorisé.
- Contrôles de l'intégrité du contenu : Implémentez des mécanismes pour vérifier l'intégrité du contenu récupéré. Calculez et comparez les hachages cryptographiques ou les signatures numériques du contenu reçu par rapport aux valeurs attendues afin de détecter toute modification ou falsification.
En mettant en œuvre ces recommandations, vous pouvez améliorer la sécurité de vos applications, protéger les données des utilisateurs et atténuer les risques associés aux attaques par manipulation d'URL.
Vous trouverez ci-dessous un exemple de code mettant en œuvre la validation par liste blanche :
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
}
}
Liens
Normes
- 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