Слушаем wifi с помощью wireshark

Всегда знал, что можно посмотреть сетевые пакеты, которые передаются по wifi сети. Но на практике этого не делал (на работе анализировал сетевые пакеты, но то был не HTTP протокол). Решил восполнить этот пробел, ведь это интересно и полезно. Более стройно выстраивается понимание протоколов TCP-IP и HTTP. Видно, как летят наши пароли и сессии, после такого опыта поневоле начинаешь относиться к безопасности сайта с большим трепетом.

Трафик будем слушать с помощью программы Wireshark. Есть много утилит для анализа сетевой активности (ngrep, tcpdump, mitmproxy), но Wireshark пожалуй самая популярная и имеет огромный функционал.

Опишу работу программы на примере таких задач:

Все действия я выполнял на ноутобуке MacBook Pro с OS X Yosemite, на других устройствах возможно что-то будет по-другому.

Небольшой дисклеймер: все ваши действия на вашей совести и ответсвенности. Не используйте описанные здесь техники во вред кому-либо.

Сетевые пакеты локального интерфейса (localhost)

Итак, устанавливаем wireshark. Запускаем, заходим в меню Capture -> Intefaces.

ws interface

Я запускаю на ноутобуке, который подключен только к wifi сети (en0 интерфейс). Насколько я понимаю, awdl0 - это кабельный сетевой интерфейс. По кабелю ноутбук никуда не подключен, поэтому и пакетов нет. А lo0 - это локальный интерфейс, им сейчас и займемся. Ставим галочку напротив него и нажимаем Start. Чтобы сосредоточиться только на HTTP протоколе, зададим Display filter: http (этот фильтр применяется к уже перехваченным и обработанным пакетам, в отличие от Capture filter, но о нем позднее):

ws http filter

Сейчас мы будем ловить сетевые пакеты, передающееся от нашего браузера к django development серверу и обратно. Есть особенности, связанные именно с django сервером.

  • Во-первых, он отдает HTTP 1.0, а не HTTP 1.1.
  • Во-вторых, что более важно, в ответных HTTP заголовках может не быть
    ни Content-Length: <response length>, ни Transfer-Encoding: chunked. В этом случае для определения конца ответа нужно дождаться закрытия соединения с сервером, чего не происходит. Это имеет значение при работе с wireshark. Wireshark понимает большое количество протоколов, в том числе и HTTP. Данные HTTP протокола могут передаваться в нескольких TCP сегментах, но программа группирует эти сегменты и показывает нам итоговый HTTP запрос или ответ. С запросом проблем не возникает, но HTTP ответа не видно в списке фреймов wireshark, потому что она не понимает, что ответ уже закончен (нет ни Content-Length, ни Transfer-Encoding).

В принципе это не страшно, т.к. мы может нажать на фрейм запроса и выбрать из меню Analyse->Follow TCP Stream. В отдельном окне мы увидим HTTP запрос и соответствующий HTTP ответ (не важно, завершен он или нет). Follow TCP Stream так же удобен в том случае, если HTTP запрос и ответ идут не по порядку (между ними могут случиться другие запросы). Т.е. мы выбираем запрос, нажимаем Follow TCP Stream и видим всю цепочку сообщений. Однако, мне хотелось видеть и запросы и ответы в списке фреймов. Это мы можем сделать, добавив ConditionalGetMiddleware в список наших MIDDLEWARE_CLASSES:

if DEBUG:
    MIDDLEWARE_CLASSES = (
        'django.middleware.http.ConditionalGetMiddleware',
    ) + MIDDLEWARE_CLASSES

Прослойка будет выставлять Content-Length в ответах.

Такое нужно только при работе с django development сервером и wireshark. В остальных случаях все работает: продакшн серверы выставляют Transfer-Encoding: chunked и отдают HTTP по кусочкам (вероятно это делает proxy сервер (nginx, apache)).

Теперь запустим простой django проект, который на главной странице отображает имя текущего пользователя, а так же форму для логина и пароля. Если пользователь не залогинен, то вместо имени показываем AnonymousUser.

Для чистоты эксперимента почистим все куки в браузере для адреса 127.0.0.1. Откроем страницу http://127.0.0.1:8000/.

Если мы не задали ‘ConditionalGetMiddleware’, то скорее всего увидим только запрос:

