Кроссбраузерный запуск «злобного» кода на клиенте

    Пост будет интересен веб-разработчикам, заинтересованным в запуске небезопасного кода на клиенте (из браузера). Под «злобным» мы понимаем код, который мы не можем выполнить в чистом JavaScript’е (в нашем случае — подписание куска данных определенным сертификатом).

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

    Вот как мы решали эту не самую тривиальную задачу.

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

    На входе у нас была работающая ActiveX-библиотека, которая делала всё, что нам нужно, но только в IE. Нам же нужно было кросс-браузерное решение.

    Сразу оговоримся о нашем понимании термина «кросс-браузерное». Согласно статистике, наши пользователи используют следующие браузеры:



    IE 8+ (39%)
    Mozilla Firefox 3.6+ (19%)
    Opera 9+ (17%)
    Chrome (14%)
    IE 7 (6%)
    IE 6 (4%)
    Safari 5+ (1%)

    Соответственно, нам требовалась корректная работа во всех этих браузерах.

    Решение пришло довольно быстро. Идея заключается в следующем: пользователь на машину устанавливает небольшую программку, которая могла бы выполнять нужные нам действия. Взаимодействие же с ней можно осуществлять при помощи HTTP-запросов. Программка получилась действительно небольшой, она умела принимать входящие запросы специального вида и запускать соответствующие методы CryptoApi.

    Оставалась понять, как же сделать нужный запрос на такой сервер. Сделать такой запрос с сервера не представлялось возможным — машина пользователя может не иметь внешнего ip-адреса по десятку разных причин. Значит, запрос надо выполнять из javascript-кода на адрес 127.0.0.1. С одной стороны, это существенно облегчало задачу идентификации выполняющего запрос и позволило обойтись без HTTPS, с другой — это означало, что нам придется делать кросс-доменный вызов, что само по себе сопряжено с рядом проблем. Тонкости кросс-доменных вызовов уже обсуждались на Хабре, например, в этой статье. Вооруженные таким образом богатым инструментарием, мы приступили к экспериментам.

    CORS


    В качестве первого подхода мы решили воспользоваться самым простым из имеющихся методов — Cross-Origin Resource Sharing. Эта технология поддерживается многими современными браузерами и проста в реализации. Но, к сожалению, она не поддерживается Оперой, а ее реализация в IE не дает возможности делать локальные вызовы с сайтов в категориях Internet и Intranet. И если с последним мы еще могли смириться (у нас в запасе был наш ActiveX-компонент), то поддержкой Оперы мы жертвовать не стали. От идеи написать расширение для Оперы, которое проксировало бы локальные вызовы с нужных адресов, нас отговорили интерфейсологи — да мы и сами не были уверены в том, что все пользователи разберутся с установкой расширения. Кроме того, его тоже пришлось бы своевременно обновлять… Пришлось искать другие решения.

    Flash


    Следующей нашей идеей было использовать для кросс-доменных запросов библиотеку flXHR. Но на этом пути нас снова подстерегали политики безопасности, на этот раз самого flash. Не успели мы переписать наш сервис на использование только GET-запросов (чтобы обойти запрет на POST запросы с https на http), как выяснилось, что flash-приложения, полученные из областей Internet и Intranet, помещаются в песочницу, изолированную от машины пользователя, в том числе и по http-запросам. Снова мимо.

    JSONP


    Тут же нас осенила новая идея. Возможность обойтись только GET-запросами означала, что мы можем, слегка подкорректировав формат запросов, воспользоваться технологией JSONP. Правим, запускаем — работает. Но это только в Хроме. Firefox — аналогично. А вот в Опере и в IE нас вновь поджидали проблемы. Выяснилось, что политики безопасности этих браузеров запрещают подключение скриптов, находящихся в более приватной зоне адресов. Агррррх!

    Iframe




    Следующий эксперимент — открыть iframe на 127.0.0.1 и как-то с ним общаться. И опять нас подвела безопасность Оперы. Эти свойства политик безопасности Оперы грозили поставить крест на нашей идее. Выяснилось, что при кросс-доменном обращении к машине пользователя, включая подключение скриптов, redirect и открытие в iframe, Опера подкладывает вместо ответа служебную страницу opera:crossnetworkwarning, в которой пользователь может подтвердить свое желание осуществить переход на локальную машину (естественно, при загрузке скрипта с локальной машины, пользователь этой страницы не увидит и подтвердить ничего не сможет). Единственное исключение — это когда переход в явном виде осуществлен пользователем (например, при клике на ссылку <a href="http://localhost/" target="my_frame">).

    Iframe + PostMessage


    Получается, что каким-никаким образом, но iframe с локальным адресом открыть можно. Но тогда можно и общаться с локальным сервером посредством postMessage! Осталось только выбрать, как открывать локальный фрейм. Возможностей, собственно, две: либо показывать пользователю ссылку, которая будет выполнять в скрытом фрейме переход на локальный адрес, либо выполнять переход самим и предоставить пользователю самостоятельно разрешить переход. Поколебавшись, мы выбрали второй путь. Преимущество его в том, что на странице opera:crossnetworkwarning (как видно на скриншоте) есть возможность запомнить разрешение для конкретного домена, в результате лишнее действие потребуется от пользователя всего один раз. Кроме того, этот способ позволяет повторять попытку обращения к сервису без участия пользователя, в том случае, когда он не установлен или не запущен, и запустить требуемый метод, как только сервис станет доступен.

    Итог


    postMessage не работает в IE 6-7 и в Firefox 3, но мы решили не добавлять на этот случай костылей a-la hash-polling. Для пользователей IE мы оставили ActiveX-компонент, а пользователям Firefox 3 (которых у нас считанные единицы) мы решили предложить обновиться до более поздних версий.

    Ну и чуть-чуть пиара (проект хороший, поэтому надеюсь на понимание). Если ваша жена или мама — бухгалтер и входят в сотню тысяч несчастных женщин, рассчитывающих зарплату в программе-монстре, — порекомендуйте им посмотреть Эврику (так называется наш сервис). Хватит уже мучиться от всякой непотребщины.
    Метки:
    СКБ Контур 91,86
    Компания
    Поделиться публикацией
    Похожие публикации

    Вакансии компании СКБ Контур

    Комментарии 50
    • +1
      Если мне не изменяет память, то такого рода решение было уже в «хакере» опубликовано, то ли в июньском, то ли в августовском
      • +2
        49% IE, ужас…
        • +3
          Немалую роль в этом играло то, что раньше подписывать отчеты можно было только из IE
          • +1
            Судя по цифрам все же 59% — 49% это только ie8+
            • +1
              Нет, это в цифрах ошибка. 49% — это весь IE. Сейчас исправлю.
            • +1
              гм. а я вот несколько раз натыкался уже на реализации систем с открытым ключем на чистом js. может ли это означать, что такую примерно штуку можно реализовать на чистом js без модулей?
        • +2
          Гм… вообще для этих цеелй используют java аплеты.
          Хотя я всё лелею мысль о подписи без них и тем более без приблуд что у вас.
          • 0
            Java-апплеты мы как-то не рассматривали. Дело в том, что нам не хотелось иметь никаких внешних зависимостей. А java установлена далеко не у всех, например у 20% наших пользователей нет java-плагина в браузере.
            А мысль о подписи без костылей мы тоже нежно лелеим.
            • +2
              Так установка вашей, пусть и мини программки, много хуже международного корпоративного стандарта который используется при подписи и создании ключей/сертификатов средствами java keytool.
              Тем более еще куча извращений. А установить java, как показала практика не большая проблема и решается подавляющим числом пользователей.
              В общем не знаю, ваша история конечно интересная, но на практике я бы такое не применял.
              • +2
                Зависимость все равно есть, в данном случае от вашей программы. Почему не стали использовать плагин? Актив реализовал всю необходимую криптографию в кросплатформенном и кроссбраузерном плагине. Работает везде, админ прав не требует. Использует сертифицированное ФСБ аппаратное СКЗИ Рутокен ЭЦП.
                • 0
                  Я лично не слышал об этом плагине, надо будет посмотреть, насколько он удобен нам.
                  • +1
                    rutokenweb.ru
                    • 0
                      Ну не все же наши клиенты используют Рутокен Web. Более того, я думаю, что таких крайне мало. И я не нашел информацию о том, чтобы Рутокен Web умел шифровать и подписывать произвольные документы.
                      • 0
                        То есть ваше решение просто прослойка для обращение к установленному криптопровайдеру? Да уж, внешних зависимостей почти нет :)
                        • 0
                          Немножко поясню. В решении Рутокен Web имеется реализация базового криптографического функционала. В качестве носителя могут использоваться токены Рутокен ЭЦП и Рутокен Web. Решение независимо, не требует установки криптопровайдеров или других дополнительных компонент. Однако оно не заменяет криптопровайдер, оно может служить для решения конкретных задач. В качестве примера на rutokenweb.ru реализован защищенный механизм аутентификации.
                          • 0
                            А есть надежда, что докрутите до варианта который можно для подписи отчетов использовать?
                            • 0
                              Механизм подписи так же реализован.
                              • 0
                                А почему нельзя рутокен веб для подписи отчетов использовать? Без установки СКЗИ
                                • 0
                                  Использовать можно. Видимо, Эврика работает с клиентами у которых уже по наследству от Контур-Экстерн установлено какое-либо СКЗИ.
                                  • 0
                                    Посмотрите хабрапочту плиз
                                    • 0
                                      Все верно. Мы в первую очередь рассчитываем на тех наших клиентов, у котрых уже установлен Контур-Экстерн и как следствие стоит Крипто-Про
                                      • 0
                                        Ну дело не только в СКЗИ. Все-таки у нас есть и клиенты, которые не пользуются Рутокеном. Но было бы очень интересно, если бы вы рассказали поподробнее, как использовать Рутокен ЭЦП для подписи отчетов.
                • +1
                  а сейчас только зарплатный сервис работает по этой схеме, или уже весь контур можно под любыми браузерами запускать?
                  • 0
                    Пока работает только зарплатный сервис, но остальные продукты тоже потихоньку к нему присматриваются.
                  • 0
                    То есть теперь можно обратиться по http к вашей прокладке и подписать что угодно? Или участие пользователя в момент обращения к закрытому ключу все же необходимо?
                    • 0
                      Нет, прокладка принимает вызовы только с адреса 127.0.0.1. Так что обратиться можно только из js-кода.
                      • 0
                        Что мешает вызвать тот же метод, что реализован у вас на сайте при посещении другого ресурса?
                        • 0
                          Ну origin при запросе, разумеется, проверяется.
                        • 0
                          Я понял, имел в виду обращение какого-нибудь трояна и т.п.

                          Так участие пользователя требуется или нет?

                          И еще вопрос. Этим ключом имеет смысл подписывать только ваши отчеты, или его можно еще как-то использовать?
                          • 0
                            Троян и так может обратиться напрямую к реестру, CryptoApi и т.п. зачем ему обращаться к нашему сервису?

                            Нет, не требуется. Ради этого все и затевалось.

                            Используется обычный ключ пользователя, установленный у него на машине/рутокене. Использование его для других целей зависит от того, на каких условиях он был приобретен у УЦ. Как минимум, ничто не запрещает использовать его, скажем, для шифрования/ЭЦП почты.
                            • 0
                              > зачем ему обращаться к нашему сервису?

                              Например, чтобы подписать что-то без ведома пользователя.
                              • 0
                                Дак он и так это может сделать. Что ему мешает вызвать соответствующий метод CryptoApi?
                                • 0
                                  Для доступа к закрытому ключу потребуется согласие пользователя. А возможно и ввод парольной фразы. А при обращении через ваш сервис это уже сделано ранее.
                      • +2
                        А Webmoney Keeper не по этому же принципу работает? Там вроде также висит локальный сервер в виде кипера к которому идет обращение из браузера. Вроде во всех браузерах работает. Не смотрели его реализацию до разработки своего решения?
                        • +1
                          Возможно по этому же :) Нет, реализацию его не смотрели. Реализовать локальный веб-сервер — это не проблема. Тем более, что нам и реализовать пришлось ВООБЩЕ не работу с криптографией (100500 разных методов). Как Данил написал в статье, у нас уже есть экстерновская активиксина, которая все умеет. Мы написали только проксирование вызовов ее COM-объектов с javascript-а. Поэтому разбираться пришлось только с кроссдоменными вызовами, и тут уж смотрели все что есть.
                          • 0
                            Реализовать локальный веб-сервер — это не проблема

                            Это понятно. Я имел ввиду реализацию запуска «злобного» в браузере. Просто в статье вы пишете о нескольких безуспешных попытках использования тех или иных подходов: CORS, FLASH, JSONP… Возможно, подглядев за работой того же WM Keeper, вы сократили бы свое время.
                            Но это вовсе не упрек! Лишь пояснение своего комментария.
                            За статью вам спасибо, и статья как раз получилась полной и интересной благодаря обзору нескольких подходов.
                        • +3
                          Возможно кому-то будет полезна информация: в Opera 12 будет реализована поддержка CORS
                          my.opera.com/core/blog/2011/10/28/cors-goes-mainline
                          • 0
                            Ну наконец-то:)
                          • 0
                            А <script src=http://localhost/bla-bla-bla…
                            ?
                            GpsGate так делает, по крайней мере.
                            • 0
                              А это и есть JSONP. В статье про него написано.
                            • 0
                              А почему не воспользовались КриптоПро ЭЦП Browser plug-in. Мы сделали его как раз для таких задач.
                              • 0
                                А когда был релиз этой прелести? ;)
                              • 0
                                Летом 2011. Сорри, раскопал тему-динозавра.

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

                                Самое читаемое