跳转至

Clear text HTTP request

明文HTTP请求

描述

明文HTTP流量是指在移动应用程序与其后端服务器之间传输的没有任何加密的数据,这使得数据易于阅读并容易被恶意行为者拦截。缺乏加密给移动应用程序带来了重大的安全风险。当登录凭据、个人数据或财务详细信息等敏感信息通过明文HTTP连接发送时,它很容易受到窃听和中间人攻击。

与移动应用程序中的明文HTTP流量相关的主要风险之一是用户数据的潜在暴露。攻击者可以拦截并提取有价值的信息,从而导致身份盗用、对帐户的未经授权访问以及个人数据的滥用。此外,攻击者可以利用这些漏洞篡改应用程序的内容或将恶意代码注入通信通道。

此外,明文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严格传输安全):利用HSTS标头指示应用程序的Web浏览器始终使用HTTPS连接,即使用户错误地在URL中输入“http://”。这可防止任何潜在的降级攻击,并确保所有通信均已加密。

  • 使用最新的TLS版本:确保移动应用程序使用最新版本的TLS(传输层安全性)协议,以利用最强大的安全功能和加密算法。

  • 实现证书检查:配置应用程序以检查已吊销或过期的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