Читай и выполняй. Как работает эксплоит новой уязвимости в GitLab - «Новости» » Самоучитель CSS
Меню
Наши новости
Учебник CSS

Невозможно отучить людей изучать самые ненужные предметы.

Введение в CSS
Преимущества стилей
Добавления стилей
Типы носителей
Базовый синтаксис
Значения стилевых свойств
Селекторы тегов
Классы
CSS3

Надо знать обо всем понемножку, но все о немногом.

Идентификаторы
Контекстные селекторы
Соседние селекторы
Дочерние селекторы
Селекторы атрибутов
Универсальный селектор
Псевдоклассы
Псевдоэлементы

Кто умеет, тот делает. Кто не умеет, тот учит. Кто не умеет учить - становится деканом. (Т. Мартин)

Группирование
Наследование
Каскадирование
Валидация
Идентификаторы и классы
Написание эффективного кода

Самоучитель CSS

Вёрстка
Изображения
Текст
Цвет
Линии и рамки
Углы
Списки
Ссылки
Дизайны сайтов
Формы
Таблицы
CSS3
HTML5

Новости

Блог для вебмастеров
Новости мира Интернет
Сайтостроение
Ремонт и советы
Все новости

Справочник CSS

Справочник от А до Я
HTML, CSS, JavaScript

Афоризмы

Афоризмы о учёбе
Статьи об афоризмах
Все Афоризмы

Видео Уроки


Наш опрос



Наши новости

      
      
  • 24 марта 2016, 16:20
26-05-2020, 12:40
Читай и выполняй. Как работает эксплоит новой уязвимости в GitLab - «Новости»
Рейтинг:
Категория: Новости

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

Автор эксплоита, который мы разберем, — исследователь и разработчик из Австрии Уильям vakzz Боулинг (William Bowling). Он обнаружил, что класс UploadsRewriter РїСЂРё определенных условиях никак РЅРµ проверяет путь РґРѕ файла. Это открывает злоумышленнику возможность скопировать любой файл РІ системе Рё использовать его РІ качестве аттача РїСЂРё переносе issue РёР· РѕРґРЅРѕРіРѕ проекта РІ РґСЂСѓРіРѕР№.



РќР° этом исследователь РЅРµ остановился Рё нашел возможность превратить эту «читалку» РІ полноценную уязвимость типа RCE. Атакующий может прочитать файл secrets.yml, в котором находится токен для подписи cookie. Специально сформированная и подписанная кука позволяет выполнять произвольные команды на сервере.



Читай и выполняй. Как работает эксплоит новой уязвимости в GitLab - «Новости»
INFO

Уязвимость относится к типу path traversal и получила номер CVE-2020-10977. Уязвимы версии GitLab EE/CE начиная с 8.5 и 12.9. Компания GitLab в рамках программы bug bounty выплатила за этот баг 20 тысяч долларов.


Стенд



Тестовое окружение для изучения этого бага поднять очень просто, так как у GitLab есть официальный докер-репозиторий. Можно одной командой запустить контейнер с любой версией приложения. Поэтому поднимем последнюю уязвимую версию — 12.9.0.



docker run --rm -d --hostname gitlab.vh -p 443:443 -p 80:80 -p 2222:22 --name gitlab gitlab/gitlab-ce:12.9.0-ce.0


Приставка CE означает Community Edition, можно взять и Enterprise (EE), но тогда придется возиться с получением ключа для пробного периода. Для демонстрационных целей хватит и CE, обе версии одинаково уязвимы.
При первом посещении GitLab попросит установить пароль главного админа. По дефолту логин — admin@example.com.



Задаем пароль админа после первого запуска GitLab
Задаем пароль админа после первого запуска GitLab

Дальше нам нужно создать два любых проекта.



Создаем два репозитория на тестовом стенде
Создаем два репозитория на тестовом стенде

