Критичность: ИНФО
Способ обнаружения: DAST, SENSITIVE INFO

Описание

При правильной реализации защиты канала связи, с использованием корректно реализованного SSLPinnig передача конфиденциальной информации в HTTPS-запросах не является уязвимостью. Однако, неправильно построенная защита канала связи может быть подвергнута атаке MiTM (“Man in the Middle”, “человек по середине”).

Использование протокола HTTPS, основанного на HTTP и SSL / TLS, позволяет защитить передаваемые данные от несанкционированного доступа и изменения. Рекомендуется использовать HTTPS для всех случаев передачи ценной информации между клиентом и сервером, в частности, для страницы логина и всех страниц, требующих аутентификации.

Рекомендации

В идеале необходимо полностью отказаться от использования не зашифрованного трафика в приложении. В случае если это сделать проблематично или есть необходимость использовать какие-то сторонние сервисы по протоколу HTTP обратите отдельное внимание на проверку и валидацию полученных даныых и никогда не передавайте по такому протоколу конфиденциальную информацию.

В случае, если необходимо выбрать, как будет осуществляться передача данных, можно руководствоваться следующей схемой:

Схема Включение чувствительной информации в HTTPS запрос

Сравнение HTTP и HTTPS:

Система_Стингрей_включение_чувствительной_информации_в_HTTPS_запрос_сравнение HTTP и HTTPS

Android использует java.net.HttpURLConnection/javax.net.ssl.HttpsURLConnection в качестве API для организации канала связи с помощью протоколов HTTP/HTTPS. Поддержка Apache HttpClient прекращена начиная с Android 6.0 (API 23).

Внимание! Для организации канала по HTTPS не стоит использовать класс SSLSocket, т.к. он, в отличие от HttpsURLConnection, по-умолчанию не проверяет соответствие имени сервера и имени хоста, указанного в сертификате. Кроме того, делая такую реализацию разработчики часто допускают ошибки, которые приводят к дефектам безопасности в канале связи.

Использование HTTPS с SSL-пиннингом

Приложение может дополнительно защитить себя от мошеннических сертификатов с помощью технологии, известной как SSL-pinning. Она предотвращает компрометацию сертификата доверенного удостоверяющего центра в системном хранилище, что делает практически невозможным нарушение безопасности канала передачи данных приложения.

Правила:

  • Сверяйте сертификат сервера с сохранённым в приложении;
  • Схема URI должна быть https://
  • В передаваемые данные можно включать чувствительную информацию;
  • Полученным данным можно доверять, т.к. они получены от подлинного сервера;
  • Обрабатывайте ошибки SSL надлежащим образом;

Пример корректной реализации SSLPinning

PrivateCertificateHttpsGet.java
package com.appsec.android.https.privatecertificate;
 
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
 
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
 
import android.content.Context;
import android.os.AsyncTask;
 
public abstract class PrivateCertificateHttpsGet extends AsyncTask {
 
    private Context mContext;
 
    public PrivateCertificateHttpsGet(Context context) {
        mContext = context;
    }
 
    @Override
    protected Object doInBackground(String... params) {
        TrustManagerFactory trustManager;
        BufferedInputStream inputStream = null;
        ByteArrayOutputStream responseArray = null;
        byte[] buff = new byte[1024];
        int length;
 
        try {
            URL url = new URL(params[0]);
            // *** 1 *** Сверяйте сертификат сервера с сохранённым в приложении
            // Настраиваем keystore для установки соединений таким образом, чтобы он включал только сертификат из ресурсов приложения
            KeyStore ks = KeyStoreUtil.getEmptyKeyStore();
            KeyStoreUtil.loadX509Certificate(ks,
                    mContext.getResources().getAssets().open("cacert.crt"));
 
            // *** 2 *** Схема URI должна быть https://
            // *** 3 *** В передаваемые данные можно включать чувствительную информацию
            trustManager = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManager.init(ks);
            SSLContext sslCon = SSLContext.getInstance("TLS");
            sslCon.init(null, trustManager.getTrustManagers(), new SecureRandom());
 
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            HttpsURLConnection response = (HttpsURLConnection)con;
            response.setDefaultSSLSocketFactory(sslCon.getSocketFactory());
 
            response.setSSLSocketFactory(sslCon.getSocketFactory());
            checkResponse(response);
 
            // *** 4 *** Полученным данным можно доверять, т.к. они получены от подлинного сервера
            inputStream = new BufferedInputStream(response.getInputStream());
            responseArray = new ByteArrayOutputStream();
            while ((length = inputStream.read(buff)) != -1) {
                if (length > 0) {
                    responseArray.write(buff, 0, length);
                }
            }
            return responseArray.toByteArray();
        } catch(SSLException e) {
            // *** 5 *** Обрабатывайте ошибки SSL надлежащим образом
            // Пропускаем, т.к. это пример
            return e;
        } catch(Exception e) {
            return e;
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (Exception e) {
                    // Пропускаем, т.к. это пример
                }
            }
            if (responseArray != null) {
                try {
                    responseArray.close();
                } catch (Exception e) {
                    // Пропускаем, т.к. это пример
                }
            }
        }
    }
 
    private void checkResponse(HttpURLConnection response) throws IOException {
        int statusCode = response.getResponseCode();
        if (HttpURLConnection.HTTP_OK != statusCode) {
            throw new IOException("HttpStatus: " + statusCode);
        }
    }
}
KeyStoreUtil.java
package com.appsec.android.https.privatecertificate;
 
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
 
