Можно ли защитить веб-страницы от анализа исходного кода?::Журнал СА 3.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Архив номеров
Контакты
   

  Опросы
  Статьи

Рынок труда  

Системные администраторы по-прежнему востребованы и незаменимы

Системные администраторы, практически, есть везде. Порой их не видно и не слышно,

 Читать далее...

Учебные центры  

Карьерные мечты нужно воплощать! А мы поможем

Школа Bell Integrator открывает свои двери для всех, кто хочет освоить перспективную

 Читать далее...

Гость номера  

Дмитрий Галов: «Нельзя сказать, что люди становятся доверчивее, скорее эволюционирует ландшафт киберугроз»

Использование мобильных устройств растет. А вместе с ними быстро растет количество мобильных

 Читать далее...

Прошу слова  

Твердая рука в бархатной перчатке: принципы soft skills

Лауреат Нобелевской премии, специалист по рынку труда, профессор Лондонской школы экономики Кристофер

 Читать далее...

Как хорошо вы это знаете  

Портал Инкоманд. Для чего он? Для кого? Какие проблемы решает?

Компания «ЕМДЕВ» – создатель интернет-портала, предлагает всем желающим протестировать себя на

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 10017
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 8228
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 8329
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 5287
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 5962
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Друзья сайта  

 Можно ли защитить веб-страницы от анализа исходного кода?

Архив номеров / 2003 / Выпуск №3 (4) / Можно ли защитить веб-страницы от анализа исходного кода?

Рубрика: Программирование /  Аудит

ДАНИИЛ АЛИЕВСКИЙ

Можно ли защитить веб-страницы

от анализа исходного кода?

Можно ли как-то помешать программисту-профессионалу, располагающему сколь угодно богатым арсеналом специализированных программ – отладчиком, собственной версией браузера с исходными текстами, виртуальной машиной и любыми другими средствами – проанализировать исходный HTML-код веб-страницы или, по крайней мере, клиентский JavaScript? Если это и возможно, то так же трудно, как реализация схемы защиты, описанной в этой статье.

Формат HTML – самый популярный формат для представления веб-страниц – изначально разрабатывался как открытый. Это значит, что любой посетитель, который видел веб-страницу в своем браузере, при желании всегда мог увидеть исходный HTML-текст этой страницы. Подобная возможность была встроена почти во все браузеры. Например, в Internet Explorer это команда меню View/Source.

Такое положение дел всех устраивало, пока веб-страницы были сравнительно несложные. Язык HTML тогда использовался в основном для выделения гиперссылок, вставки графики и стилевого оформления текста. В этой ситуации исходный HTML-текст отличался от того, что посетитель видел на экране, только сравнительно небольшим количеством простых тегов, управляющих заголовками, курсивом, жирностью, таблицами и т. п. Прятать от пользователя такой HTML-текст не было особого смысла. Любой HTML-верстальщик cмог бы без особого труда воспроизвести его (или написать эквивалентный), просто глядя на экран готовой веб-страницы.

Но постепенно приемы верстки усложнялись, появились стили и, самое главное, стал широко применяться язык JavaScript. Ускорение связи сделало возможным встраивание в HTML-страницы нетривиальных JavaScript-программ, которые при желании могут насчитывать десятки тысяч строк кода.

Сегодня достаточно сложная веб-страница уже не отличается от программы, написанной на традиционном языке типа Delphi или C++ и реализующей некоторый пользовательский интерфейс.

В такой ситуации открытость формата HTML иногда оказывается нежелательной. Стандарты HTML и JavaScript как бы «навязывают» соглашение, по которому все исходные тексты программ автоматически доступны конечному пользователю. Но в мире обычных программ только сторонники движения «Open Source» придерживаются такого подхода. Остальные разработчики склонны рассматривать свой исходный код как коммерческую тайну или «ноу-хау». Более того, в некоторых случаях разработчики стараются помешать даже изучению скомпилированного машинного кода (или виртуального байт-кода, как в случае Java). Для этого применяют различные приемы защиты от дизассемблирования и отладки.

