ЗАЩИТА ПРОПРИЕТАРНОГО ПРОГРАММНОГО ПРОДУКТА ОТ НЕСАНКЦИОНИРОВАННЫХ ИЗМЕНЕНИЙ

PROTECTION OF PROPRIETARY SOFTWARE PRODUCT FROM UNAUTHORIZED CHANGES
Плетнев А.В.
Цитировать:
Плетнев А.В. ЗАЩИТА ПРОПРИЕТАРНОГО ПРОГРАММНОГО ПРОДУКТА ОТ НЕСАНКЦИОНИРОВАННЫХ ИЗМЕНЕНИЙ // Universum: технические науки : электрон. научн. журн. 2021. 9(90). URL: https://7universum.com/ru/tech/archive/item/12268 (дата обращения: 02.05.2024).
Прочитать статью:
DOI - 10.32743/UniTech.2021.90.9.12268

 

АННОТАЦИЯ

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

ABSTRACT

This article touches on the problem of intellectual property protection of software developers working in web-stack with the interpreted backend in commercial projects, and offers solutions to these problems. The article provides practical recommendations for the implementation of mechanisms and methods for protecting software products.

 

Ключевые слова: защита программного кода, web-технологии, криптография, несанкционированный, backend, frontend.

Keywords: protection of program code, web-technologies, cryptography, unauthorized, backend, frontend.

 

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

Упомянутый выше недостаток – это возможность несанкционированного изменения кода продукта! Любой более-менее искушенный в web-разработке злоумышленник легко откроет сборку Вашего frontend в любом текстовом редакторе и сможет внести нужные ему изменения: надписи на элементах управления и их стили. Более того: при должном навыке такой злоумышленник сможет внести изменения в логику работы Вашего frontend, даже если при сборке вы используете minify и\или uglify плагины. А для замены графики и шрифтов в Вашей frontend-сборке и вовсе не нужны никакие специальные знания. С интерпретируемым backend для такого злоумышленника все еще проще – весь программный код лежит на сервере в открытом виде. Использование методов обфускации программного кода [5] интерпретируемого backend, также как и использование uglify для frontend, не является панацеей, т.к. попросту потребует от злоумышленника чуть большей усидчивости для достижения своих корыстных целей.

«Постойте, но ведь мой проект опубликован на удаленном хостинге, доступ к файлам которого есть только у меня! Все что тут написано, не имеет никакого смысла!» – с негодованием возразите вы и будете совершенно правы. Указанная проблема обретает смысл в тех случаях, когда:

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

Этот список можно было бы продолжить и далее, но общая мысль, вкладываемая в каждый пункт, будет одна – «Вы с особым трепетом относитесь к неприкосновенности объекта своей интеллектуальной собственности и не желаете нести финансовые потери от несанкционированных изменений и\или кражи Вашего продукта».

Теперь, когда стало понятно, что описанная проблема актуальна для многих разработчиков проприетарных программных продуктов, позвольте представить Вашему вниманию решение, которое я неоднократно и успешно применял в своих разработках. Практическое руководство по решению поставленной задачи, представленное далее в этой статье, состоит и четырех достаточно простых в реализации шагов. Итак, …

Во-первых: не используйте интерпретируемые языки программирования для разработки backend в коммерческих продуктах. Также не рекомендую использовать такие языки как Java, Kotlin, Scala и C#, так как в свободном доступе существует множество инструментов, позволяющих эффективно декомпилировать сборки, получаемые компиляторами этих языков, обратно в исходный код на этом языке [1][3].

Используйте компилируемые языки такие как C [9], C++ [8] или Go [10] – в настоящее время для этих языков существует множество качественных библиотек для реализации web-сервисов. Компиляторы этих языков дают на выходе самодостаточный исполняемый бинарный файл. Для запуска такого файла на целевом сервере не нужно устанавливать в систему никаких дополнительных фреймворков и сред исполнения. Выполняйте сборку проекта в режиме release, чтобы в Ваш выходной бинарный файл не включалась отладочная информация, облегчающая работу злоумышленника. Декомпиляция полученного бинарного файла возможна, но разбор полученного декомпилятором ассемблерного кода – это весьма трудоемкий процесс, за который вряд ли кто-то возьмется. По трудозатратам это, как минимум, может стать равносильно разработке всего проекта с нуля, потому что полученный дизассемблером код практически никогда не компилируется обратно в работающий бинарный файл.

Во-вторых: если проект использует реляционную базу данных и для взаимодействия с ней Вы не использовали ORM-библиотеки, исключите из исходного кода своего backend все строки, содержащие SQL-запросы [7]. Просто вынесите все SQL-запросы во внешний файл-словарь в формате «ключ=значение», где «ключ» – уникальное имя запроса, а «значение» – текст SQL-запроса. Для удобства в качестве ключа используйте имя функции, в которой этот SQL-запрос будет использоваться. Полученный файл-словарь зашифруйте, а в код своего сервиса добавьте две функции:

  1. LoadSqlDictionary - Вызывается при старте сервиса, открывает зашифрованный файл с SQL-запросами, расшифровывает его содержимое и помещает полученный словарь в память;
  2. GetQueryText - Вызывается внутри каждой функции, где необходимо выполнять тот или иной SQL-запрос. В качестве атрибута принимает уникальное имя запроса, а в качестве результата возвращает извлеченный из словаря в памяти текст SQL-запроса, который необходимо выполнить.

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

Для создания и редактирования зашифрованного файла с SQL-запросами в процессе работы над проектом Вам понадобится написать небольшую программу, назовем ее SQLEncrytor. Пишите ее криптографическую часть на том-же языке программирования, на котором пишите свой backend, т.к. необходимо, чтобы библиотека или модуль криптографии у обоих проектов были общими. Функции SQLEncrytor должны включать:

  1. Создание нового и открытие существующего зашифрованного файла с SQL-запросами;
  2. Загрузка списка имен запросов и отображение их на форме пользовательского интерфейса (см. пример функции LoadSqlDictionary выше);
  3. Добавление новых SQL-запросов с сохранением данных в словарь в памяти;
  4. Загрузка текста SQL-запроса из словаря в памяти в поле редактирования при выборе его имени из списка имен запросов (см. пример функции GetQueryText выше) и сохранение изменений обратно в словарь в памяти;
  5. Шифрование содержимого словаря в памяти и сохранение результата в файл.

На рисунке 1 представлен примерный вид пользовательского интерфейса SQLEncrytor.

 

Рисунок 1. «Пользовательский интерфейс SQLEncrytor»

 

В-третьих: Зашифруйте сборку Вашего frontend! На этом шаге остановимся подробнее.

Известно, что любая сборка frontend состоит из множества файлов, разложенных сборщиком по нескольким каталогам [2][11]. Как правило структура файлов в сборке frontend выглядит так, как показано на рисунке 2.

 

Рисунок 2. «Структура Frontend»

 

Чтобы зашифровать frontend, Вам понадобится написать еще одну программу – назовем ее FrontendEncryptor. Как и в случае с SQLEncrytor, пишите FrontendEncryptor на том-же языке программирования, на котором пишите свой backend. Удобнее будет, если FrontendEncryptor будет консольным приложением.

Идея состоит в том, чтобы зашифровать все файлы frontend и последовательно поместить их в один общий «файл данных». Также FrontendEncryptor должен создавать еще один вспомогательный «индексный файл». Этот файл необходим для хранения логической структуры каталога dist и данных о местоположении каждого из файлов сборки в файле данных. Содержимое индексного файла также должно быть зашифровано.

Можно было бы обойтись и без индексного файла и файла данных, а просто зашифровать каждый файл в отдельности с сохранением его имени и расположения в структуре каталога dist. Такой подход, конечно же, немного упростит реализацию FrontendEncryptor и механизма работы с зашифрованным frontend на backend’е, но усложнит регулярно выполняемый процесс обновления Вашего продукта на серверах заказчика. Ведь куда проще выполнить замену всего пары файлов вместо нескольких десятков, потому как сборщики frontend, при выполнении сборки проекта, всегда пересоздают заново все содержимое каталога dist. Имена всех файлов изменяются от сборки к сборке и, перед копированием новой версии на сервер, всегда нужно будет сначала удалить на сервере все файлы старой версии. Кроме того, способ, который предлагается в этой статье, позволяет скрыть структуру Вашей сборки frontend.

