Расскажу о том, с чем на практике столкнулся буквально недавно – о том, насколько может быть небезопасным прикладной программный интерфейс сервера в 1С.
Обычно разработчики 1С не думают об уязвимостях – когда ты варишься только в мире 1С, не выходишь за его рамки, кажется, что все и так хорошо. Но если чуть-чуть расшириться и заглянуть снаружи, немного с другим опытом, то можно увидеть много интересных дыр, которые сразу не заметны.
В ходе доклада:
-
посмотрим, что нам советуют стандарты от 1С;
-
порассуждаем, от кого мы защищаемся и как защищаемся;
-
и я продемонстрирую, каким небезопасным может быть программный интерфейс, предоставляемый 1С.
Стандарты 1С
Я выбрал два основных стандарта на тему безопасности.
Первый стандарт касается ограничения на установку признака «Вызов сервера». Я вынес из него пару тезисов.
-
Во-первых, не нужно ставить флаг «Вызов сервера» всем серверным общим модулям.
-
И во-вторых, в модулях с флагом «Вызов сервера» должен быть только тот код, который действительно нужно вызывать с клиента, и этот код должен делать только то, что заложено разработчиком, чтобы не вызывать никаких побочных эффектов.
Казалось бы, здесь все достаточно очевидно и понятно – так же, как и в любом другом стандарте 1С.
Второй стандарт – о безопасности прикладного программного интерфейса сервера. Согласно этому стандарту:
-
Когда у нас приложение работает в управляемом режиме (тонкий или веб-клиент), обмен данными между клиентом и сервером происходит по открытому протоколу HTTP. И все, что может сделать клиент, можно сделать и сторонними средствами.
-
В данном случае потенциальную угрозу представляют все серверные функции и процедуры, которые доступны для вызова из клиентского кода.
Здесь тоже вроде все понятно.
Вообще, стандарты 1С – это достаточно компромиссная штука. Я даже сам пару из них написал. Если делать стандарт понятным, то это будет огромная статья. Если писать его совсем кратко, то это будет понятно только тем, кто это и так понимает. А здесь, скорее, будет что-то промежуточное – как правило, одного простого прочтения будет недостаточно, чтобы понять, что там закладывалось в этом стандарте при разработке.
Обычно, когда стандарт разрабатывают, идет бурная дискуссия, каждый накидывает что-то свое. В итоге из этого выжимают только суть очень общими словами. Если вдуматься, эта формулировка покрывает достаточно много, но для человека, который только что столкнулся с этим стандартом, достаточно сложно понять, о чем речь.
На этом слайде я выписал основные ключевые моменты, на которые хочу обратить ваше внимание.
-
Любой экспортный метод из общего модуля с флагом «Вызов сервера» можно вызвать с клиента.
-
Его может вызвать любой пользователь, который хоть как-то авторизован в программе, даже если у него урезаны все права – для этого достаточно только залогиниться.
-
При этом если на какие-то общие формы или объекты метаданных вы можете настроить права, роли, а на общие модули этого сделать нельзя. Конечно, когда мы вызываем в модуле какой-нибудь метод, который обращается к базе данных, а к базе данных у нас по правам нет доступа, тогда, конечно, будет ошибка. Но у нас в типовых конфигурациях достаточно много кода, который на базу никак не завязан, и там таких ограничений нет. Если очень хорошо покопаться, можно найти достаточно интересные лазейки с точки зрения повышения привилегий или позволяющие сделать что-то плохое – главное, знать, что искать.
-
-
По модулям форм – если мы получили форму на клиенте, она создалась, у нас на это были права, то дальше из этой формы мы можем вызывать любой серверный метод с директивой &НаСервере и &НаСервереБезКонтекста – даже неэкспортный. Тем более, мы можем вызывать весь клиентский код с директивой &НаКлиенте – там можно сделать, что угодно. Например, в «Бухгалтерии предприятия» в очень многих местах выполнение кода вынесено на форму. Если такая форма висит в фоне, при желании можно с этим что-то нехорошее сделать.
-
С кодом и данными на клиенте все и так должно быть понятно – этот код и данные нам доступны без ограничений. И какая-то безопасность на клиенте – это просто фикция. Если мы в формочке на клиенте поставим проверку пароля на правильность, после чего дадим пользователю какой-то доступ, то буквально за несколько минут можно будет получить этот же доступ и без пароля.
От кого мы хотим защищаться?
Кто может проникнуть в базу:
-
В базу может проникнуть внешний злоумышленник. Для этого ему достаточно подобрать параметры аутентификации. Если раньше какая-нибудь тетенька-бухгалтер работала в своей «Бухгалтерии» без пароля, и никто ее не беспокоил, то теперь у нас случился карантин, и большинство людей, кто мог, ушли на удаленку – соответственно, опубликовали свои базы в интернет или как-то организовали удаленный доступ. Но та безопасность, которая была, она и осталась – в лучшем случае там хотя бы какой-то пароль есть.
-
По пользователю – если у нас БСП, то логин автоматически формируется как ФамилияИО. Но, как правило, даже это нам не нужно, потому что, как правило, для удобства никто не убирает пользователей из выпадающего списка – если мы знаем, по какому адресу подключаться к базе, мы и так всех пользователей базы увидим.
-
Пароль можно подобрать.
-
Проверка сложности по умолчанию отключена – бухгалтеры, которые работают со своей программой один-на-один, в большинстве своем даже не знают, что там как-то можно пароли проверять.
-
Самое главное, что пользователи не любят сложные пароли. Из моей практики, 99% простых пользователей – это те, кто использует пароли «1», «123», «1234». В лучшем случае, пароль хотя бы есть.
-
И только начиная с 8.3.16, когда 1С озаботилась этой проблемой, добавила какую-то защиту от перебора. И то – по умолчанию там дается 5 попыток, и для пароля «123» этого вполне хватит.
-
-
Если про внешних злоумышленников люди хоть как-то думают, то про пользователей, которые и так в базу могут войти, но имеют ограниченные права, особо не задумываются.
-
Например, никто не задумывается о защите от внешних пользователей. У нас может быть менеджер магазина, имеющий доступ к нашей базе с ограниченными правами – он к нам заходит и делает какую-то свою ограниченную часть работы в АРМ, куда у него выведен один документ.
-
Или у нас может быть какой-нибудь условный кладовщик, которого как-то обидели, но при этом у него есть доступ в базу – ограниченный, но есть.
-
И самая изюминка, когда мы берем какую-нибудь нашу рабочую «Бухгалтерию», добавляем туда АРМ для личного кабинета и публикуем ее, даем к ней публичный доступ – так точно делать не надо.
-
Как будем защищаться?
Как бы в большинстве случаев начал защищаться 1С-ник?
-
Он бы предложил отключить интерактивное открытие внешних обработок, отчетов.
-
А еще – он бы предложил отобрать у пользователя все права, оставить доступ только к тем объектам, которые есть в интерфейсе, чтобы пользователь не смог сделать ничего из того, чего в интерфейсе нет.
Казалось бы, мы теперь в безопасности? К сожалению, так думает очень много людей.
Но в моей практике пришлось очень часто доказывать, что это не так.
Демонстрация
Дальше я покажу на примере. Возьмем демо-базу от 1С – открываем инструменты разработчика в браузере и запускаем «Бухгалтерию».
У нас здесь есть список пользователей, который получается обычным GET-запросом.
Мы можем выполнить этот GET-запрос отдельно на соседней вкладке. Даже не имея никаких данных авторизации, часть информации из того, что нужно для внешнего злоумышленника, мы уже получили.
Давайте залогинимся под продавцом, у которого, скорее всего, менее привилегированные права, чем у других пользователей.
В списке запросов фильтром XHR оставляем только запросы к серверу. Все современные браузеры имеют приблизительно одинаковые инструменты разработчика. У меня используется Firefox, но то, что я сейчас здесь покажу, применимо в Chrome, Edge и других браузерах.
Моя задача – показать, что любой экспортный метод в общем модуле с признаком «Вызов сервера» можно вызвать очень просто, и при этом не нужно как-то явно грузить обработку, написанную на языке 1С. Что нам для этого потребуется?
Я буду акцентировать внимание только на тех вызовах, которые нам нужны сейчас для нашей задачи.
Первый вызов – это login. В параметрах передается:
-
version – версия платформы,
-
cred – это логин пароль в base64,
-
clnId – некий уникальный идентификатор клиента, по которому сервер будет знать, что этот клиент – это именно этот клиент.
Если пароль верный, мы в заголовке ответа получим значение идентификатора сессии vrs-session, который нужно будет вставлять в одноименный параметр заголовка запроса для каждого последующего обращения, чтобы сервер знал, что этот запрос делает уже авторизованный конкретный пользователь.
Такое же значение, как и в vrs-session, находится в теле ответа в параметре идентификатора сессии seance.
Также в теле ответа нас интересует свойство assemblyVersion – это некая версия конфигурации, значение которой мы будем вставлять в параметр confver для запроса описания модуля.
Далее нас будет интересовать запрос с методом defs – первый в этом списке, имеющий один параметр confver.
Запрос defs, скорее всего, расшифровывается как definitions – здесь содержится описание метаданных и модулей, которые нам доступны для работы. По сути, это программный интерфейс, представляемый общими модулями.
В ответе на этот запрос к нам приходит JSON-структура, где есть:
-
main – модуль управляемого приложения;
-
global – это общие модули, помеченные флагом «Глобальный»;
-
client – все клиентские модули;
-
server – все серверные модули с флагом «Вызов сервера».
Например, здесь можно увидеть, что «МодульУправляемогоПриложения» возвращает помимо некого ID-шника еще такое свойство как image – по сути, скомпилированный в байткод сам модуль.
Может, кому-то будет интересно узнать, что в ранних версиях 1С:Предприятие, где-то в районе 8.3.2 клиентский код, написанный в 1С, транслировался в код на JavaScript и, начиная с какой-то версии от этого отказались в пользу компиляции клиентского кода на сервере в байткод для виртуальной машины на JavaScript и выполнении этого байткода уже непосредственно в JavaScript на клиенте пользователя.
Если посмотреть скрипты, которые обрабатывают этот код, то можно найти часть, отвечающую за интерпретатор этого байткода. При желании можно разобраться и посмотреть под отладчиком, как это работает.
Я это говорю к тому, что клиентский код в виде байткода находится на клиенте, его можно здесь запускать под отладкой, можно просто посмотреть все клиентские данные – они находятся на клиенте как некие структуры в JavaScript, и их можно менять. Пошел в консоль, написал код на JavaScript, заменил, и все. Вот и вся защита на уровне клиента. По сути, ее нет.
Это было небольшое отступление. Нас же интересует в текущей теме моего доклада серверные модули с признаком «Вызов сервера».
Здесь мы можем увидеть огромный список серверных модулей с параметрами
-
id – идентификатор модуля;
-
code – имя модуля;
-
cached и securityinfo – я не разбирался в том, что это значит, но оно нам и не нужно в данный момент.
Давайте откроем конфигурацию «Бухгалтерия предприятия» и найдем в ней какой-нибудь подходящий пример модуля с вызовом сервера – благодаря стандартам у нас такие модули здесь заботливо размечены. Просто сделаем отбор, и пройдемся, посмотрим код, выберем что-нибудь интересное.
Что-то нехорошее с сервером 1С и с базой 1С я делать не собираюсь, поэтому возьму какой-то простой метод, на котором можно посмотреть, что этот вызов делается достаточно просто, и мне за это ничего не будет.
Я предварительно нашел достаточно простой пример в общем модуле РегламентированнаяОтчетностьАЛКОВызовСервера. В нем есть экспортная функция ВерсияПлатформы(), которая нам, по идее, должна вернуть строковое представление версии платформы. Давайте выполним вызов этого метода.
Мы видим, что этот модуль к нам в описании пришел, и из него нас будет интересовать вот этот ID-шник – скопируем его в буфер обмена. Я покажу, как дальше 1С понимает, что этот модуль ей может дать.
Ниже здесь идут вызовы defs, куда, опять же, передается некий идентификатор версии конфигурации confver и еще один дополнительный параметр id – это идентификатор общего модуля.
В ответ на этот запрос к нам приходит описание.
Для клиентских общих модулей нам приходит байт-код.
Для серверных общих модулей к нам приходит описание экспортного программного интерфейса. В блоке func перечислены функции. Здесь мы можем видеть:
-
имя функции;
-
количество параметров у каждой из функций;
-
массив с параметрами;
-
массив с пометками о том, какие параметры поддерживают значения по умолчанию.
И то же самое в блоке proc с процедурами.
Найдем какой-нибудь пример, где производится какой-нибудь вызов. Вызов у нас производится в методе call, которому мы в параметрах передаем идентификатор модуля, а в заголовках – передаем идентификатор сессии vrs-session.
В теле мы передаем метод, который мы хотим вызвать. И, собственно, параметры этого метода.
Если функция выполнится успешно, она вернет нам какой-то результат.
Четыре запроса в Postman
Теперь давайте все то же самое повторим в Postman.
Я сюда вынес эти несколько запросов, чтобы буквально по шагам повторить их. Я мог бы сделать это кодом на JavaScript прямо в браузере, или мог бы написать код на 1С, который будет вызывать этот запрос – это все равнозначные варианты, просто в Postman мне будет это быстрее сделать.
Авторизация в программе. Для авторизации передаем в параметрах метода login параметры:
-
cred, где содержится логин и пароль в base64;
-
vl, устанавливающий русский язык;
-
и clnId, где содержится произвольный уникальный идентификатор клиента.
Выполняем. В заголовках ответа к нам приходит идентификатор сессии vrs-session – копируем его.
В теле ответа к нам приходит значение assemblyVersion.
Получение списка модулей. Когда мы хотим получить список модулей, мы методу defs в параметре confver передаем скопированное значение assemblyVersion
И в заголовке запроса указываем идентификатор сессии vrs-session.
Вызываем – получаем список описаний модулей. В нем находим интересующий нас модуль РегламентированнаяОтчетностьАЛКОВызовСервера и копируем его идентификатор id.
Описание модуля. Для метода defs передаем параметры confver и скопированный id модуля (не забываем, что в заголовке запроса нужно указать идентификатор сессии vrs-session) – и получаем описание модуля, где можно увидеть все процедуры и функции, которые у него доступны.
В данном случае нас интересует функция ВерсияПлатформы(). Видно, что она доступна для вызова, параметров у нее нет. Теперь давайте вызовем эту функцию.
Вызов методов модуля. Для вызова мы используем метод call, в который передается параметр – идентификатор метода id, который мы получили на предыдущих шагах.
В хедере указываем идентификатор сессии vrs-session,
А в теле указываем метод, который мы хотим получить – у него параметров нет.
В полученном ответе видно, что мы вызывали метод «ВерсияПлатформы», и видно его возвращаемое значение – это именно строковое значение версии платформы, как и ожидалось.
Таким путем можно вызвать любой метод. Методы, в которых есть параметры, вызывать чуть сложнее.
Разобраться в том, как можно вызвать метод без параметров через веб-интерфейс сервера 1С, я смог приблизительно за час. Если бы я потратил два-три часа, я бы смог уже и с параметрами вызывать – это не сильно сложнее. Если потратить еще день, можно уверенно оперировать и вызывать контекстные и неконтекстные методы формы – это достаточно простое действие.
Главное, нужно понимать, что, если мы не даем пользователю открывать обработку, в которой он может написать код на 1С, это не значит, что пользователь у нас с конфигурацией ничего сделать не сможет.
Еще раз кратко – доступные прикладные методы HTTP-интерфейса сервера 1С
Я показал, как можно вызвать метод в общем модуле, а теперь хочу кратко проговорить это еще раз на слайдах, чтобы было более понятно.
С помощью GET-запроса e1cib/users мы можем получить список пользователей, для которых стоит галочка «Показывать в списке выбора».
Здесь на слайде показана строка для авторизации в веб-клиенте – отправляем POST-запрос, куда передаем логин, пароль, который преобразуется в base64. Причем этот метод даже описан на ИТС.
В заголовке ответа по результатам этого запроса мы получаем значение идентификатора сессии vrs-session, который надо будет использовать в последующих запросах к серверу.
И из тела ответа берем версию сборки assemblyVersion.
Здесь на слайде показано, каким способом мы можем получить список модулей, который доступен для вызова – делается GET-запрос, в котором передается значение версии сборки confver, и в заголовках передаем идентификатор сессии vrs-session.
В теле ответа нас интересует идентификатор id для нужного модуля.
Вот таким образом можно получить информацию о том, что этот модуль предоставляет.
Зачем это нужно? Сейчас в «Бухгалтерии предприятия» я просто скачал конфигурацию той же версии, что и в облаке 1С, и посмотрел в конфигураторе, что за методы есть у нее в модулях.
Но у разработчиков часто бывает ситуация, когда пользователи ставят в облаке какое-то расширение, которого в физическом виде с возможностью посмотреть его код, у нас нет. Однако мы можем вот таким образом получить программный интерфейс, который предоставляет это расширение. И методом подбора посмотреть, что интересного в нем есть – повызывать эти методы.
Здесь на слайде показан сам вызов – мы передаем POST-запрос, в котором:
-
в параметрах запроса указывается id – идентификатор модуля;
-
в заголовке передаем идентификатор сессии vrs-session;
-
а в теле формируем структуру в определенном формате, в которой указывается имя метода, который мы хотим вызвать, и параметры.
В данном случае метод ВерсияПлатформы() не имеет параметров, поэтому параметры – это пустой массив. Если параметры есть, они будут заполняться. Там немного посложнее, но это не какой-то Rocket Science.
Я надеюсь, после моего рассказа у вас появится немного больше понимания, что за тем, что вы делаете доступным для вызова на клиенте, надо следить очень внимательно. Потому что все, что вы там напишете, можно использовать против вас, имея под рукой вот такие нехитрые инструменты.
Поэтому никогда не выставляйте рабочую базу в интернет, чтобы дать туда публичный доступ для какого-то личного кабинета. Если хотите сделать что-то подобное – заморачивайтесь с синхронизацией, разделяйте информацию на разные базы.
Вопросы
Можно вызывать любые методы из любых серверных модулей, или только тех, которые доступны на форме? Насколько я понимаю, ответ простой – любые открытые методы, у которых есть программный интерфейс и у модуля стоит признак «Вызов сервера».
Да. Вызвать вы его точно можете, он может просто не выполниться полностью, например, потому, что внутри идет обращение к базе данных, а на эти объекты базы данных нет прав, или явно идет проверка, что если роль доступна такая-то, то делаем, если нет, то кидаем исключение.
Протокол работы тонкого клиента идентичен?
По сути, с тонким клиентом можно сделать все то же самое, только чуть посложнее разобраться – там может использоваться шифрование, сжатие. Но в сам процесс там вклиниться не сложно – для этого есть такие инструменты, как wireshark. А так – все то же самое.
Если мы можем вызвать любой экспортный метод из общего модуля с флагом «Вызов сервера», можем ли мы как-то вызывать методы глобального контекста, например, «Выполнить()» с произвольным кодом в параметрах?
На клиенте это точно можно делать. А что касается сервера – у пользователя же есть настройка «Защита от опасных действий», которая может ограничивать это выполнение. И еще, я так понимаю, в КОРП-версии на уровне профилей тоже можно все ограничить.
При этом нужно иметь в виду, что у нас большинство программ 1С выставлено без особого подхода к безопасности – защиту от опасных действий часто отключают, и в таком случае можно вообще делать, что угодно.
В моей практике люди написали модуль интеграции, который работал за счет серверного метода, куда передается некий произвольный параметр в виде текста. Этот параметр потом подавался на вход методу «Выполнить» – выполняй что хочешь. И аргументировали это тем, что им не нужно будет каждый раз переписывать эту обработку – достаточно отобрать у пользователя права на запуск внешних обработок, тогда он не сможет написать код, который вызовет этот метод, и все будет хорошо. Пришлось доказывать, что все-таки не все хорошо, что можно за пять минут написать инструмент, который воспользуется этой универсальной функцией.
*************
Данная статья написана по итогам доклада (видео), прочитанного на онлайн-митапе "Безопасность в 1С".