Кроме соображений «коммерческой тайны» возможны случаи, когда открытость исходного кода веб-страниц недопустима по самому замыслу. Возьмем, например, веб-страницу, содержащую некоторый тест. По его результатам, которые посетитель должен куда-то отправить, принимаются какие-то решения. Такой тест в принципе нельзя реализовывать в рамках открытого HTML и JavaScript – иначе любой грамотный программист легко подделает результаты тестирования, просто подсмотрев в JavaScript-коде правильные ответы. Обычно приходится использовать серверное решение – ответы посетителя обрабатываются серверным скриптом. Но у такого решения есть свои минусы: например, необходимость в течение всего прохождения теста поддерживать связь с Интернетом или высокая нагрузка на сервер при достаточной его популярности.

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

Я попытаюсь подробно ответить на этот вопрос.

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

Сразу оговорюсь: я не располагаю готовым программным пакетом, позволяющим автоматизировать защиту страниц. Я также никогда не реализовывал описываемую далее систему защиты для своих веб-страниц. Описываемая схема рассматривается чисто теоретически.

Я также не думаю, что защита от взлома именно языков HTML и JavaScript – действительно актуальная задача, по крайней мере, сегодня. Пока что, по моему личному мнению, эти языки еще «не тянут» на роль профессиональной технологии разработки нетривиального программного обеспечения. Если действительно требуется разработать клиентское приложение, серьезно защищенное от анализа исходных текстов, то, наверное, рациональнее написать его на других языках программирования.

Но с теоретической точки зрения задача защиты исходных текстов HTML и JavaScript, как мне кажется, представляет интерес именно потому, что эти языки крайне уязвимы для взлома. Технологии, которые позволили бы защитить от анализа исходных текстов HTML/JavaScript-программу, скорее всего, окажутся применимыми практически к любому другому языку.

Еще одна необходимая оговорка. Я не берусь утверждать, что предлагаемые здесь идеи действительно позволяют создать надежную защиту. Может быть, можно придумать программу, автоматически «взламывающую» любую защиту, наподобие описанных далее. Если вы найдете принципиальные упущения в приведенных далее построениях или, наоборот, придумаете альтернативную систему защиты – буду рад обсудить это на форуме xpoint.ru, а лучше пишите на daniel@siams.com.

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

Постановка задачи

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

<script language="JavaScript" src="..."></script>

Вместе этот блок файлов мы будем называть веб-приложением.

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

Мы исходим из того, что в распоряжении потенциального взломщика имеются любые программные средства, которые уже существуют или могут быть в принципе разработаны. Например, взломщиком может быть программист компании Microsoft, располагающий полным исходным текстом самого популярного браузера Internet Explorer. Такой программист вполне может чуть-чуть «подправить» браузер с тем, чтобы любой HTML- или JavaScript-код, который браузеру приходится интерпретировать, автоматически сохранялся в протоколе на диске. (Недооценка подобных возможностей взломщика-профессионала – одна из самых типичных ошибок новичков, пытающихся защитить свои страницы.)

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

Веб-приложение должно исполняться (визуализироваться и реагировать на действия пользователя) с помощью HTML- или JavaScript-кода, расположенного на компьютере клиента. В идеале пользователь должен иметь возможность отключиться от Интернета и работать с веб-приложением в режиме off-line, пользуясь копиями файлов приложения, которые браузер поместил в свой внутренний кэш. Данное требование можно немного ослабить: допустить небольшой обмен данными с сервером (скажем, для целей защиты). Но мы не рассматриваем ситуацию, когда весь нетривиальный код «прячется» банальным образом: интерпретируется полностью на сервере.

Дополнительное требование: веб-приложение должно корректно работать хотя бы в некоторых стандартных браузерах, скажем, Internet Explorer или Netscape.

«Несерьезные» приемы

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

Самый наивный прием – борьба с мышкой

Очень многие авторы «систем защиты» в первую очередь начинают рассуждать следующим образом.

Чтобы увидеть исходный текст страницы, посетитель должен выбрать в браузере команду «View Source». Самый типичный способ это сделать – «всплывающее» меню, доступное по правой кнопке мышки. Пишем на JavaScript обработчик нажатия правой кнопки мышки, который вместо визуализации локального меню высвечивает какое-нибудь ругательство. Есть еще команда «View/Source» в основном меню браузера. Кладем главную страницу внутрь фрейма: тогда Internet Explorer покажет по этой команде тривиальный FRAMESET. А если умный пользователь увидит внутри FRAMESET адрес страницы с фреймом и откроет ее непосредственно? И с этим можно справиться – достаточно проверить в JavaScript, что мы находимся внутри нужного фрейма, и если это не так, то сразу уйти на страницу с «ругательствами».