По факту стенд уже готов, и можно приступать к рассмотрению деталей. Однако я еще скачаю исходники GitLab, чтобы наглядно продемонстрировать, в какие части кода закралась ошибка.



 

Чтение локальных файлов



Р?так, сразу Рє делу — проблема находится РІ функции копирования issue.



INFO



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





Создадим РІ проекте Test новый issue.



Создание нового issue в GitLab

При создании можно описать детали проблемы в формате Markdown, а еще загрузить произвольный файл, например скриншот с ошибкой или лог-файл, чтобы упростить жизнь разработчикам.



Прикрепление файла к описанию возникшей проблемы

Все загруженные файлы складываются на диск в папку /var/opt/gitlab/gitlab-rails/uploads/. Р—Р° это отвечает класс FileUploader



doc/development/file_storage.md


31: | Description                           | In DB? | Relative path (from CarrierWave.root)                       | Uploader class         | model_type |
...
39: | Issues/MR/Notes Markdown attachments | yes | uploads/:project_path_with_namespace/:random_hex/:filename | `FileUploader` | Project |


Сначала генерируется рандомная hex-строка, которая будет именем папки.



app/uploaders/file_uploader.rb


011: class FileUploader < GitlabUploader
...
019:
VALID_SECRET_PATTERN = %r{Ah{10,32}z}.freeze
...
069:
def self.generate_secret
070:
SecureRandom.hex
071:
end
...
157:
def secret
158:
@secret ||= self.class.generate_secret
159: 
160:
raise InvalidSecret unless @secret =~ VALID_SECRET_PATTERN
161: 
162:
@secret
163:
end


А имя файла используется то, которое передали при загрузке.



app/uploaders/file_uploader.rb


212:   def secure_url
213: File.join('/uploads', @secret, filename)
214: end


После загрузки аттача ссылка в формате Markdown вставляется в описание проблемы. Сохраним ее.



GitLab позволяет перенести issue из одного проекта в другой, что бывает очень полезно, если ошибка касается и другого продукта того же разработчика.



Эта кнопка перемещает сообщения о проблемах между проектами

После нажатия на кнопку выбираем проект, куда хотим отправить issue.



Выбор проекта для перемещения issue

Во время перемещения в старом проекте issue закрывается и появляется в новом.



Старый issue в новом проекте

Причем аттачи копируются, а не переносятся. То есть для них создаются новые файлы и ссылки на них, соответственно.



Прикрепленные файлы копируются при перемещении issue

Посмотрим в коде, как выполняется перенос. Все роуты, которые касаются issues, можно найти в папке routes РІ файле issues.rb. Там в том числе есть роут move, который отвечает за перенос. Именно он обрабатывает пользовательский POST-запрос с необходимыми параметрами.



config/routes/issues.rb


5: resources :issues, concerns: :awardable, constraints: { id: /d+/ } do
6:
member do
...
9:
post :move


Затем мы попадаем в одноименную функцию.



app/controllers/projects/issues_controller.rb


123:   def move
124: params.require(:move_to_project_id)
125:
126: if params[:move_to_project_id].to_i > 0
127: new_project = Project.find(params[:move_to_project_id])
128: return render_404 unless issue.can_move?(current_user, new_project)
129:
130: @issue = Issues::UpdateService.new(project, current_user, target_project: new_project).execute(issue)
131: end


Здесь вызывается Issues::UpdateService.new, РІ качестве аргументов передаются ID текущего проекта, пользователь, который инициировал перенос, Рё проект, РєСѓРґР° РЅСѓР¶РЅРѕ перенести issue. После этого управление переходит Рє классу UpdateService. Он, в свою очередь, вызывает метод move_issue_to_new_project.



app/services/issues/update_service.rb


03: module Issues
04: class UpdateService < Issues::BaseService
05: include SpamCheckMethods
06:
07: def execute(issue)
08: handle_move_between_ids(issue)
09: filter_spam_check_params
10: change_issue_duplicate(issue)
11: move_issue_to_new_project(issue) || update_task_event(issue) || update(issue)
12: end


