Как использовать NSSecureCoding с объектами id

Я создаю связанный список и использую контейнеры для группировки объекта, следующего и предыдущего свойств. Как и в случае с коллекциями Foundation, я хотел бы реализовать NSSecureCoding. Вот декларация:

@interface ListContainer : NSObject <NSCopying, NSSecureCoding>

@property (readonly, nonatomic) id object;
@property (nonatomic) ListContainer * next;
@property (nonatomic) ListContainer * previous;

@end

При реализации метода - initWithCoder: меня осенило, что я не знаю, какой класс использовать для объекта:

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];

    if (self) {

        _object = [aDecoder decodeObjectOfClass:<#(__unsafe_unretained Class)#> forKey:@"object"];

        BOOL nextIsNil = [aDecoder decodeBoolForKey:@"nextIsNil"];

        if (!nextIsNil) {

            // Decode next
            _next = [aDecoder decodeObjectOfClass:[ListContainer class] forKey:@"next"];

            if (_next == nil) {
                return nil;
            }

            // Link the nodes manually to prevent infinite recursion
            self.next.previous = self;
        }
    }

    return self;
}

Должен ли я использовать -decodeObjectForKey: вместо этого? Это все еще безопасное кодирование?


person André Fratelli    schedule 09.07.2015    source источник
comment
Когда вы кодируете объект, вы можете иметь частную переменную, используя NSStringFromClass, используя имя класса объекта id. Когда вы декодируете его, используйте это, чтобы декодировать его. Вы даже можете закодировать его в NSDictionary с помощью пары ключ/значение, а не отдельной переменной, при декодировании просто используйте значение ключа className для создания экземпляра соответствующего класса и значение ключа значения с соответствующим значением.   -  person TheCodingArt    schedule 12.07.2015
comment
Я думал об этом. Но если я правильно понимаю, безопасное кодирование используется для предотвращения создания экземпляров объектов неизвестных типов, когда данные поступают из стороннего источника (т.е. из сети). Если я это сделаю, я по-прежнему позволяю злоумышленникам создавать практически любой объект, поскольку они могут просто закодировать любой класс, который они хотят, в этом ключе. Они называют это атакой замещения. Я хочу, чтобы мои контейнеры были защищены от этого.   -  person André Fratelli    schedule 13.07.2015
comment
NSDictionary, в котором вы будете хранить пары ключ/значение, содержащие назначенную информацию, будет храниться на диске с использованием механизма безопасного кодирования, а это означает, что знание того, что объект имеет тип NSDictionary, должно быть декодировано перед знанием ключа/значения. данные раскрываются.   -  person TheCodingArt    schedule 13.07.2015
comment
Я не могу с этим согласиться. Во-первых, потому что нет гарантии, что он останется на диске. Закодированные объекты часто используются для передачи по сети. Во-вторых, потому что информация о том, что он имеет тип NSDictionary, явно записана в кодировке. Думаю, это было бы слабым решением против атак. Сломать кодировку было бы так же просто, как самому закодировать список и проверить двоичный результат.   -  person André Fratelli    schedule 13.07.2015
comment
Игнорируя тот факт, что отправка этой информации по сети ранее не упоминалась, и тот факт, что NSDictionary на диске будет использовать безопасную кодировку, означает, что никто не сможет определить, что класс является NSDictionary из-за того, что NSDictionary кодируется через: (Во-вторых, потому что знание о том, что он имеет тип NSDictionary, явно написано в кодировке) и даже более того, это может быть замаскировано пользовательским контейнером. Есть много других способов обойти это, что я отмечу   -  person TheCodingArt    schedule 13.07.2015
comment
Вы должны немного творчески подойти к тому, что я изложил выше, скорее ожидая прямого решения вашей конкретной проблемы. Обратите внимание, что то, что я сказал, все еще может работать, даже если вы не чувствуете себя комфортно с этим, если вы вырываете это из контекста и используете его подход. Вы можете использовать пользовательские ключи через сервер для идентификации с локальными классами. Таким образом, вы не указываете тип класса, но можете сопоставить его с типом класса, используя значение ключа, например, имея ключ для type = list, subtype = user, это может сопоставляться с классом UserList.   -  person TheCodingArt    schedule 13.07.2015
comment
Просто помните, что вы должны знать класс. Единственный способ обойти это - иметь что-то, что указывает на значение, которое вы можете на 100% идентифицировать как сопоставление с другим классом. Если вы хотите все усложнить, вы можете подклассировать группу классов, которые наследуются от чего-то общего, переопределить метод decodeWithClass, а затем генерировать пользовательское исключение, если он не декодирует должным образом. Таким образом, вы можете создать собственный синтаксический анализатор, который будет перехватывать исключение и пытаться декодировать новый класс каждый раз, когда возникает исключение.   -  person TheCodingArt    schedule 13.07.2015
comment
Я не упомянул отправку по сети, потому что это одна из основных целей NSSecureCoding вместо NSCoding, я предполагал, что это будет неявно: безопасное кодирование безопасно. Я до сих пор не думаю, что эти обходные пути вообще безопасны. Обратите внимание, что классы Apple, такие как NSArray, реализуют безопасное кодирование. Они не ограничивают классы, которые могут быть закодированы известными классами, они являются общими. Разве безопасный код не перечисляет известные/ожидаемые классы? Как же тогда Apple это делает? Они не ограничивают, какие классы могут быть закодированы. Так что либо NSArray лжет, либо есть какое-то реальное решение этой проблемы.   -  person André Fratelli    schedule 14.07.2015
comment
Обратите внимание: что бы я ни делал, если я кодирую класс с помощью контейнера, он всегда будет слабым. Я не могу придумать, как получить класс из закодированных данных, и он все еще безопасен. Кроме того, я никогда не говорил, что буду использовать контейнер. На самом деле это для фреймворка, поэтому он должен поддерживать практически любой класс, который кто-то захочет туда добавить.   -  person André Fratelli    schedule 14.07.2015
comment
Как указано в приведенной ниже ссылке: При «обычном» кодировании имя созданного класса хранится в архиве, и деархиватор доверяет ему, когда он переходит к его выделению во время декодирования. Поэтому я предполагаю, что хранение класса, как вы предлагаете, на самом деле было бы избыточным. Из: prod.lists.apple.com/archives/ какао-dev/2014/Jun/msg00232.html   -  person André Fratelli    schedule 16.07.2015