Одна из самых очевидных ошибок таких авторов – они забывают про кэш браузера. Обычно проще всего получить все исходные тексты, «спрятанные» таким образом, очистив кэш в Internet Explorer и затем зайдя на «защищенную» веб-страницу. После этого достаточно заглянуть в папку «Temporary Internet Files» и обнаружить там все «спрятанные» HTML- и JavaScript-файлы.

С кэшированием, правда, можно бороться.

Борьба с кэшированием

Более «подкованные» разработчики защитных механизмов могут вспомнить про кэширование страниц и попытаться его отключить. В принципе это не так уж сложно. Для запрета кэширования существуют специальные элементы в заголовке HTTP-ответа:

  Pragma: no-cache

  Cache-Control: no-cache

(имеет смысл указать оба элемента). Заголовок HTTP-ответа достаточно легко настраивается в любом сервере. Для тех, кто не имеет доступа к нужным настройкам своего сервера, существуют эквивалентные META-теги в самом HTML:

<meta http-equiv="Pragma" content="no-cache">

<meta http-equiv="Cache-Control" content="no-cache">

Очевидный метод «взлома» таких способов «защиты» – использование нестандартного браузера, игнорирующего указание «не кэшировать страницы».

Нужно иметь в виду, что если единственная задача взломщика – получить на диске точную копию HTML-страницы или подключаемого JavaScript-файла, то «браузер», решающий такую задачу, пишется за 10 минут на любом языке типа Perl или Java. Все, что нужно – отправить на сервер стандартный запрос по HTTP-протоколу (например, такой же, какой выдает Internet Explorer), получить соответствующий ответ и сохранить его на диске. В большинстве современных языков программирования такая задача решается тривиально.

Существует и общее решение, позволяющее справиться с любым видом «блокировки кэширования». Это proxy-сервер, устанавливаемый на клиентский компьютер и «пропускающий через себя» без модификаций все HTTP-запросы. Такой proxy может быть совершенно «невидимым» и для клиента, и для сервера. «По пути» proxy-сервер может сохранять на диске все проходящие через него HTML- и JavaScript-файлы. Скажу больше: proxy-сервер с такими возможностями, скорее всего, уже давно существует, например, среди бесплатных утилит ускорения доступа в Интернет.

Нестандартная обработка запросов к файлам

Однажды я столкнулся с любопытной схемой защиты, основанной на нестандартной реакции веб-сервера на самый обыкновенный (с виду) файл. К HTML-странице был подключен сложный JavaScript-файл с традиционным расширением .js. Но это был не обычный текстовый файл, а PHP-скрипт, возвращающий браузеру JavaScript-код. Веб-сервер был настроен таким образом, что конкретно этот файл с расширением .js обрабатывался интерпретатором PHP.

PHP-скрипт полностью анализировал заголовок HTTP-запроса, посылаемый браузером серверу. Каждый такой заголовок среди прочего содержит поле «Referer»: URL документа, инициировавшего данный запрос (если таковой имеется). Для Java-скриптов, подключаемых тегом:

<script language="JavaScript" src="..."></script>

Поле «Referer» в заголовке запроса всегда содержит адрес HTML-страницы, внутри которой находится указанный тег. Если же попытаться прочитать тот же Java-скрипт, просто набрав его URL в адресной строке браузера, то поле «Referer» будет отсутствовать.

PHP-скрипт, «притворявшийся» обычным .js-файлом, проверял, действительно ли «Referer» соответствует адресу страницы, к которой этот JavaScript должен быть подключен, или одной из таких страниц. Если это условие соблюдалось, то PHP возвращал корректный работоспособный JavaScript. В противном случае возвращался другой JavaScript – тоже сложный, но совершенно бесполезный.

Конечно, кэширование этого JavaScript-файла было отключено.

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

Cамогенерация методом document.writeln()

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