app/services/issues/update_service.rb


097:     def move_issue_to_new_project(issue)
098: target_project = params.delete(:target_project)
099:
100: return unless target_project &&
101: issue.can_move?(current_user, target_project) &&
102: target_project != issue.project
103:
104: update(issue)
105: Issues::MoveService.new(project, current_user).execute(issue, target_project)
106: end


Следующую часть уже выполняет класс Issues::MoveService — это наследник Issuable::Clone::BaseService.



app/services/issues/move_service.rb


3: module Issues
4: class MoveService < Issuable::Clone::BaseService


Здесь сначала вызывается метод execute из дочернего, а затем из родительского класса.



app/services/issues/move_service.rb


03: module Issues
04: class MoveService < Issuable::Clone::BaseService
05: MoveError = Class.new(StandardError)
06:
07: def execute(issue, target_project)
08: @target_project = target_project
...
18: super
19:
20: notify_participants
21:
22: new_entity
23: end


В родителе нас интересует вызов метода update_new_entity.



app/services/issuable/clone/base_service.rb


03: module Issuable
04: module Clone
05: class BaseService < IssuableBaseService
06: attr_reader :original_entity, :new_entity
07:
08: alias_method :old_project, :project
09:
10: def execute(original_entity, new_project = nil)
11: @original_entity = original_entity
12:
13: # Using transaction because of a high resources footprint
14: # on rewriting notes (unfolding references)
15: #
16: ActiveRecord::Base.transaction do
17: @new_entity = create_new_entity
18:
19: update_new_entity
20: update_old_entity
21: create_notes
22: end
23: end


После создания нового issue в целевом проекте этот метод выполняет перенос данных из оригинального issue.



app/services/issuable/clone/base_service.rb


27:       def update_new_entity
28: rewriters = [ContentRewriter, AttributesRewriter]
29:
30: rewriters.each do |rewriter|
31: rewriter.new(current_user, original_entity, new_entity).execute
32: end
33: end


За копирование отвечает ContentRewriter.



app/services/issuable/clone/content_rewriter.rb


03: module Issuable
04: module Clone
05: class ContentRewriter < ::Issuable::Clone::BaseService
06: def initialize(current_user, original_entity, new_entity)
07: @current_user = current_user
08: @original_entity = original_entity
09: @new_entity = new_entity
10: @project = original_entity.project
11: end
...
13: def execute
14: rewrite_description
15: rewrite_award_emoji(original_entity, new_entity)
16: rewrite_notes
17: end


На данном этапе нам интересен только метод rewrite_description, который копирует содержимое описания ошибки.



app/services/issuable/clone/content_rewriter.rb


21:       def rewrite_description
22: new_entity.update(description: rewrite_content(original_entity.description))
23: end


Наконец мы добрались до rewrite_content. Здесь Рё вызывается метод, который дублирует аттачи старого issue РІ новый. Этим занимается Gitlab::Gfm::UploadsRewriter.



54:       def rewrite_content(content)
55: return unless content
56:
57: rewriters = [Gitlab::Gfm::ReferenceRewriter, Gitlab::Gfm::UploadsRewriter]
58:
59: rewriters.inject(content) do |text, klass|
60: rewriter = klass.new(text, old_project, current_user)
61: rewriter.rewrite(new_parent)
62: end
63: end


Он парсит содержимое описания issue в поисках шаблона с аттачем.



app/uploaders/file_uploader.rb


11: class FileUploader < GitlabUploader
...
17:
MARKDOWN_PATTERN = %r{!?[.*?](/uploads/(?<secret>[0-9a-f]{32})/(?<file>.*?))}.freeze


lib/gitlab/gfm/uploads_rewriter.rb


