URL Manipulation
URL Manipulation
Description
When an attacker gains control over the URL used by an application to fetch content, it introduces a critical security risk. By manipulating the URL, the attacker can redirect the application to a malicious server or inject their own content into the response. This can lead to various security vulnerabilities and potential consequences. For instance:
- Code Execution: The attacker may alter the URL to point to a server they control, enabling them to deliver malicious code to the application. This code could exploit vulnerabilities in the application, execute arbitrary commands, or install malware on the user's system.
- Data Theft: By redirecting the application to a fraudulent server, the attacker can trick users into entering sensitive information such as login credentials, credit card details, or personal data, which can then be captured and misused.
- Content Manipulation: The attacker can modify the content retrieved by the application, altering the displayed information or injecting malicious scripts. This can lead to various consequences such as displaying misleading information, defacing webpages, or conducting phishing attacks.
- Supply Chain Attacks: If the application fetches content from external sources, such as libraries or plugins, controlling the URL can allow the attacker to replace legitimate resources with compromised or malicious versions. This can compromise the security of the entire system.
Below is a sample code of an application fetching attacker controlled input:
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
}
}
Recommendation
It is crucial to take proactive measures to protect your applications from URL manipulation attacks. Consider the following recommendations:
- Input Validation: Implement strict input validation mechanisms to ensure that URLs used for content fetching are properly formatted and adhere to expected patterns. This can include checking for valid URL schemes, enforcing expected domain names, and validating query parameters.
- Whitelist Approach: Maintain a whitelist of trusted domains or sources from which your application fetches content. Only allow requests to these trusted sources to minimize the risk of accessing malicious or unauthorized content.
- Content Integrity Checks: Implement mechanisms to verify the integrity of the fetched content. Calculate and compare cryptographic hashes or digital signatures of the received content against expected values to detect any modifications or tampering.
By implementing these recommendations, you can enhance the security of your applications, protect user data, and mitigate the risks associated with URL manipulation attacks.
Below is sample code implement whitelist validation:
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
}
}
Links
Standards
- 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