localhost request only

Ответ все же можно увидеть, выбрав запрос и зайдя в Analyse->Follow TCP Stream:

localhost request only follow tcp

А если мы включили ‘ConditionalGetMiddleware’, то видим HTTP ответ уже в списке фреймов:

localhost request response

Content-Length задан:

localhost request response follow tcp

Ну пока не очень интересно, только поковырялись с wireshark. Но давайте попробуем залогиниться.

Вводим логин+пароль и нажимаем Login. В wireshark увидим 4 новых фрейма: POST запрос, редирект на главную страницу (ответ 302), запрос на получение главной, ответ главной страницы:

localhost four new frames

Проследим внимательно за передаваемой информацией.

Фрейм с POST запросом помимо HTTP заголовков содержит данные формы. Вот как они выглядят:

localhost login password

Логин и пароль как на ладони.

Ответом на POST запрос был фрейм с HTTP кодом 302 (редирект). В этом ответе сервер просит сохранить сессию в куках:

localhost 302 session

Следующим идет запрос главной страницы, в куках передается id сессии:

localhost session in request

Вообщем вот так можно смотреть за данными, которыми обменивается ваше приложение с клиентом. Все эти данные можно увидеть и в wifi сети, которые передаются любым пользователем (если идут запросы по незащищенному соединению http). Если мы логинимся - виден логин/пароль. Если просто отправляем запросы - виден id сессии.

Зная id сессии мы легко можем зайти под ее обладателем, просто записав их в наши куки. Для простоты, можно проверить какой-нибудь консольной утилитой (curl, httpie). Пример с httpie:

$ http 127.0.0.1:8000 "Cookie: sessionid=tmpocxkz6zsir6xe6i03kspucvlqq385"
HTTP/1.0 200 OK
Content-Length: 567
Content-Type: text/html; charset=utf-8
Date: Thu, 16 Apr 2015 13:06:58 GMT
Server: WSGIServer/0.1 Python/2.7.6
Set-Cookie:  csrftoken=3bUoLB28WyzcH7qG5GXreWPm0Pj11861; expires=Thu, 14-Apr-2016 13:06:58 GMT; Max-Age=31449600; Path=/
Vary: Cookie
X-Frame-Options: SAMEORIGIN

    <html>
        <body>
            Hello, alex
            <div>
                <form action="/login/" method="POST">
                    <input type="text" name="username" />
                    <input type="password" name="password" />
                    <input type="text" hidden name="next" value="/" />
                    <input type='hidden' name='csrfmiddlewaretoken' value='3bUoLB28WyzcH7qG5GXreWPm0Pj11861' />
                    <input type="submit" name="Login" value="Login" />
                </form>
            </div>
        </body>
    </html>

Вывелось Hello, alex, значит мы зашли под пользователем alex.

Сетевые пакеты нашей локальной машины во внешний мир

Послушаем внешние сетевые запросы нашего компьютера.

В wireshark выбираем Capture -> Intefaces, ставим галочку напротив en0 и нажимаем Start:

my wifi interface

Зайду в админку этого сайта (lexev.org). В wireshark поставлю Display фильтр

http.request.full_uri contains lexev.org

чтобы видеть только запросы на домен lexev.org. Вот что получилось:

my wifi lexev

Видно id сессии, можно делать с ней все что захочешь.

Сетевые пакеты других пользователей открытой wifi сети

До сих пор мы слушали только свои запросы и ответы. Но гораздо интересней послушать других пользователей.

Зайдем в кафе с открытой wifi сетью, запустим wireshark. Заходим в Capture -> Intefaces, выбираем соответствующий интерфейс и нажимаем Options (не Start). Видим что-то такое:

public wifi options

Дважды щелкаем на интерфейс и ставим галочку напротив Capture packets in monitor mode:

public wifi monitor model

Ok, Start. Все, теперь мы слушаем всю сеть (кроме нас самих).

В публичной wifi сети будет летать очень много пакетов, за час легко можно наловить больше Гб информации. Соответственно неудобно ее анализировать и сохранять. Тут пригодятся Capture фильтры, они применяются к еще не обработанным фреймам. Отсеченные фреймы этим фильтром не сохраняются. Отличие от Display фильтров в том, что мы работаем с нераскодированными данными, мы не знаем, что это - http или что-то другое. Поэтому Capture фильтры сложней составить. Итак, давайте попробуем сохранять только HTTP запросы GET или POST на 80 порту.