05: module Gitlab
06: module Gfm
...
14: class UploadsRewriter
15: def initialize(text, source_project, _current_user)
16: @text = text
17: @source_project = source_project
18: @pattern = FileUploader::MARKDOWN_PATTERN
19: end
20:
21: def rewrite(target_parent)
22: return @text unless needs_rewrite?
23:
24: @text.gsub(@pattern) do |markdown|


И если находит, то копирует этот файл.



25:
file = find_file(@source_project, $~[:secret], $~[:file])
26:
break markdown unless file.try(:exists?)
27: 
28:
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
29:
moved = klass.copy_to(file, target_parent)


lib/gitlab/gfm/uploads_rewriter.rb


60:       def find_file(project, secret, file)
61: uploader = FileUploader.new(project, secret: secret)
62: uploader.retrieve_from_store!(file)
63: uploader
64: end


app/uploaders/file_uploader.rb


165:   # Return a new uploader with a file copy on another project
166: def self.copy_to(uploader, to_project)
167: moved = self.new(to_project)
168: moved.object_store = uploader.object_store
169: moved.filename = uploader.filename
170:
171: moved.copy_file(uploader.file)
172: moved
173: end


app/uploaders/file_uploader.rb


175:   def copy_file(file)
176: to_path = if file_storage?
177: File.join(self.class.root, store_path)
178: else
179: store_path
180: end
181:
182: self.file = file.copy_to(to_path)
183: record_upload # after_store is not triggered
184: end


Как видишь, ни find_file, РЅРё copy_to, ни copy_file никак РЅРµ проверяют РёРјСЏ файла, Р° значит, любой файл РІ системе может легким движением СЂСѓРєРё превратиться РІ аттач.



Чтобы это проверить, воспользуемся методом выхода РёР· директории РїСЂРё помощи стандартного ../. Нужно только определиться с количеством ходов наверх. По дефолту полный путь до загружаемых файлов в контейнере GitLab такой, как на скриншоте.



Путь к аттачам GitLab на диске

Полный путь до картинки из моего issue будет выглядеть следующим образом:



/var/opt/gitlab/gitlab-rails/uploads/@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35/ed4ae110d9f4021350e5c1eaa123b6e1/mia.jpg


Длинный код в середине — это уникальный хеш текущего проекта. Таким образом, нам нужно минимум десять конструкций ../, чтобы попасть РІ корневую директорию контейнера.



Попробуем прочитать файл /etc/passwd. Редактируем описание issue и добавляем необходимое количество ../ РІ пути Рє файлу. РЇ рекомендую ставить РёС… побольше, чтобы точно попасть РєСѓРґР° РЅСѓР¶РЅРѕ.



Path traversal в имени прикрепляемого к issue файла
Path traversal в имени прикрепляемого к issue файла

Теперь сохраняем и переносим файл в другой проект.



Успешная подмена прикрепленного файла через path traversal в GitLab
Успешная подмена прикрепленного файла через path traversal в GitLab

Появилась возможность скачать файл passwd, и если это сделать, то ты увидишь содержимое /etc/passwd.



Чтение локальных файлов через path traversal в GitLab

Таким образом можно читать все, на что хватает прав у пользователя, от имени которого работает GitLab. В случае с Docker это git.
Тогда возникает другой вопрос: а что же интересного можно прочитать?

Цитирование статьи, картинки - фото скриншот - Rambler News Service.
Иллюстрация к статье - Яндекс. Картинки.
Есть вопросы. Напишите нам.
Общие правила  поведения на сайте.

