Fossil хранит все данные – коммиты, тикеты, странички вики, аттачменты, и т.д. – в виде артефактов, к которым он адресуется по SHA1 хэшу содержимого.

Например, вот так выглядит описание коммита (манифест) 30fbf3723ed00679c6 0006217c5d996b20cb5aa6:

C Add\stest.txt.
D 2012-08-12T11:40:52.146
F test.txt e2db724da21fa19b21835946bcf8359a598ef67c
P 337fd550fc7257170d25a6b22622c20d137d1b56
R 6e7be55bd50eabe58814c006fd8a25a6
U dchest
Z aa72952b1d3914bc6502058f0e66239a

Этот манифест говорит, что пользователем dchest был сделан коммит 8 августа 2012 года с комментарием «Add test.txt», и что теперь в проекте 1 файл, test.txt.

Сами файлы хранятся аналогично: как видно из манифеста, файл test.txt адресуется по хэшу содержимого, e2db724da21fa19b2 1835946bcf8359a598ef67c.

This is test.

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

Fossil deconstructed repository

Когда Fossil синхронизирует содержимое репозиториев, он получает от сервера все отсутствующие у клиента артефакты, и отправляет все отсутствующие у сервера артефакты, а потом парсит содержимое артефактов, чтобы получить полное состояние репозитория в момент синхронизации.

Как Fossil отличает простое содержимое файлов от манифестов? А никак – любой текст, который можно пропарсить как манифест, будет манифестом. В этом ничего страшного нет, потому что случайно сделать содержимое файла, который будет парсится как правильный манифест, сложно. А если неслучайно?

Тут к нам приходят на помощь аттачменты. В Fossil, как в любом нормальном баг-трекере, можно разрешить пользователям прикреплять файлы к тикетам или к вики-страничкам. Например, к этому тикету прикреплен файл с патчем. Что если прикрепить текстовый файл, содержимое которого описывает коммит, как в начале этой заметки?

Вот как можно было делать коммиты в любой репозиторий Fossil без прав на коммиты, только с правами на аттачменты:

  1. Склонировать репозиторий.
  2. Понаделать нужных коммитов (например, встроить куда-нибудь бэкдор).
  3. Запустить fossil deconstruct и найти коммиты и новые файлы для этих коммитов.
  4. Создать тикет и прикрепить к нему, один по одному, содержимое файлов из пункта 3.
  5. Готово – теперь репозиторий содержит ваши коммиты!

Что еще хуже, в оригинальном репозитории в timeline новые коммиты не покажутся пока администратор репозитория не запустит fossil rebuild или не синхронизируется с другой копией репозитория, а те, кто склонирует оригинальный репозиторий получат и наши злые коммиты.

Я сообщил об этой уязвимости автору. Он закрыл уязвимость 4 апреля: теперь любой аттачмент, который парсится как манифест, просто сжимается gzip’ом, то есть, если вы приаттачили malicious_commit.txt, он окажется в репозитории сжатым в malicious_commit.txt.gz. Вот только новая версия Fossil (1.23) вышла 8 августа, и в release notes про уязвимость ни слова, поэтому если у ваших проектов была всем открыта возможность добавлять аттачменты, сделайте fossil rebuild и проверьте историю коммитов, убедившись, что в репозитории нет лишних (или отсутствующих) коммитов. Ну и, естественно, если вы еще сидите на старых версиях Fossil, срочно обновитесь или хотя бы запретите аттачменты.