Ответы (1)


В итоге я отправил тот же вопрос в список рассылки Cocoa, и произошла самая интересная дискуссия. Некоторые из основных моментов:

[...] Создайте NSArray из обычных вещей, таких как NSString, NSNumber, закодируйте его, декодируйте с помощью decodeObjectForClasses, без классов. Вы потерпите неудачу в массиве. Добавьте NSArray в список разрешенных классов и... он работает. Итак, вы думаете, что NSArray будет вслепую декодировать что угодно, так что это перестанет быть безопасным.

Добавьте в массив объект пользовательского класса, реализующего безопасное кодирование, и он снова начнет давать сбой. NSArray и другие типы коллекций допускают использование элементов известных защищенных системных типов, таких как NSString, но терпят неудачу во всем, кроме этого. [...]

На данный момент я понимаю, что NSArray ведет себя не так, как я ожидал. Безопасное кодирование больше не кажется таким безопасным:

Это кажется далеким от идеала [...] Тот факт, что он декодирует набор классов, которые, как известно, реализуют NSSecureCoding, неверен, IMO, по двум причинам [...]

1) Тот факт, что содержащийся класс реализует NSSecureCoding, не означает, что я этого ожидаю. [...]

2) Это ограничивает классы, которые могут быть сохранены. [...]

Получение класса, которого я не ожидаю, в атаке замещения особенно ужасно. Однако обещание Cocoa, по-видимому, отличается:

[...] если вы используете NSArray() или другие классы коллекций непосредственно в своем коде, вам нужно проверить, что вы получили обратно. Они «безопасно» декодируются в той мере, в какой Apple считает, что их декодирование не приведет к переполнению буфера и т. д. это все, что вы получаете по умолчанию. [...]

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

Реквизит достается Роланду Кингу за все усилия. Полную беседу можно посмотреть здесь.

person André Fratelli    schedule 16.07.2015
comment
Но это реализация Apple. Почему вы не делаете свою собственную реализацию? - person Leo Natan; 17.07.2015
comment
Конечно, это определенно вариант. Я просто предположил, что NSSecureCoding справится с такими ситуациями, после того как все это написано в документы в отношении NSCoding: This technique is potentially unsafe because by the time you can verify the class type, the object has already been constructed, and if this is part of a collection class, potentially inserted into an object graph. Вот как они вводят безопасное кодирование, поэтому я думаю, что это дает неправильное представление. - person André Fratelli; 17.07.2015
comment
Реализация Apple логична. NSArray — это универсальный контейнер. Он не может проверять типы объектов, поскольку не зависит от типа. Если вы реализуете список объектов id, вы столкнетесь с той же проблемой. Как вы проверяете типы во время декодирования, когда вы должны что-то принимать? Таким образом, вы получите что-то похожее на [aDecoder decodeObjectOfClass:[NSObject class] forKey:@"next"];, что бессмысленно для вашей цели. Более того, поскольку злоумышленник может манипулировать закодированными данными, хранение информации о классе объекта в закодированных данных также бессмысленно. - person Leo Natan; 17.07.2015
comment
Так что вы получаете от безопасного кодирования обещание, что вы получите безопасный объект массива, а не объект с плохим поведением. - person Leo Natan; 17.07.2015
comment
Да, теперь я это знаю :) и, конечно, это имеет смысл, но я все еще думаю, что документы вводят в заблуждение. Я не знал, реализовала ли Apple какое-то обходное решение для этого, поэтому я подумал, что копнуть глубже было хорошей идеей. Теперь, когда я знаю факты, я понимаю почему и почему нет - person André Fratelli; 17.07.2015