В чём опасность BinaryFormatter?

В чём опасность BinaryFormatter?


Может ли кто-то аргументированно объяснить, в чём опасность сериализации данных в двоичном виде, или сослаться на хорошее объяснение? Желательно без аргументов вроде "это опасно по своей природе" или "это не может быть безопасно обработано"


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

Форматтер не учитывает видимость членов класса и даже не запускает контструктор так, как это делается с помощью new. Он создаёт экземпляры по сути вот так:

var obj = FormatterServices.GetUninitializedObject(typeof(MyType));

То есть если в конструкторе есть какая-либо защита целостности состояния объекта при создании экземпляра, её легко обойти вот таким образом. Ну а дальше на что фантазии хватит.

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

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

В некоторых инфраструктурах форматтер - лучшее средство для максимально быстрой и удобной передачи данных между нодами в кластере, например через AMQP. То есть в защищённой от внешнего воздействия среде его вполне можно использовать, и его используют. Как минимум потому что ни один сериализатор не даст такой же производительности при использовании сложных объектов, хотя местами тот же JsonSerializer в последних (8+) дотнетах за счёт кодогенерации начинает догонять.


У BinaryFormatter-а есть две больших проблемы: он обходит конструкторы (которые могут валидировать данные), и он полиморфический. Последняя проблема особенно опасна, поскольку при помощи этого атакующий легко может заставить BinaryFormatter создать объект по своему выбору. Например, следующим образом.

Пускай атакующий хочет создать в вашем процессе объект типа X. Допустим, у вас десериализуется объект, у которого есть поле типа object, и он не реализует ISerializable. Тогда BinaryFormatter возьмёт реальный тип объекта из сериализованных данных. Атакующий может подставить реально тип X, который будет десериализован.

А в чём опасность того, что будет создан какой-то объект? Ведь атакующий, в конце-концов, не управляет этим объектом? Дело в том, что в .NET есть много классов, которые могут быть опасны, если просто так окажутся в вашем процессе. исследованию того, какие именно классы опасны, посвящён, например, проект why so serial, который приводит примеры опасных классов (и умеет создавать их десериализованное представление).

Самый простой пример — старый класс TempObjectCollection, который в своём финализаторе удаляет набор файлов, имена которых хранятся в поле объекта. Как вы понимаете, поле объекта контролируется атакующим, и он может «напихать» туда любые имена файлов.

Но может быть, можно спасти BinaryFormatter, ограничив его лишь «белым списком» классов для десериализации? К сожалению, и это не поможет, по двум причинам.

Первая причина — практически весь код, использующий BinaryFormatter, исходит из полиморфизма. И этот код просто перестанет работать.

Вторая причина — десериализация, при которой обходится валидация значений в конструкторе тоже очень опасна. Например, даже если вы десериализуете массивы и словари с числовыми/строковыми ключами, всё равно атакующий может испортить ваш объект: в том же Dictionary<K, V> есть много полей, которые могут привести, например, к переполнению памяти.




Report Page