В чём опасность BinaryFormatter?
Может ли кто-то аргументированно объяснить, в чём опасность сериализации данных в двоичном виде, или сослаться на хорошее объяснение? Аргументы вроде "это опасно по своей природе" или "это не может быть безопасно обработано", приведённые в "Deserialization risks in use of BinaryFormatter and related types", меня не убедили.
Ответы (3 шт):
Суть в том что при десериализации форматтер может создать любой тип в памяти, вообще совершенно любой. Например если подсунуть куда-то свою либу со своим типом, а затем скормить форматтеру свой сериализованный объект, то он просто запустит ваш код без ведома приложения, которое ждёт какие-то данные.
Форматтер не учитывает видимость членов класса и даже не запускает контструктор так, как это делается с помощью new
. Он создаёт экземпляры по сути вот так:
var obj = FormatterServices.GetUninitializedObject(typeof(MyType));
То есть если в конструкторе есть какая-либо защита целостности состояния объекта при создании экземпляра, её легко обойти вот таким образом. Ну а дальше на что фантазии хватит.
Да, форматтер быстрый и надёжный в плане создания снапшота объекта и записи его на диск или для передачи по сети, но при этом следует очень внимательно отнестись к нюансам безопасности.
По сути никто не запрещает его использовать, пользуйтесь наздоровье, но его вынесли из основного кода .NET во внешний пакет и прикрыли ключами в файле проекта не просто так. Поэтому установить и разлочить его придётся руками, это и есть подтверждение тому, раз вы решились всё-таки его использовать - то понимаете, что делаете.
В некоторых инфраструктурах форматтер - лучшее средство для максимально быстрой и удобной передачи данных между нодами в кластере, например через AMQP. То есть в защищённой от внешнего воздействия среде его вполне можно использовать, и его используют. Как минимум потому что ни один сериализатор не даст такой же производительности при использовании сложных объектов, хотя местами тот же JsonSerializer
в последних (8+) дотнетах за счёт кодогенерации начинает догонять.
Смотрите, в чём дело.
У BinaryFormatter-а есть две больших проблемы: он не выполняет конструкторы (которые могут валидировать данные), и он полиморфный. Последняя проблема особенно опасна, поскольку при помощи этого атакующий легко может заставить BinaryFormatter создать объект по своему выбору. Например, следующим образом.
Пускай у атакующего есть возможность самому скрафтить бинарные данные для десериализации. Допустим, атакующий хочет создать в вашем процессе объект типа X
. Если у вас десериализуется объект другого типа Y
, атакующий может подсунуть в сериализованные данные объект нужного ему типа X
. Приведение типов провалится, но объект типа X
тем не менее будет создан.
(Если атакующий не хочет, чтобы были проблемы с приведением типов, он может подыскать сериализуемый тип Y
, у которого есть поле типа object
, и разместить экземпляр класса X
в этом поле.)
А в чём опасность того, что будет создан какой-то объект? Ведь атакующий, в конце-концов, не управляет этим объектом? Дело в том, что в .NET есть много классов, которые могут быть опасны, если просто так окажутся в вашем процессе. Исследованию того, какие именно классы опасны, посвящён, например, проект why so serial, который приводит примеры опасных классов (и умеет создавать их десериализованное представление).
Самый простой пример — старый класс TempObjectCollection
, который в своём финализаторе удаляет набор файлов, имена которых хранятся в поле объекта. Как вы понимаете, поле объекта контролируется атакующим, и он может «напихать» туда любые имена файлов.
Кроме того, такие классы вполне могут быть найтись не в стандартной библиотеке .NET, а найтись в популярных nuget-пакетах, или просто быть непреднамеренно написанными авторами атакуемой программы.
Но может быть, можно спасти BinaryFormatter, ограничив его лишь «белым списком» классов для десериализации? К сожалению, и это не поможет, по двум причинам.
Первая причина — практически весь код, использующий BinaryFormatter, исходит из полиморфизма. И этот код просто перестанет работать.
Вторая причина — десериализация, при которой обходится валидация значений в конструкторе тоже очень опасна. Например, даже если вы десериализуете массивы и словари с числовыми/строковыми ключами, всё равно атакующий может испортить ваш объект: в том же Dictionary<K, V>
есть много полей, которые могут привести, например, к переполнению памяти.
Написано по мотивам:
Коротко - BinaryFormatter
не совместим между разными версиями .NET.
Ещё он устарел и не подреживается в последних версиях .net
Eсли вы сериализуете им данные в одной версии, а потом обновите версию .net в проекте, то будут ошибки десериализации. Либо если на клиенте и сервер отличается версия .net