public class KeyStoreUtil {
    public static KeyStore getEmptyKeyStore() throws KeyStoreException,
            NoSuchAlgorithmException, CertificateException, IOException {
        KeyStore ks = KeyStore.getInstance("BKS");
        ks.load(null);
        return ks;
    }
 
    public static void loadAndroidCAStore(KeyStore ks)
            throws KeyStoreException, NoSuchAlgorithmException,
            CertificateException, IOException {
        KeyStore aks = KeyStore.getInstance("AndroidCAStore");
        aks.load(null);
        Enumeration aliases = aks.aliases();
        while (aliases.hasMoreElements()) {
            String alias = aliases.nextElement();
            Certificate cert = aks.getCertificate(alias);
            ks.setCertificateEntry(alias, cert);
        }
    }
     
    public static void loadX509Certificate(KeyStore ks, InputStream is)
            throws CertificateException, KeyStoreException {
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X509");
            X509Certificate x509 = (X509Certificate)factory.generateCertificate(is);
            String alias = x509.getSubjectDN().getName();
            ks.setCertificateEntry(alias, x509);
        } finally {
            try { is.close(); } catch (IOException e) { /* Пропускаем, т.к. это пример*/ }
        }
    }
}
PrivateCertificateHttpsActivity.java
package com.appsec.android.https.privatecertificate;
 
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
 
public class PrivateCertificateHttpsActivity extends Activity {
 
    private EditText mUrlBox;
    private TextView mMsgBox;
    private ImageView mImgBox;
    private AsyncTask mAsyncTask ;
 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
         
        mUrlBox = (EditText)findViewById(R.id.urlbox);
        mMsgBox = (TextView)findViewById(R.id.msgbox);
        mImgBox = (ImageView)findViewById(R.id.imageview);
    }
     
    @Override
    protected void onPause() {
        if (mAsyncTask != null) mAsyncTask.cancel(true);
        super.onPause();
    }
     
    public void onClick(View view) {
        String url = mUrlBox.getText().toString();
        mMsgBox.setText(url);
        mImgBox.setImageBitmap(null);
         
        if (mAsyncTask != null) mAsyncTask.cancel(true);
         
        mAsyncTask = new PrivateCertificateHttpsGet(this) {
            @Override
            protected void onPostExecute(Object result) {
                if (result instanceof Exception) {
                    Exception e = (Exception)result;
                    mMsgBox.append("\nException occurs\n" + e.toString());
                } else {
                    byte[] data = (byte[])result;
                    Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
                    mImgBox.setImageBitmap(bmp);
                }
            }
        }.execute(url);
    }
}
Использование современного подхода — Network Security Configuration

Платформа Android предоставляет новый простой инструмент для настройки сети — Network Security Configuration (NSC). Он доступен с Android 7.0. С помощью NSC можно производить настройку сетевых соединений, в том числе и SSLPinning, с использованием файлов XML. Чтобы включить конфигурацию, необходимо связать файл конфигурации с манифестом приложения. Для того, что это сделать, используйте атрибут networkSecurityConfig в теге application.

1. Создать файл с конфигурацией

res/xml/network_security_config.xml

2. Добавить атрибут android:networkSecurityConfig с указанием расположения файла в AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="co.netguru.demoapp">
<application
android:networkSecurityConfig="@xml/network_security_config">
...
</application>
</manifest>

3. Настройте файл конфигурации и добавьте отпечатки сертификатов:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<pin-set>
<pin digest="SHA-256">ZC3lTYTDBJQVf1P2V7+fibTqbIsWNR/X7CWNVW+CEEA=</pin>
<pin digest="SHA-256">GUAL5bejH7czkXcAeJ0vCiRxwMnVBsDlBMBsFtfLF8A=</pin>
</pin-set>
</domain-config>
</network-security-config>

Этот метод чрезвычайно прост в реализации. Однако имейте в виду, что он доступен только для API уровня 24 или выше.

Предыдущая Включение sensitive-информации в параметры GET-запроса
Следующая Передача sensitive информации в HTTP-запросе
Содержание: