URL Manipulation
URLの操作
説明
攻撃者が、アプリケーションがコンテンツを取得するために使用するURLの制御を獲得すると、重大なセキュリティリスクが生じます。URLを操作することで、攻撃者はアプリケーションを悪意のあるサーバーにリダイレクトしたり、独自のコンテンツをレスポンスに注入したりすることができます。これにより、さまざまなセキュリティ脆弱性および潜在的な結果がもたらされる可能性があります。例えば:
- コード実行:攻撃者はURLを変更して自分が制御するサーバーを指すようにし、悪意のあるコードをアプリケーションに配信できる可能性があります。このコードは、アプリケーションの脆弱性を悪用したり、任意のコマンドを実行したり、ユーザーのシステムにマルウェアをインストールしたりする可能性があります。
- データ窃取:アプリケーションを不正なサーバーにリダイレクトすることで、攻撃者はユーザーを騙してログイン認証情報、クレジットカードの詳細、個人データなどの機密情報を入力させ、それをキャプチャして悪用することができます。
- コンテンツの操作:攻撃者は、アプリケーションによって取得されたコンテンツを変更し、表示される情報を改ざんしたり、悪意のあるスクリプトを注入したりすることができます。これにより、誤解を招く情報の表示、ウェブページの改ざん、フィッシング攻撃の実行など、さまざまな結果が生じる可能性があります。
- サプライチェーン攻撃:アプリケーションがライブラリやプラグインなどの外部ソースからコンテンツを取得する場合、URLを制御することで、攻撃者は正当なリソースを侵害されたバージョンや悪意のあるバージョンに置き換えることができます。これにより、システム全体のセキュリティが損なわれる可能性があります。
以下は、攻撃者が制御する入力を取得するアプリケーションのサンプルコードです:
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
}
}
推奨事項
URL操作攻撃からアプリケーションを保護するためには、予防的な対策を講じることが重要です。以下の推奨事項を検討してください:
- 入力検証:厳格な入力検証メカニズムを実装し、コンテンツ取得に使用されるURLが適切にフォーマットされ、予想されるパターンに準拠していることを確認します。これには、有効なURLスキームの確認、予想されるドメイン名の適用、クエリパラメータの検証などが含まれます。
- ホワイトリストアプローチ:アプリケーションがコンテンツを取得する、信頼できるドメインまたはソースのホワイトリストを維持します。悪意のあるコンテンツまたは許可されていないコンテンツにアクセスするリスクを最小限に抑えるため、これらの信頼できるソースへのリクエストのみを許可します。
- コンテンツの整合性チェック:取得したコンテンツの整合性を検証するメカニズムを実装します。受信したコンテンツの暗号化ハッシュまたはデジタル署名を計算し、予想される値と比較することで、変更や改ざんを検出します。
これらの推奨事項を実施することで、アプリケーションのセキュリティを強化し、ユーザーデータを保護し、URL操作攻撃に関連するリスクを軽減することができます。
以下は、ホワイトリスト検証を実装するサンプルコードです:
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
}
}
リンク
基準
- 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