コンテンツにスキップ

Clear text HTTP request

平文のHTTPリクエスト

説明

平文のHTTPトラフィックとは、モバイルアプリとバックエンドサーバー間で暗号化されずに送信されるデータのことであり、容易に読み取ることができ、悪意のある攻撃者による傍受の被害を受けやすくなります。この暗号化の欠如は、モバイルアプリに重大なセキュリティリスクをもたらします。ログイン資格情報、個人データ、財務の詳細などの機密情報が平文のHTTP接続を介して送信されると、盗聴や中間者攻撃(man-in-the-middle)に対して脆弱になります。

モバイルアプリの平文のHTTPトラフィックに関連する主なリスクの1つは、ユーザーデータが公開される可能性があることです。攻撃者は価値のある情報を傍受して抽出し、個人情報の盗難、アカウントへの不正アクセス、個人データの悪用につながる可能性があります。さらに、攻撃者はこれらの脆弱性を悪用してアプリのコンテンツを改ざんしたり、通信チャネルに悪意のあるコードを挿入したりする可能性があります。

さらに、平文のHTTPトラフィックはユーザーのプライバシーを侵害する可能性があります。モバイルアプリは、分析やターゲット広告の目的でユーザーデータを収集して送信することがよくあります。暗号化されていないと、このデータは第三者から簡単にアクセスでき、ユーザーの機密性が損なわれ、プライバシー違反につながる可能性があります。

安全な通信プロトコルを実装していないモバイルアプリは、なりすまし攻撃のリスクにも直面します。攻撃者は偽のWi-Fiネットワークを作成するか、他の手法を使用してトラフィックを傍受し、正規のバックエンドサーバーになりすますことができます。ユーザーは気付かずに自分のデータを攻撃者に送信し、攻撃者はそれを悪意のある目的で悪用する可能性があります。

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."
            }
        }
    }
}

推奨事項

モバイルアプリでの平文HTTPトラフィックに関連するリスクを軽減するには、安全な通信プロトコルとベストプラクティスを実装することが不可欠です。モバイルアプリの通信のセキュリティを強化するための詳細な推奨事項を以下に示します。

  • HTTPS暗号化の採用:最も基本的な手順は、モバイルアプリとバックエンドサーバー間のすべての通信にHTTPS(HTTP Secure)を使用することです。HTTPSは送信中にデータを暗号化し、機密情報が機密かつ安全に保たれるようにします。信頼できる認証局(CA)からSSL/TLS証明書を取得し、すべてのAPIエンドポイントとデータ転送にHTTPS接続を適用します。

  • 証明書のピン留め(Certificate Pinning):HTTPS接続のセキュリティを強化するために証明書のピン留めを実装します。これには、サーバーのSSL証明書またはその公開鍵をモバイルアプリ内にハードコーディングすることが含まれます。これにより、アプリは意図したサーバーとのみ通信することが保証され、攻撃者が独自の証明書を使用しようとする中間者攻撃を防ぐことができます。

  • HSTS(HTTP Strict Transport Security)の有効化:ユーザーが誤ってURLに「http://」と入力した場合でも、HSTSヘッダーを使用してアプリのWebブラウザーに常にHTTPS接続を使用するように指示します。これにより、潜在的なダウングレード攻撃を防ぎ、すべての通信が確実に暗号化されます。

  • 最新のTLSバージョンの使用:モバイルアプリが最新バージョンのTLS(Transport Layer Security)プロトコルを使用して、最も堅牢なセキュリティ機能と暗号化アルゴリズムを活用していることを確認します。

  • 証明書チェックの実装:侵害された可能性のあるサーバーとのやり取りを避けるために、失効または期限切れのSSL証明書がないかチェックするようにアプリを構成します。

  • 機密データの暗号化:デバイスに保存されているかサーバーに送信される機密データを暗号化します。強力な暗号化アルゴリズムを利用し、暗号化キーを安全に管理することで、データ送信にさらなるセキュリティ層を追加できます。

  • すべてのAPIの安全なデータ送信:アプリで使用されるすべてのAPIを評価し、それらがデータ送信にHTTPSを使用していることも確認します。これには、サードパーティのAPIやアプリに統合されたサービスが含まれます。

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."
            }
        }
    }
}

リンク

標準

  • 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