Баг в CLR: как затащить объект в песочницу без маршаллинга и вызвать Callback

в 9:25, , рубрики: .net, vulnerability, Блог компании Luxoft, вулнерабилити, информационная безопасность

Добрый день! Надеюсь, я уже завоевал на Хабре достижение «узнал автора по заголовку» -) Однако сегодня речь пойдет о свежей, еще не закрытой уязвимости в .Net, на которую меня навел своей мыслью один человек (кто подкинет ему инвайт?), который написал мне на почту:

Вы пытались IL кодом приводить объекты к строковому типу и передавать в другие домены?

Сначала я его не понял, но потом родился пример кода, который пробрасывает любой объект типа, находящегося в SharedDomain в песочницу и позволяет использовать его методы БЕЗ маршаллинга.

Дырой, с одной стороны это назвать достаточно сложно, т.к. почву для этого должен подготовить хост. И не самым обычным способом, надо заметить. Но с другой стороны… Да, это баг.

Первое, что нам понадобится — это ставшие обыденностью, методы EntityPtr.ToPointer() и *.ToInstance() из Баг в CLR: как затащить объект в песочницу без маршаллинга и вызвать Callback - 1 DotNetEx. Их комбинация заставляет приводить объект к несовместимому типу. Т.е. к тому типу, которым он не является:

string str = EntityPtr.ToInstance<string>(EntityPtr.ToPointer(new List<int>()));

Естественно, если вызвать любой метод у такой «строки», ничего не получится: вылетит Exception (кроме виртуальных методов от object — их позиции в таблице вирт методов совпадут с переопределенными в нашем типе и вызов произойдет корректно)

Однако, поскольку строка является уже сериализованным объектом, то при маршаллинге она передается по ссылке, без копирования. Это значит, что мы можем прокинуть ее в метод, код которого выполняется в «песочнице» и там, внутри, сделать кастинг обратно в тип.

Начну сразу со вкусного:


        private void methodInsideAppDomain(string str)
        {
            object tmp = str;
            var act = (Action)tmp;
            act();
        }

        public static void Go(string startingIntPtr)
        {
            // make appdomain
            var permissions = new PermissionSet(PermissionState.None);
            permissions.AddPermission(new SecurityPermission(SecurityPermissionFlag.Execution));
            var dom = AppDomain.CreateDomain("PseudoIsolated", null, new AppDomainSetup
            {
                ApplicationBase = AppDomain.CurrentDomain.BaseDirectory
            }, permissions);

            // create object instance
            var asmname = typeof (AppDomainRunner).Assembly.FullName;
            var typename = typeof (AppDomainRunner).FullName;
            var instance = (AppDomainRunner)dom.CreateInstanceAndUnwrap(asmname, typename);

            // enumerate objects from outside area to our appdomain area
            instance.methodInsideAppDomain(startingIntPtr);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Expression<Action> expression = () => Console.WriteLine("Surprise, motherf*ckers!");

            AppDomainRunner.Go(EntityPtr.ToInstance<string>(EntityPtr.ToPointer(expression.Compile())));

            Console.ReadKey();
        }
    }

Выведет приветствие на экран

Итак, что будет работать:

  1. Протаскивание объектов любого типа с кастингом внутри sandbox — в тип из Shared Domain.
  2. Если вызывать виртуальные методы базового класса, который находится в Shared Domain и которые вы переопределили на свои — вызовутся ваши
  3. Если вы передадите в качестве Action — скомпилированный Expression — также вызовется. Но при этом вызовется он также в sandbox, поскольку не произойдет переключения домена, который идет при маршаллинге.

Что нельзя:

  1. Приводить к своему типу в sandbox, поскольку в песочнице тип и все что с ним связано прогружено во второй раз и привести к такому же типу не получится: физически описатели типов в разных доменах будут по разным адресам, а значит получится InvalidCastException.
  2. В качестве Action отдавать обычные делегаты на свой код. Делегаты либо домен проверяют, либо вызов внутри домена идет по near jmp, а у доменов — разные селекторы кода… В общем, вызов падает. Нужно делать Expression.
  3. Даже если приводить тип не надо (отдаем List&ltCustomType&gt, Получаем list.First().DoSomething()), все равно ничего не работает. Надо работать через базовый тип

На что влияет:

  • Собственно говоря, поскольку такое должен подстроить хост, а в sandbox этого не сделать, ценность уязвимости падает
  • Не вызвать внешний код, чтобы произошло переключение в хостирующий AppDomain. Прокинутый вовнутрь делегат будет вызван под теми же правами. Хотя можно через цепочку сделать метод RunAsHost(() => ...), но об этом — позже =)
  • Можно довольно безопасно, без маршаллинга организовать быстрый вызов методов хоста из песочницы. Иногда такое бывает критичным. Например, для проброса котировок

Баг в CLR: как затащить объект в песочницу без маршаллинга и вызвать Callback - 2

Автор: sidristij

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js