Основным приемом в данном случае является самогенерация страницы JavaScript-методом document. writeln(). HTML-страница (включающая, возможно, некоторый JavaScript) в этом случае «зашифровывается» в виде некоторой бессмысленной на вид строки символов – строковой константы языка JavaScript. Затем вызывается некоторая JavaScript-функция, расшифровывающая эту строку, и ее результат передается методу document.writeln().

В Интернете можно найти специальные утилиты, выполняющие подобное преобразование с готовой HTML-страницей. Некоторые из них при этом выполняют сжатие данных, так что «бессмысленная» строка оказывается короче исходного HTML- и JavaScript-кода.

При банальной реализации подобную защиту тоже легко взломать. Ведь страница уже «вынужденно» содержит процедуру расшифровки! Ничто не мешает слегка подправить исходный текст страницы и вставить перед вызовом document.writeln() распечатку аргумента этого метода в любое место, откуда его легко прочитать.

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

Есть одна общая проблема, которую следует иметь в виду при использовании метода document. writeln(). Если содержимое страницы в Internet Explorer целиком выделить, скопировать в ClipBoard и затем попытаться вставить в какой-нибудь HTML-редактор, например в FrontPage Editor, то содержимое страницы будет вставлено уже «в готовом виде». Если часть страницы (включая какие-то JavaScript-фрагменты) была сгенерирована вызовом document.writeln(), то в HTML-редактор попадет и скрипт, содержащий вызов и тот HTML-код, который им был создан.

Шифрование кода

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

О защите видимого HTML-кода

Сразу оговоримся. Описанные далее методы не позволяют защитить конечный HTML (без JavaScript), который пользователь в конце концов видит на защищенных страницах. Видимый HTML всегда можно скопировать из Internet Explorer через ClipBoard в HTML-редактор, как уже было сказано в предыдущем разделе. Мне трудно представить ситуацию, когда HTML, уже видимый пользователю, настолько сложен, что его крайне трудно воспроизвести по содержимому экрана и, соответственно, имеет смысл защищать от анализа.

Серьезная защита уже визуализированного HTML невозможна в принципе. Это очевидно: всегда можно «слегка исправить» стандартный браузер, например Internet Explorer, так, чтобы в момент анализа HTML-кода и воспроизведения его на экране весь этот HTML сохранялся в протоколе на диске. Но на «несерьезном» уровне кое-какая защита все же возможна.

Вот пример такой защиты для Internet Explorer, признаюсь, не слишком «изящной». Можно просто запретить пользователю выделять какие-либо фрагменты страницы, тогда он не сможет ничего скопировать в ClipBoard. Один из способов это сделать в Internet Explorer – 10 раз в секунду исполнять примерно такую конструкцию:

var rng= document.body.createTextRange();

  rng.moveToPoint(0,0);

  rng.select();

Саморасшифровывающиеся функции

Самый очевидный путь построения защиты основан на шифровании. Мы сосредоточимся только на защите JavaScript-кода: любой HTML-код можно сгенерировать динамически средствами JavaScript.

Будем считать, что JavaScript-код, в соответствии с хорошим тоном программирования, состоит из большого количества не слишком больших функций. Мы начнем со следующей идеи.

Давайте зашифруем основное тело каждой функции и добавим в ее начало код, расшифровывающий и исполняющий ее тело. В языке JavaScript это означает, что текст функции

function somefunction() {

    какие-то операторы;

  }

заменяется на эквивалентный текст:

function somefunction() {

    var encrypted= "...";

      // зашифрованный текст тех же самых операторов

var source=decoding_function (encrypted);

    eval(source);

  }

Для выполнения такой замены, конечно, следует написать некую автоматическую (или автоматизированную) утилиту.

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

Используемый алгоритм шифрования, в частности его криптостойкость, не имеет значения. Все равно все ключи для расшифровки, если такие имеются, распространяются вместе с зашифрованными данными. Единственная цель шифрования – сделать текст JavaScript визуально нечитаемым. Собственно, разумнее всего вместо шифрования использовать какой-нибудь простой алгоритм сжатия типа Лемпеля-Зива.

Мы не будем останавливаться на реализации такого алгоритма – это классическая задача, многократно описанная в литературе. Тут есть лишь один специфический подводный камень.