В конце марта 2020 года в популярном инструменте GitLab был найден баг, который позволяет перейти от простого чтения файлов в системе к выполнению произвольных команд. Уязвимости присвоили статус критической, поскольку никаких особых прав в системе атакующему не требуется. В этой статье я покажу, как возникла эта брешь и как ее эксплуатировать. Автор эксплоита, который мы разберем, — исследователь и разработчик из Австрии Уильям vakzz Боулинг (William Bowling). Он обнаружил, что класс UploadsRewriter РїСЂРё определенных условиях никак РЅРµ проверяет путь РґРѕ файла. Это открывает злоумышленнику возможность скопировать любой файл РІ системе Рё использовать его РІ качестве аттача РїСЂРё переносе issue РёР· РѕРґРЅРѕРіРѕ проекта РІ РґСЂСѓРіРѕР№. РќР° этом исследователь РЅРµ остановился Рё нашел возможность превратить эту «читалку» РІ полноценную уязвимость типа RCE. Атакующий может прочитать файл secrets.yml, в котором находится токен для подписи cookie. Специально сформированная и подписанная кука позволяет выполнять произвольные команды на сервере. INFO Уязвимость относится к типу path traversal и получила номер CVE-2020-10977. Уязвимы версии GitLab EE/CE начиная с 8.5 и 12.9. Компания GitLab в рамках программы bug bounty выплатила за этот баг 20 тысяч долларов. Стенд Тестовое окружение для изучения этого бага поднять очень просто, так как у GitLab есть официальный докер-репозиторий. Можно одной командой запустить контейнер с любой версией приложения. Поэтому поднимем последнюю уязвимую версию — 12.9.0. Приставка CE означает Community Edition, можно взять и Enterprise (EE), но тогда придется возиться с получением ключа для пробного периода. Для демонстрационных целей хватит и CE, обе версии одинаково уязвимы. При первом посещении GitLab попросит установить пароль главного админа. По дефолту логин — admin@example.com. Задаем пароль админа после первого запуска GitLab Дальше нам РЅСѓР¶РЅРѕ создать РґРІР° любых проекта. Создаем РґРІР° репозитория РЅР° тестовом стенде РџРѕ факту стенд СѓР¶Рµ готов, Рё РјРѕР¶РЅРѕ приступать Рє рассмотрению деталей. Однако СЏ еще скачаю РёСЃС…РѕРґРЅРёРєРё GitLab, чтобы наглядно продемонстрировать, РІ какие части РєРѕРґР° закралась ошибка. Чтение локальных файлов Р?так, сразу Рє делу — проблема находится РІ функции копирования issue. INFO Р’ СЂСѓСЃСЃРєРѕР№ версии интерфейса issue перевели как «обсуждение», РЅРѕ РјРЅРµ кажется, что РїРѕ смыслу ближе термин «баг», «ошибка» или «проблема», ведь именно РёС… чаще всего Рё описывают РІ issue. РЇ Р±СѓРґСѓ использовать то английское написание, то различные вариации СЂСѓСЃСЃРєРѕРіРѕ, так что РЅРµ удивляйся. Создадим РІ проекте Test новый issue. Создание нового issue в GitLab При создании можно описать детали проблемы в формате Markdown, а еще загрузить произвольный файл, например скриншот с ошибкой или лог-файл, чтобы упростить жизнь разработчикам. Прикрепление файла к описанию возникшей проблемы Все загруженные файлы складываются на диск в папку /var/opt/gitlab/gitlab-rails/uploads/. Р—Р° это отвечает класс FileUploader doc/development/file_storage.md Сначала генерируется рандомная hex-строка, которая будет именем папки. app/uploaders/file_uploader.rb А имя файла используется то, которое передали при загрузке. app/uploaders/file_uploader.rb После загрузки аттача ссылка в формате Markdown вставляется в описание проблемы. Сохраним ее. GitLab позволяет перенести issue из одного проекта в другой, что бывает очень полезно, если ошибка касается и другого продукта того же разработчика. Эта кнопка перемещает сообщения о проблемах между проектами После нажатия на кнопку выбираем проект, куда хотим отправить issue. Выбор проекта для перемещения issue Во время перемещения в старом проекте issue закрывается и появляется в новом. Старый issue в новом проекте Причем аттачи копируются, а не переносятся. То есть для них создаются новые файлы и ссылки на них, соответственно. Прикрепленные файлы копируются при перемещении issue Посмотрим в коде, как выполняется перенос. Все роуты, которые касаются issues, можно найти в папке routes РІ файле issues.rb. Там в том числе есть роут move, который отвечает за перенос. Именно он обрабатывает пользовательский POST-запрос с необходимыми параметрами. config/routes/issues.rb Затем мы попадаем в одноименную функцию. app/controllers/projects/issues_controller.rb Здесь вызывается Issues::UpdateService.new, РІ качестве аргументов передаются ID текущего проекта, пользователь, который инициировал перенос, Рё проект, РєСѓРґР° РЅСѓР¶РЅРѕ перенести issue. После этого управление переходит Рє классу UpdateService. Он, в свою очередь, вызывает метод move_issue_to_new_project. app/services/issues/update_service.rb app/services/issues/update_service.rb Следующую часть уже выполняет класс Issues::MoveService — это наследник Issuable::Clone::BaseService. app/services/issues/move_service.rb Здесь сначала вызывается метод execute из дочернего, а затем из родительского класса. app/services/issues/move_service.rb В родителе нас интересует вызов метода update_new_entity. app/services/issuable/clone/base_service.rb После создания нового issue в целевом проекте этот метод выполняет перенос данных из оригинального issue. app/services/issuable/clone/base_service.rb За копирование отвечает ContentRewriter. app/services/issuable/clone/content_rewriter.rb На данном этапе нам интересен только метод rewrite_description, который копирует содержимое описания ошибки. app/services/issuable/clone/content_rewriter.rb Наконец мы добрались до rewrite_content. Здесь Рё вызывается метод, который дублирует аттачи старого issue РІ новый. Этим занимается Gitlab::Gfm::UploadsRewriter. Он парсит содержимое описания issue в поисках шаблона с аттачем. app/uploaders/file_uploader.rb lib/gitlab/gfm/uploads_rewriter.rb И если находит, то копирует этот файл. lib/gitlab/gfm/uploads_rewriter.rb app/uploaders/file_uploader.rb app/uploaders/file_uploader.rb Как видишь, ни find_file, РЅРё copy_to, ни copy_file никак РЅРµ проверяют РёРјСЏ файла, Р° значит, любой файл РІ системе может легким движением СЂСѓРєРё превратиться РІ аттач. Чтобы это проверить, воспользуемся методом выхода РёР· директории РїСЂРё помощи стандартного /. Нужно только определиться с количеством ходов наверх. По дефолту полный путь до загружаемых файлов в контейнере GitLab такой, как на скриншоте. Путь к аттачам GitLab на диске Полный путь до картинки из моего issue будет выглядеть следующим образом: Длинный код в середине — это уникальный хеш текущего проекта. Таким образом, нам нужно минимум десять конструкций /, чтобы попасть РІ корневую директорию контейнера. Попробуем прочитать файл /etc/passwd. Редактируем описание issue и добавляем необходимое количество / РІ пути Рє файлу. РЇ рекомендую ставить РёС… побольше, чтобы точно попасть РєСѓРґР° РЅСѓР¶РЅРѕ. Path traversal РІ имени прикрепляемого Рє issue файла Теперь сохраняем Рё переносим файл РІ РґСЂСѓРіРѕР№ проект. Успешная подмена прикрепленного файла через path traversal РІ GitLab Появилась возможность скачать файл passwd, Рё если это сделать, то ты увидишь содержимое /etc/passwd. Чтение локальных файлов через path traversal в GitLab Таким образом можно читать все, на что хватает прав у пользователя, от имени которого работает GitLab. В случае с Docker это git. Тогда возникает другой вопрос: а что же интересного можно прочитать?
Просмотров: 616
Комментариев: 0:   26-05-2020, 12:40
Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь. Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

 
Еще новости по теме:



Другие новости по теме: