Первая реакция
Первая реакция

Если вы когда-нибудь пытались просмотреть сетевые запросы десктопного или мобильного приложения через HTTPS-прокси, то наверняка сталкивались со странной ситуацией: прокси настроен, корневой сертификат установлен и доверен, системный прокси прописан, а в списке запросов пусто. Или приложение начинает выдавать ошибку сразу после включения перехвата.

Ниже будет описан набор реальных сценариев, со схемами работы на сетевом уровне и примерами того, как это выглядит в прокси-приложении. Для этого я использую Сольпугу и несколько демо-приложений с разными условиями.

1. Приложение вообще не использует прокси и устанавливает соединение напрямую

Самый простой и очень частый случай. Приложение либо полностью игнорирует системные настройки прокси, либо использует собственный HTTP-клиент, который читает прокси только из переменных окружения / собственной конфигурации.

Типичные примеры:

  • Flutter (часто игнорирует системный прокси)

  • Кастомные реализации на OkHttp / URLSession с особыми конфигурациями

  • Приложения, которые проверяют наличие специальной переменной (типа HTTP_PROXY в Linux) и отказываются так работать

Тут мало что можно сделать, разве что в случае кастомных реализаций делать специальные сборки для работы через внешние прокси.

Диаграмма сценария 1
Диаграмма сценария 1

В приложении перехватчика в этот момент видно только чужой трафик (браузер, другие приложения), а нужного приложения в списке нет.

Прошел запрос только из Safari
Прошел запрос только из Safari

2. Приложение использует прокси, но не доверяет его сертификату

Трафик доходит до прокси, но TLS-рукопожатие падает, потому что приложение (или система) не доверяет его корневому сертификату.

Прокси-сервер для каждого имени хоста генерирует на лету конечный (leaf) сертификат и подписывает его своим собственным корневым центром сертификации. Приложение принимает подменный сертификат только в том случае, если корневой сертификат прокси-сервера находится в хранилище доверенных сертификатов, к которому оно фактически обращается. 

На устройстве могут быть системное хранилище и отдельное пользовательское. На Android 7+ приложения по умолчанию доверяют только системному хранилищу, а на iOS профиль конфигурации должен быть помечен как полностью доверенный; в противном случае сертификат устанавливается, но игнорируется. 

В этом случае можно попробовать добавить корневой сертификат в системное хранилище или разрешить в приложении обращение к пользовательскому, если это возможно.

Диаграмма сценария 2
Диаграмма сценария 2
Появляется попытка соединения, которая сразу заканчивается ошибкой TLS
Появляется попытка соединения, которая сразу заканчивается ошибкой TLS

3. TLS-рукопожатие начинается, но SSL Pinning прерывает его до установки соединения

SSL Pinning - распространённая практика в приложениях, которые работают с чувствительными данными. Во время соединения, после обычной проверки цепочки сертификатов, приложение дополнительно проверяет открытый ключ сервера (или весь сертификат) на соответствие жестко зашитому в код значению (SPKI pin / certificate pin). Поддельный же конечный сертификат содержит открытый ключ прокси-сервера, а не реального сервера. Даже если система полностью доверяет корневому центру сертификации прокси-сервера, проверка не проходит, и соединение так и не устанавливается.

Диаграмма сценария 3
Диаграмма сценария 3

В интерфейсе это выглядит почти так же, как в предыдущем случае: короткоживущий запрос с ошибкой TLS. Разница только в содержании ошибки и в том, что система сертификату доверяет, а приложение - нет.

Два запроса на один и тот url, один прошел нормально, а второй упал с ошибкой
Два запроса на один и тот url, один прошел нормально, а второй упал с ошибкой

4. Приложение использует собственный TLS-стек или отдельное хранилище сертификатов

Некоторые приложения не используют системный TLS и системное хранилище сертификатов. Они либо:

  • либо используют собственный BoringSSL / OpenSSL / mbedTLS,

  • либо имеют встроенное хранилище доверенных сертификатов,

  • либо реализуют pinning на уровне своего стека.

Например, Firefox на Linux может потребовать отдельного импорта корневого сертификата через свои настройки в свое хранилище. В этом случае даже правильно установленный системный корневой сертификат прокси ничего не даёт.

Диаграмма сценария 4
Диаграмма сценария 4

В этом случае запросы могут проходить частично, либо не проходить вовсе. Например, здесь сертификат установлен, запросы Curl и Lynx бегают, а Firefox артачится:

Упал именно HTTPS запрос
Упал именно HTTPS запрос

5. Разные части приложения используют разные сетевые библиотеки и каналы связи

Очень частый случай в больших приложениях. Основной функционал ходит через один HTTP-клиент (который «уважает» прокси), а аналитика, пуши, веб-сокеты, загрузка изображений, платежный SDK - через другой сетевой стек.

В результате часть запросов видна, а часть нет.

Диаграмма сценария 5
Диаграмма сценария 5

6. Приложение использует протокол или транспорт, который не поддерживается конкретным прокси-приложением

Даже если приложение поддерживает работу через прокси-сервер и не использует pinning, оно может использовать протокол, который конкретный прокси плохо или совсем не поддерживает.

HTTP/2: требует полноценной поддержки бинарного формата данных, синхронизацию для stateful HPACK. Многие прокси пропускают только HTTP/1.1 и теряют часть запросо.

HTTP/3 / QUIC: работает поверх UDP. Классические дебаг-прокси почти всегда TCP-only. Если сеть разрешает UDP/443 - клиент устанавливает QUIC напрямую, минуя прокси.

Диаграмма сценария 6
Диаграмма сценария 6

В TCP-прокси такой трафик просто отсутствует, либо трансформируется в HTTP/1, если источник поддерживает несколько типов соединения (заголовок alt-svc):

Ответ доступен по HTTP/3, но тут мы видим HTTP/1.1
Ответ доступен по HTTP/3, но тут мы видим HTTP/1.1

Заключение

Выше были разобраны самые распространенные случаи игнорирования прокси приложениями. Список наверняка не полный, могут быть более экзотичные кейсы. Поэтому важно понимать, как устроены сетевые механизмы в тестируемом приложении и почему именно так. Далеко не всегда отсутствие трафика в перехватчике есть признак бага, иногда это особенности архитектуры или ограничения инструмента.