Как правило, алгоритмы расшифровки (или распаковки сжатых данных) должны получать на вход массив бинарных данных – некоторую цепочку битов. А строчная JavaScript-константа, присваиваемая переменной encrypted оператором:

var encrypted= "...";

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

Какие символы можно для этого использовать? HTML- или JavaScript-файл, содержащий приведенный выше фрагмент JavaScript, – это (скорее всего) обычный текстовый файл, в котором каждый символ кодируется одним байтом. Внутри же языка JavaScript все символы хранятся в формате Unicode по 2 байта на символ. Байты файла со значениями 0..127 всегда преобразуются в соответствующие символы Unicode, но для байтов 128..255 способ трансляции зависит от кодировки HTML-страницы, указанной HTTP-заголовком Content-Type или выбранной пользователем в браузере. Поэтому «безопасно» использовать в строчной константе только младшие 128 символов ASCII. Из них «отпадают» управляющие символы с кодами 0..31. Также не очень разумно использовать символы " и , при записи которых в строчной константе нужно добавлять лишний символ .

Я бы рекомендовал использовать 64 заведомо «безопасных» символа, скажем, все латинские буквы, цифры и пару знаков препинания. Тогда с помощью каждого символа можно кодировать 6 битов информации. Эти биты можно передать расшифровывающему алгоритму для восстановления полного Unicode-текста исходного JavaScript-кода.

«Спагетти»

К сожалению, приведенное выше решение только на первый взгляд кажется действенным.

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

А что, если не делать специальную расшифровывающую функцию, а просто разместить соответствующий код в каждой точке, где он нужен? Это не поможет – такой код все равно можно автоматически повсюду отыскать и заменить своим кодом, который будет дополнительно записывать на диск расшифрованный текст. Даже если делать расшифровывающий код каждый раз немного другим, чтобы его нельзя было искать автоматически, все равно остается «узкое место» – вызов «eval». Все вызовы eval или эквивалентные средства JavaScript типа

new Function(...)

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

А может быть, можно помешать злоумышленнику менять исходный текст наших функций? Вообще-то можно. Достаточно где-нибудь в расшифровывающем коде получить полный JavaScript-текст рассматриваемой функции – в JavaScript для этого следует выполнить оператор

 somefunction+""

(somefunction – имя нашей функции). После чего можно использовать полученный текст для расшифровки, например, наложить его в режиме XOR на зашифрованные данные. Предполагается, что алгоритм зашифровки учитывает эту операцию. Тогда искажение исходного текста функции сделает расшифровку невозможной.

Только все это – борьба с непрофессионалами. Опытный взломщик может попросту использовать собственный интерпретатор JavaScript, в котором сам оператор eval будет записывать свой аргумент на диск. Такому взломщику не нужно менять исходные тексты.

Я предлагаю пойти другим путем. Пусть алгоритм расшифровки (это может быть и одна вызываемая отовсюду функция) выдает не один полный JavaScript-текст, а серию небольших JavaScript-кусочков. Исходный текст полной функции somefunction мы превратим в «спагетти»: смесь из коротких (одна-две строки) незашифрованных фрагментов и вызовов eval для столь же коротких расшифрованных кусочков, выданных алгоритмом расшифровки. Более того, некоторые расшифрованные кусочки на самом деле будут представлять собой опять же зашифрованный код аналогичного вида:

var encrypted= «...»;

var source= decoding_function(encrypted);

    ....

    eval(source);

Код source, расшифровываемый здесь, может, в свою очередь, быть зашифрован. Количество «уровней» зашифровки может быть для каждого расшифровываемого кусочка функции своим и случайным.

Процесс превращения произвольной функции в подобное «спагетти» вполне можно сделать автоматическим. Написание соответствующего софта – не слишком простая, но и не самая сложная задача. Потребуется лишь корректный синтаксический анализатор JavaScript-кода, да и то не обязательно полный: скажем, можно не анализировать «внутренности» формул, ограничившись разбиением JavaScript-кода на операторы.

Не упускаем ли мы что-то очевидное? Увы, пока да.

Если расшифровывающий код будет выглядеть столь банально:

var encrypted= "...";

var source= decoding_function(encrypted);

    eval(source);

то нет проблем написать утилиту, которая отыщет все подобные фрагменты, выполнит расшифровку и заменит вызов eval на расшифрованный код. Если даже source будет, в свою очередь, зашифрован – такую утилиту можно вызвать многократно.

Чтобы создание общей расшифровывающей утилиты было невозможно, надо сделать переменную encrypted вычисляемой – зависящей от хода выполнения программы. Например, если выполняется некий цикл, то он может «по пути» собирать encrypted сложением элементов массива строк. Для правильной сборки encrypted может, например, использоваться информация о времени выполнения участков программы.

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

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

На банальном уровне мы также защитились от специальных средств исполнения программы вроде собственного интерпретатора JavaScript – теперь взломщику недостаточно «перехватить» пару операторов типа eval, чтобы получить «на блюдечке» первоначальные исходные тексты JavaScript-функций.

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

Для борьбы с подобным «on-line» взломом существуют специальные технологии, которые мы рассмотрим в следующем разделе.

Борьба с отладчиками

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

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

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

Тайминг

Основной способ борьбы с отладчиками – анализ времени выполнения программы. Очевидно, когда взломщик занимается «вскрытием» защиты и анализирует исполняемый код, программа выполняется несравненно медленнее, чем в нормальных условиях. Для языка JavaScript это, пожалуй, единственное, чем хороший отладчик «выдает себя» – в остальном все операторы JavaScript работают обычным образом.

Как этим воспользоваться?

Банальное решение – в случае чересчур долгого исполнения некоторого кода выдавать сообщение об ошибке – не годится. Взломщик просто «обойдет» соответствующую проверку, подправив «на лету» исполняемый JavaScript или изменив значение подходящей переменной. Надо быть хитрее.

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

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

Веб-сервер и электронная подпись

Вообще говоря, для борьбы с описанным методом защиты существует сравнительно несложный общий прием. Взломщик может «подделать» таймер, JavaScript-объект Date, традиционно используемый для измерения времени. Располагая собственным отладчиком, можно «подделать» любой источник информации о времени – скажем, метод System.current TimeMillis() из Java-апплетов (и даже низкоуровневую команду процессора Intel RDTSC, в случае если бы мы защищали ассемблерную программу). «Подделанный» таймер может выдавать виртуальное время, в котором не учитываются интервалы пассивного ожидания нажатия взломщика на клавиши.

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

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

В двух словах, идея здесь следующая. Есть два ключа (длинные цепочки битов): закрытый и открытый. Располагая одним ключом, невозможно восстановить другой. Закрытый ключ – секретный, открытый – общедоступный (например, распространяется вместе с данными). Данные шифруются с помощью закрытого ключа, а расшифровываются с помощью открытого.

Если данные удалось расшифровать открытым ключом, значит, есть гарантия, что они были зашифрованы соответствующим закрытым ключом. Иначе говоря, данные, зашифрованные таким способом, – это нечто вроде подписи источника данных, владеющего закрытым ключом. Такую «подпись» подделать невозможно, или, по крайней мере, сегодня неизвестны алгоритмы, позволяющие это сделать менее чем за астрономическое время.

Если допустить небольшой обмен данными с веб-сервером, к которому взломщик не имеет доступа, то, кажется, ничто не мешает использовать идею электронной подписи для получения неподделываемой информации, в частности, текущего времени. Для обращения к серверу за зашифрованным временем и расшифровки этого времени, вероятно, лучше использовать Java-апплет – JavaScript для такой задачи чересчур неуклюж. Расшифрованное текущее время можно использовать для замера быстродействия и обнаружения отладчика. По крайней мере, так можно поступать иногда, скажем, раз в несколько минут. В большинстве ситуаций, чтобы не перегружать Интернет, лучше полагаться на обычный тайминг и только иногда «сверять» часы JavaScript с показаниями часов сервера. Можно даже допустить работу в off-line-режиме (без обращений к серверу) на те периоды времени, пока не исполняется наиболее важный код, требующий особо тщательного «скрытия».

Конечно, использовать эту технику надо так же хитро, как и любые другие защитные приемы, рассматривавшиеся выше. Скажем, просто сделать Java-апплет, возвращающий столь сложным образом полученное время, совершенно бессмысленно. Взломщик просто подменит весь этот апплет. Язык Java нужно использовать только для сервисных функций – чтения зашифрованных данных с сервера и исполнения «критичных ко времени» фрагментов алгоритма расшифровки. Сам же алгоритм расшифровки по открытому ключу должен быть реализован многократно в рамках саморасшифровывающегося JavaScript, в свою очередь, по возможности, защищенного таймингом.