Для этого зададим такой хитрый фильтр:

(port 80 and tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x47455420) or (tcp dst port 80 and (tcp[((tcp[12:1] & 0xf0) >> 2):4] = 0x504f5354))

на этапе выбора интерфейса (кнопка Options):

public wifi capture filter

Попробуем подключиться с другого девайса (телефона) к этой wifi сети и опять отправим GET запрос на lexev.org. Для удобства добавим тот же Display фильтр домена (Capture и Display фильтры можно комбинировать). Видим id сессии:

public wifi admin session

Ради интереса попробуем войти, введя логин и пароль. Смотрите все:

public wifi admin login and password

Все как на ладони.

Сетевые пакеты других пользователей в закрытой wifi сети

Допустим есть wifi сеть с WPA шифрованием. В интерфейсах выберем wifi, включим для него monitor mode (все как для открытой сети, только без Capture фильтра) и попробуем послушать сетевые пакеты. В результатах будет что-то такое:

private undercrypted

Мы видим зашифрованные данные. Но, если мы знаем пароль от wifi, то можем расшифровать пакеты. Для этого заходим в Edit -> Preferences. Выбираем Protocol -> IEEE 802.11.

private IEEE

Нажимаем Edit напротив Decryption Keys. Далее New. В новом окошке вводим:

Key type: wpa-pwd
Key: password:wifiname

Соответственно заменяем password на пароль от wifi сети, а wifiname - на название сети.

private wifi password

Apply, ok.

Все, теперь wireshark расшифровывает пакеты, и мы можем смотреть http данные как и раньше:

private wifi decrypted

Послушаем открытую wifi сеть

Ради интереса зашел в макдональдс и примерно на час запустил wireshark (ловил только GET и POST http запросы). Далее сохранил все пакеты в файл pcap (File -> Save as). Теперь вопрос, как проанализировать сохраненные фреймы? Их накопилось довольно много, вручную лазить по ним не удобно. Воспользуемся программой tshark, с помощью нее можно выбрать нужные данные и записать их в CSV.

Вот так можно сохранить поля “номер фрейма”, “HTTP метод”, “full_uri”

tshark -r macdak_pushkin_get_post_only.pcap -T fields -e frame.number -e http.request.method -e http.request.full_uri > results.csv

Написав небольшой python скриптик, подсчитал количество запросов на каждый url и сгруппировал по доменам второго уровня. Вот 20 самых популярных доменов, на которые заходили:

Домен Количество запросов
vk.com 6280
beeline.ru 5407
vk.me 2817
instagram.com 867
google.com 544
apple.com 536
yandex.ru 473
symcb.com 471
msftncsi.com 441
msn.com 304
yandex.net 302
trendmicro.com 292
co.uk 270
badoocdn.com 258
yadro.ru 188
marketgid.com 184
adfox.ru 183
mycdn.me 165
interfax.ru 154
scorecardresearch.com 137

Да, пару интересных сессий удалось перехватить. Например, для сайта знакомств mamba.ru, они передаются в открытую по HTTP. Скопировал куки, вставил их в chrome с помощью плагина EditThisCookie, зашел на сайт и вуаля, я - Сергей. Могу читать сообщения, смотреть настройки и прочее. Сразу скажу, что ничего я там не делал, посмотрел и вышел :).

Способы защиты

Пожалуй единственным способом защиты является использование TLS (https). Здесь тоже есть нюанcы, его нужно использовать правильно. Описание правильной (и безопасной) работы с https заслуживает отдельного поста, так что тут не буду на этом останавливаться.

Вот как выглядит https трафик в wireshark:

private wifi decrypted

Все данные зашифрованы, ничего не узнать.

Итог

  • По возможности используйте https, особенно если вы получаете какие-либо важные данные от пользователя (если что-то связано с банковскими данными/карточками, так вообще обязательно).
  • Будучи в открытой wifi сети, заходя на сайт по незащищенному каналу (http) всегда помните, что вас можно легко прослушать. Это же относится и к закрытой wifi, злоумышленнику достаточно лишь узнать пароль от wifi.

Полезные ссылки