Итак, перейдем к описанию алгоритма работы FrontendEncryptor:

  1. При запуске FrontendEncryptor принимает два параметра командной строки:
    • путь до каталога dist, содержащего файлы сборки frontend, которые нужно зашифровать;
    • путь до каталога, в который будут записаны зашифрованные выходные индексный файл и файл данных.
  2. Используя рекурсию для прохода по всему содержимому каталога сборки frontend на всю его глубину, нужно сформировать список всех ее файлов и контрольных сумм для каждого из них. Формируемый список представляет собой массив, каждый элемент которого является структурой следующего вида:
    • Path – cтрока. Записываем сюда путь до файла относительно каталога dist;
    • Hash – строка. Записываем сюда контрольную сумму файла;
    • Offset – целое. Указатель на начало зашифрованного файла в файле данных. Смещение в байтах относительно начала файла данных. Пока записываем сюда 0;
    • Size – целое. Размер в байтах зашифрованного файла в файле данных. Пока записываем сюда 0;
  3. Создаем файл данных в каталоге, путь к которому FrontendEncryptor получил при запуске во втором параметре командной строки и открываем его на запись;
  4. Проходя прямым циклом по полученному списку, на каждой итерации с каждым элементом этого списка выполняем следующее:
    • Получаем значение Path из структуры очередного элемента списка и открываем исходный файл по полученному пути;
    • В поле Offset записываем текущий на данной итерации размер файла данных в байтах плюс 1.
    • Зашифровываем содержимое открытого исходного файла. Результат шифрования записываем в конец файла данных;
    • В поле Size записываем размер блока байт, полученного в результате шифрования исходного файла;
    • Закрываем исходный файл.
  5. Полученный таким образом массив структур шифруем и сохраняем результат в индексный файл в каталог, путь к которому FrontendEncryptor получил при запуске во втором параметре командной строки. В Таблице 1 наглядно показывается, как выглядит фрагмент заполненного таким образом массива структур;
  6. Закрываем файл данных и завершаем работу FrontendEncryptor.

Таблица 1.

Структура индекса

Path

Hash

Offset

Size

css/app.4274622d.css

A162FF6747BD3414F9FB

1

137478

css/chunk-1fc29f6e.71f72053.css

BF288C2ABD0637134BF23

137479

92612

css/chunk-412ef3bc.bdaff37c.css

9D0BDC6D806A976AC097

230092

32561

img/6-small.9cb47a98.png

D0414BF07B10EC1F1A5C

262654

8022

img/9-small.30df7a62.png

1D144415CB57C87ADD26

270676

9106

js/app.8d1e09da.js

DCD32F3E7368607767B0

279783

1082954

index.html

9C177B846722DBAE51B7

1362738

2301

 

Выбирайте алгоритмы шифрования и хеширования на свое усмотрение, но помните, что чем сложнее алгоритм, тем больше вычислительных мощностей сервера он потребует для своей работы [4][6] и, как следствие, будет больше замедлять работу Вашего backend. Данное обстоятельство следует учесть при разработке высоконагруженных систем.

Скомпилированный и готовый к использованию FrontendEncryptor необходимо поместить в системный каталог с программами или в любой каталог, путь к которому зарегистрирован в переменной окружения PATH, чтобы FrontendEncryptor можно было запускать из командной строки, находясь в любом каталоге.

Для автоматизации запуска FrontendEncryptor понадобится написать простой файл сценария командной строки, в котором:

  1. выполняется запуск FrontendEncryptor с передачей ему параметров командной строки: путь к каталогу dist и путь к каталогу для вывода зашифрованных файлов;
  2. выполняются команды копирования зашифрованных файлов в каталог с проектом Вашего backend или в каталог репозитория, из которого выполняется обновление тестовой, либо производственной среды (по желанию).

Полученный файл сценария командной строки необходимо поместить в корневой каталог с проектом Вашего frontend. Если Вы работаете в Unix-подобной системе, то в атрибутах этого файла необходимо установить атрибут «Исполняемый». В сценарий работы сборщика frontend необходимо добавить обработчик события, срабатывающего по успешному завершению процесса сборки проекта в режиме build for production, который будет запускать наш файл сценария командной строки, запускающий FrontendEncryptor и другие необходимые команды. Чтобы узнать, как добавлять обработчики событий в сценарий сборщика Вашего frontend, обратитесь к документации по используемому Вами JS-фреймворку.

Еще один важный момент: если в Вашем frontend используется локальный роутер, не используйте нигде в его маршрутах символ точки. Далее станет понятно, для чего это нужно.

В-четвертых: Доработайте код Вашего backend для работы с зашифрованным frontend! Здесь все достаточно просто – как и в случае со словарем SQL-запросов, описанном выше, добавьте пару функций в свой код:

  1. LoadFrontendIndex – Вызывается при старте сервиса, открывает зашифрованный индексный файл, расшифровывает его содержимое и сохраняет результат в памяти в виде массива структур;
  2. GetFrontendFile – Принимает в качестве параметра строку, содержащую путь до запрашиваемого файла. Выполняет поиск по полю Path в массиве структур в памяти и, в случае нахождения подходящего элемента, получает оттуда значение полей Hash, Offset и Size. Открывает файл данных, перемещается на Offset байт от начала файла и читает с этой позиции Size байт. Расшифровывает полученный фрагмент файла данных, вычисляет контрольную сумму от расшифрованного результата и сравнивает ее с Hash. Если контрольные суммы равны, то GetFrontendFile возвращает расшифрованный фрагмент файла данных, как содержимое запрошенного файла в виде байтового массива, иначе возвращает ошибку. В случае, когда поиск по массиву структур в памяти не дал результат, GetFrontendFile также возвращает ошибку.

Для сборок frontend, использующих локальный роутер, следует учесть один нюанс в реализации функции GetFrontendFile: если путь до запрашиваемого файла не содержит символ точки, то это не файл, который необходимо извлечь и расшифровать из файла данных, а локальный маршрут frontend, для которого нужно извлечь и расшифровать файл index.html. Это необходимо для того, чтобы пользователь Вашего frontend, находясь на любой их страниц системы (в любой позиции роутера) не получал ошибку 404 при обновлении страницы или при прямом переходе по ссылке на такую страницу из вне.

Функцию GetFrontendFile следует вызывать внутри функции-обработчика http-запросов роутера Вашего backend, обрабатывающего запросы на получение файлов frontend. Путь к файлу, который передается как параметр функции GetFrontendFile, следует брать из контекста http-запроса (как правило – это поле path). В большинстве библиотек, предназначенных для реализации web-сервисов, указатель на http-контекст передается в функцию-обработчик http-запросов роутера как параметр. Возвращаемый функцией GetFrontendFile байтовый массив данных следует передавать обратно в http-контекст (как правило – это поле Response). В реализации вашего frontend я рекомендую предусмотреть страницы для отображения ошибок, которые необходимо будет возвращать в Response в случаях, когда GetFrontendFile возвращает ошибку. В самом простом случае можно просто возвращать ResponseCode 404 или 500 в зависимости от типа ошибки.

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

 

Список литературы:

  1. Декомпиляция Java приложений / [Электронный ресурс]. - Режим доступа: URL: https://habr.com/ru/post/176825/ (дата обращения: 03.08.2021).
  2. Чиннатамби К. Изучаем React. 2-е издание. – М.: Издательство «Бомбора», 2019.
  3. Stanislav Sidristij. Как работает декомпиляция в .Net или Java на примере .Net / [Электронный ресурс]. - Режим доступа: URL: https://habr.com/ru/company/clrium/blog/244095/ (дата обращения: 03.08.2021).
  4. Денис Голуб. Криптоалгоритмы. Классификация с точки зрения количества ключей / [Электронный ресурс]. - Режим доступа: URL: https://habr.com/ru/post/336578/ (дата обращения: 05.08.2021).
  5. Обфускация как метод защиты программного обеспечения / [Электронный ресурс]. - Режим доступа: URL: https://habr.com/ru/post/533954/ (дата обращения: 08.08.2021).
  6. Максим Белов. Основы и способы информационной безопасности в 2017 году / [Электронный ресурс]. - Режим доступа: URL: https://habr.com/ru/post/344294/ (дата обращения: 07.08.2021).
  7. Новиков Б. А., Горшкова Е. А. Основы технологий баз данных. – М.: ДМК Пресс, 2019.
  8. Бьярне Страуструп. Программирование: принципы и практика использования С++. – М.: ИД «Вильямс», 2011.
  9. Брайан У. Керниган, Деннис М. Ритчи. Язык программирования С. – М.: ИД «Вильямс», 2017.
  10. Алан А. А. Донаван, Брайан У. Керниган. – М.: «Язык программирования Go», ИД «Вильямс», 2016.
  11. Бенджамин Листоун, Эрик Хенчетт. Vue.js в действии. – СПб, ИД «Питер», 2019.
Информация об авторах

директор ТОО «SimCo Soft», Руководитель группы разработки ТОО «OneBill», Республика Казахстан, г. Алматы

CEO «SimCo Soft» LLP, Team lead «OneBill» LLP, Republic of Kazakhstan, Almaty

Журнал зарегистрирован Федеральной службой по надзору в сфере связи, информационных технологий и массовых коммуникаций (Роскомнадзор), регистрационный номер ЭЛ №ФС77-54434 от 17.06.2013
Учредитель журнала - ООО «МЦНО»
Главный редактор - Ахметов Сайранбек Махсутович.
Top