№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
DOI - 10.32743/UniTech.2021.90.9.12268
ЗАЩИТА ПРОПРИЕТАРНОГО ПРОГРАММНОГО ПРОДУКТА ОТ НЕСАНКЦИОНИРОВАННЫХ ИЗМЕНЕНИЙ
Плетнев Андрей Владимирович
директор ТОО «SimCo Soft», Руководитель группы разработки ТОО «OneBill», Республика Казахстан, г. Алматы E-mail: [email protected]
PROTECTION OF PROPRIETARY SOFTWARE PRODUCT FROM UNAUTHORIZED CHANGES
Andrey Pletnev
CEO «SimCo Soft» LLP, Team lead «OneBill» LLP, Republic of Kazakhstan, Almaty
АННОТАЦИЯ
Данная статья затрагивает проблемы защиты интеллектуальной собственности разработчиков программного обеспечения, работающих в 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, не является панацеей, т.к. попросту потребует от злоумышленника чуть большей усидчивости для достижения своих корыстных целей.
«Постойте, но ведь мой проект опубликован на удаленном хостинге, доступ к файлам которого есть только у меня! Все что тут написано, не имеет никакого смысла!» - с негодованием возразите вы и будете совершенно правы. Указанная проблема обретает смысл в тех случаях, когда:
a) Ваш продукт опубликован на корпоративном сервере в дата-центре предприятия-заказчика в промышленной или предпромышленной эксплуатации и к файлам на этом сервере имеет доступ персонал заказчика - мастера на все руки, доверия к которым у вас нет;
b) Ваш продукт находится на стадии внедрения и вы еще не получили свой гонорар сполна;
Библиографическое описание: Плетнев А.В. ЗАЩИТА ПРОПРИЕТАРНОГО ПРОГРАММНОГО ПРОДУКТА ОТ НЕСАНКЦИОНИРОВАННЫХ ИЗМЕНЕНИЙ // Universum: технические науки : электрон. научн. журн. 2021. 9(90). URL: https://7universum.com/ru/tech/archive/item/12268
№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
c) Вы намерены в дальнейшем заниматься сопровождением и доработкой этого продукта под потребности этого предприятия и получать от этого дополнительный доход. При этом Вы ни коим образом не желаете допускать никого, кроме себя, к выполнению изменений своего продукта;
d) Вы выполнили разработку продукта для компании дистрибьютора программного обеспечения и в вашем с ними договоре указано, что Вы получаете дополнительный гонорар за каждую новую адаптацию своего продукта под нового конечного заказчика и не хотите, чтобы этот дистрибьютор мог делать такие адаптации самостоятельно либо с привлечением третьей стороны;
e) В Вашем продукте применен разработанный Вами уникальный алгоритм, исходным кодом которого Вы не желаете делиться с остальным Миром.
Этот список можно было бы продолжить и далее, но общая мысль, вкладываемая в каждый пункт, будет одна - «Вы с особым трепетом относитесь к неприкосновенности объекта своей интеллектуальной собственности и не желаете нести финансовые потери от несанкционированных изменений и\или кражи Вашего продукта».
Теперь, когда стало понятно, что описанная проблема актуальна для многих разработчиков проприетарных программных продуктов, позвольте представить Вашему вниманию решение, которое я неоднократно и успешно применял в своих разработках. Практическое руководство по решению поставленной задачи, представленное далее в этой статье, состоит и четырех достаточно простых в реализации шагов. Итак, ...
Во-первых, не используйте интерпретируемые языки программирования для разработки 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.
№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
• • • SQLEncryptür
Query lisl SQL Editor
Users.CetList UPDATE
Users. Add New users
SET
Users.Upd ate Attribute passwd = $1
Users.SetPasswd WHERE
Users.5et[jack id = $2
Users.Delete
Büülis.GBtüst
Baaks.AddNew
Bog ks .Update Attribute
Books.Delete
Baaks.Restare
& - + v^
Рисунок 1. «Пользовательский интерфейс SQLEncrytor»
В-третьих. Зашифруйте сборку Вашего frontend! На этом шаге остановимся подробнее.
Известно, что любая сборка frontend состоит из множества файлов, разложенных сборщиком по
нескольким каталогам [2][11]. Как правило структура файлов в сборке frontend выглядит так, как показано на рисунке 2.
" Щ css
£ ар р. 4 2 74 6 2 2d.css £ chunk-1fc29f6e.71f72053.css £ chunk-412ef3bc.bdaff37c.css £ chunk-490a14fe.fd79da1b.css £ chunk-7Q49ec69.fdd2fb19.cs5 £ chunk-45389511.42cbOa4a.css £ chunk-d6aeeecc.056cd1fe.css irrig
Ш 6-small.c9b47a98.png Е 9-small.30df7a62.png Д login-v2.72cd8a26.svg нз Iogo_a.fe37ef63.jpg
" Ш ls
¡i app.8d1e09da.js % app.3d1e09da.js,map ii chunk-1fc2 9f6e.e19c3 3b7.js || chunk-1fc29f6e.e19c33b7.js.map ¡i Chunk-2d0a334b.0aa7beed.js || Chunk-2d0a334b.0aa7beed.js. map Ü chunk-2d22bcc3.16dc5eea.js || chunk-2d22bcc3.16dc5eea.js.map □ favicon.ico Ш index.html
Рисунок 2. «Структура Frontend»
Чтобы зашифровать frontend, Вам понадобится написать еще одну программу - назовем ее FrontendEncryptor. Как и в случае с SQLEncrytor, пишите FrontendEncryptor на том-же языке программирования, на котором пишите свой backend. Удобнее будет, если FrontendEncryptor будет консольным приложением.
Идея состоит в том, чтобы зашифровать все файлы frontend и последовательно поместить их в один общий «файл данных». Также FrontendEncryptor должен создавать еще один вспомогательный «индексный файл». Этот файл необходим для хранения логической структуры каталога dist и данных о местоположении каждого из файлов сборки в файле
№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
данных. Содержимое индексного файла также должно быть зашифровано.
Можно было бы обойтись и без индексного файла и файла данных, а просто зашифровать каждый файл в отдельности с сохранением его имени и расположения в структуре каталога dist. Такой подход, конечно же, немного упростит реализацию FrontendEncryptor и механизма работы с зашифрованным frontend на backend^, но усложнит регулярно выполняемый процесс обновления Вашего продукта на серверах заказчика. Ведь куда проще выполнить замену всего пары файлов вместо нескольких десятков, потому как сборщики frontend, при выполнении сборки проекта, всегда пересоздают заново все содержимое каталога dist. Имена всех файлов изменяются от сборки к сборке и, перед копированием новой версии на сервер, всегда нужно будет сначала удалить на сервере все файлы старой версии. Кроме того, способ, который предлагается в этой статье, позволяет скрыть структуру Вашей сборки frontend.
Итак, перейдем к описанию алгоритма работы FrontendEncryptor:
1. При запуске FrontendEncryptor принимает два параметра командной строки:
• путь до каталога dist, содержащего файлы сборки frontend, которые нужно зашифровать;
• путь до каталога, в который будут записаны зашифрованные выходные индексный файл и файл данных.
2. Используя рекурсию для прохода по всему содержимому каталога сборки frontend на всю его глубину, нужно сформировать список всех ее файлов и контрольных сумм для каждого из них. Формируемый список представляет собой массив, каждый элемент которого является структурой следующего вида:
• Path - строка. Записываем сюда путь до файла относительно каталога 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-1 fc29f6e.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 с передачей ему параметров командной строки: путь к
№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
каталогу 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. Если контрольные суммы равны, то Get-FrontendFile возвращает расшифрованный фрагмент
файла данных, как содержимое запрошенного файла в виде байтового массива, иначе возвращает ошибку. В случае, когда поиск по массиву структур в памяти не дал результат, GetFrontendFile также возвращает ошибку.
Для сборок frontend, использующих локальный роутер, следует учесть один нюанс в реализации функции GetFrontendFile: если путь до запрашиваемого файла не содержит символ точки, то это не файл, который необходимо извлечь и расшифровать из файла данных, а локальный маршрут frontend, для которого нужно извлечь и расшифровать файл index.html. Это необходимо для того, чтобы пользователь Вашего frontend, находясь на любой их страниц системы (в любой позиции роутера) не получал ошибку 404 при обновлении страницы или при прямом переходе по ссылке на такую страницу из вне.
Функцию GetFrontendFile следует вызывать внутри функции-обработчика http-запросов роутера Вашего backend, обрабатывающего запросы на получение файлов frontend. Путь к файлу, который передается как параметр функции GetFrontendFile, следует брать из контекста http-запроса (как правило -это поле path). В большинстве библиотек, предназначенных для реализации web-сервисов, указатель на http-контекст передается в функцию-обработчик http-запросов роутера как параметр. Возвращаемый функцией GetFrontendFile байтовый массив данных следует передавать обратно в http-контекст (как правило - это поле Response). В реализации вашего frontend я рекомендую предусмотреть страницы для отображения ошибок, которые необходимо будет возвращать в Response в случаях, когда GetFrontend-File возвращает ошибку. В самом простом случае можно просто возвращать 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/drium/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).
№ 9 (90)
UNIVERSUM:
ТЕХНИЧЕСКИЕ НАУКИ
сентябрь, 2021 г.
7. Новиков Б.А., Горшкова Е.А. Основы технологий баз данных. - М.: ДМК Пресс, 2019.
8. Бьярне Страуструп. Программирование: принципы и практика использования С++. - М.: ИД «Вильямс», 2011.
9. Брайан У. Керниган, Деннис М. Ритчи. Язык программирования С. - М.: ИД «Вильямс», 2017.
10. Алан А.А. Донаван, Брайан У. Керниган. - М.: «Язык программирования Go», ИД «Вильямс», 2016.
11. Бенджамин Листоун, Эрик Хенчетт. Vue.js в действии. - СПб, ИД «Питер», 2019.