Динамическое исполнение программного кода с помощью метода Выполнить() является одним из наиболее рискованных механизмов платформы 1С:Предприятие. Попытка подставить непроверенную пользовательскую строку или имя переменной напрямую в исполняемый контекст часто приводит к уязвимостям выполнения произвольного кода (Code Injection). Разберем, почему простые фильтрации символов не обеспечивают безопасность и как правильно организовать работу с динамическими объектами.
Рассмотрим типовую ситуацию, когда разработчик пытается динамически обратиться к переменной с таблицей значений, предварительно очистив входную строку от точки с запятой:
ИмяНужнойТаблицы = ОчиститьОтТочкиЗапятой(ИмяНужнойТаблицы);
Выполнить("ВременнаяТаблицаДляСохранения = " + ИмяНужнойТаблицы + ".Скопировать();");
СохранитьТаблицуКудаНадо(ВременнаяТаблицаДляСохранения);
На первый взгляд кажется, что удаление символа ; предотвратит добавление дополнительных выражений. Однако подход с использованием «черных списков» (удаление точки с запятой, круглых скобок и других спецсимволов) фундаментально ненадежен. Выясним причину, по которой злоумышленник всё равно сможет внедрить исполняемый код.
Проанализируем примеры конструкций, с помощью которых внедренный код успешно исполняется даже при отсутствии явной точки с запятой во входной строке:
Символ() с кодом 59 (символ точки с запятой). Например, конструкция вида Выполнить("Выполнить(""Сообщить(1)"" + Символ(59) + ""Сообщить(2)"")"); полностью обходит текстовую очистку входной строки от символа ;.Выполнить() выполняется в той же области видимости и с теми же правами, что и вызывающий программный код. Если процедура запущена в привилегированном режиме или на сервере без контроля прав, внедренный код получает полные права администратора информационной базы, что позволяет выполнить удаление данных, чтение защищенных сведений или вызовы внешних систем.Наиболее надежный и правильный архитектурный подход — полностью отказаться от метода Выполнить(). Для динамического обращения к объектам по строковому имени следует использовать коллекцию Структура или Соответствие.
Посмотрим на пример безопасной реализации:
// Формируем контейнер с таблицами
КонтейнерТаблиц = Новый Структура;
КонтейнерТаблиц.Вставить("ТаблицаТовары", ИсходнаяТаблицаТовары);
КонтейнерТаблиц.Вставить("ТаблицаУслуги", ИсходнаяТаблицаУслуги);
// Безопасное получение и обработка таблицы
ИсходнаяТЗ = Неопределено;
Если КонтейнерТаблиц.Свойство(ИмяНужнойТаблицы, ИсходнаяТЗ) И ТипЗнч(ИсходнаяТЗ) = Тип("ТаблицаЗначений") Тогда
ВременнаяТаблицаДляСохранения = ИсходнаяТЗ.Скопировать();
СохранитьТаблицуКудаНадо(ВременнаяТаблицаДляСохранения);
Иначе
ВызватьИсключение "Указана недопустимая таблица данных.";
КонецЕсли;
В этом случае значение переменной ИмяНужнойТаблицы выступает исключительно строковым ключом поиска в структуре. Какое бы вредоносное содержимое ни было передано в строку, оно физически не может быть исполнено платформой.
Если набор возможных имен таблиц заранее известен и ограничен, применим строгий контроль по списку разрешенных значений:
РазрешенныеТаблицы = Новый Массив;
РазрешенныеТаблицы.Добавить("ТаблицаТовары");
РазрешенныеТаблицы.Добавить("ТаблицаУслуги");
Если РазрешенныеТаблицы.Найти(ИмяНужнойТаблицы) = Неопределено Тогда
ВызватьИсключение "Попытка обращения к неразрешенной переменной.";
КонецЕсли;
Если применение метода Выполнить() или Вычислить() неизбежно, а имя переменной формируется динамически, необходимо валидировать строку на строгое соответствие стандарту идентификатора языка 1С. В строке не должно быть ничего, кроме букв, цифр и знака подчеркивания, при этом строка не должна начинаться с цифры.
Разберем функцию проверки корректности имени идентификатора:
Функция ЭтоКорректныйИдентификатор1С(Знач ИмяПеременной)
ИмяПеременной = СокрЛП(ИмяПеременной);
Если ПустаяСтрока(ИмяПеременной) Тогда
Возврат Ложь;
КонецЕсли;
ДопустимыеСимволы = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZабвгдеёжзийклмнопрстуфхцчшщъыьэюяАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ_0123456789";
Цифры = "0123456789";
// Первый символ не может быть цифрой
ПервыйСимвол = Сред(ИмяПеременной, 1, 1);
Если СтрНайти(Цифры, ПервыйСимвол) > 0 Тогда
Возврат Ложь;
КонецЕсли;
// Проверка всех символов строки
ДлинаСтроки = СтрДлина(ИмяПеременной);
Для Позиция = 1 По ДлинаСтроки Цикл
Символ = Сред(ИмяПеременной, Позиция, 1);
Если СтрНайти(ДопустимыеСимволы, Символ) = 0 Тогда
Возврат Ложь;
КонецЕсли;
КонецЦикла;
Возврат Истина;
КонецФункции
После проверки функция гарантирует, что в строке содержится только одно имя переменной без пробелов, операторов, вызовов функций, кавычек или спецсимволов.
При проектировании модулей, выполняющих динамический код, важно использовать встроенные средства платформы для минимизации возможного ущерба:
УстановитьБезопасныйРежим(Истина). В безопасном режиме платформа блокирует запуск привилегированного режима, выполнение внешних приложений через операционную систему, вызовы COM-объектов и внешних компонент, а также ограничивает прямой доступ к файловой системе.