Идею электронной подписи можно использовать и более широко. Скажем, JavaScript-программа может правильно работать только при условии, что она имеет связь с «родным» сервером, причем гарантированно с ним, а не с его виртуальным аналогом. А это значит, что владельцы сервера могут в принципе организовать наблюдение за исполнением своих программ. Если сервер обнаружит, что JavaScript-программа на некотором клиентском компьютере (IP-адресе) исполняется «подозрительным» образом, он может вообще заблокировать дальнейшую работу JavaScript на этом компьютере, скажем, неправильной дальнейшей реакцией на запросы зашифрованных данных с этого IP-адреса. «Подозрительным» может считаться обращение за некоторыми двумя порциями данных (например, за текущим временем), разделенные слишком большим временным интервалом, что может свидетельствовать об исполнении соответствующего кода под отладчиком.

Маскировка – «безумный» код

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

Цель взломщика – проанализировать логику работы некоторых алгоритмов, реализованных на JavaScript. Но для этого нужно увидеть каждый такой алгоритм в относительно «обозримом» виде, скажем, в течение часа проследить работу сотни JavaScript-операторов. А что, если эти сто операторов разбавлены «мусором» из тысячи ненужных операторов, не мешающих работе программы, но крайне запутывающих потенциального читателя?

Думаю, вполне можно разработать программный пакет, который бы целенаправленно «замусоривал» исходный код на языке JavaScript (или на любом другом языке) бесполезными операторами, которые делают что-то «на вид» вполне ценное, но в конце концов не оказывают влияния на нормальное исполнение программы. Более того, некоторые куски программы, например, начальный этап, на котором расшифровываются первые функции, могут почти полностью состоять из подобного «безумного» кода. Взломщику могут потребоваться сотни часов, чтобы «пробраться» через десятки тысяч ненужных операторов и дойти до той точки, где, наконец, появляется очередной расшифрованный фрагмент JavaScript. А если к этому добавить тайминг и связь с сервером, то подобное «путешествие» сквозь «безумный» код почти наверняка закончится уходом в совершенно бессмысленные области или даже обнаружением взломщика и блокировкой его IP-адреса.

Эквивалентные преобразования

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

Речь идет об эквивалентных преобразованиях JavaScript-текста, снижающих его читабельность. Конечно, простое удаление лишних пробелов и «склеивание» строк – не слишком ценное действие: такой текст очень легко автоматически сформатировать обратно в читабельный вид. Но вот замена всех идентификаторов – переменных и функций – на бессмысленные эквиваленты типа «v1», «v2», ... может быть полезной.

Это, конечно, не настоящая защита – любая программа, скомпилированная классическими компиляторами C++ или Delphi, уже «защищена» таким образом. Но по крайней мере, это делает «взлом» JavaScript-кода почти такой же непростой задачей, что и анализ машинного кода обычных программ.

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

Приведенные здесь идеи по защите JavaScript-программ от взлома, т.е. несанкционированного изучения исходных текстов, вполне применимы и к другим языкам. Язык JavaScript интересен своей предельной незащищенностью от такого рода атак. Он позволяет использовать лишь сравнительно «серьезные» схемы защиты, построенные на достаточно фундаментальных идеях. Разного рода частные «трюки», основанные на особых свойствах процессора или операционной системы, здесь не проходят.

Многие из описанных выше идей были почерпнуты автором из книги:

Защита информации в персональных ЭВМ. Спесивцев, Вегнер, Крутяков, Серегин, Сидоров. М., Радио и связь, ВЕСТА, 1993. (Библиотека системного программиста).

Эту книгу написали авторы Cerberus – одной из лучших систем защиты машинного кода, популярных во времена DOS-программ. В частности, идеи саморасшифровки, тайминга и «безумного» кода – оттуда.

Автор также признателен участникам форума xpoint.ru, неоднократно обсуждавшим тему защиты веб-страниц.


Комментарии отсутствуют

Добавить комментарий

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru