[Перевод] Курс MIT «Безопасность компьютерных систем». Лекция 21: «Отслеживание данных», часть 1

Массачусетский Технологический институт. Курс лекций #6.858. «Безопасность компьютерных систем». Николай Зельдович, Джеймс Микенс. 2014 год


Computer Systems Security — это курс о разработке и внедрении защищенных компьютерных систем. Лекции охватывают модели угроз, атаки, которые ставят под угрозу безопасность, и методы обеспечения безопасности на основе последних научных работ. Темы включают в себя безопасность операционной системы (ОС), возможности, управление потоками информации, языковую безопасность, сетевые протоколы, аппаратную защиту и безопасность в веб-приложениях.

Лекция 1: «Вступление: модели угроз» Часть 1 / Часть 2 / Часть 3
Лекция 2: «Контроль хакерских атак» Часть 1 / Часть 2 / Часть 3
Лекция 3: «Переполнение буфера: эксплойты и защита» Часть 1 / Часть 2 / Часть 3
Лекция 4: «Разделение привилегий» Часть 1 / Часть 2 / Часть 3
Лекция 5: «Откуда берутся ошибки систем безопасности» Часть 1 / Часть 2
Лекция 6: «Возможности» Часть 1 / Часть 2 / Часть 3
Лекция 7: «Песочница Native Client» Часть 1 / Часть 2 / Часть 3
Лекция 8: «Модель сетевой безопасности» Часть 1 / Часть 2 / Часть 3
Лекция 9: «Безопасность Web-приложений» Часть 1 / Часть 2 / Часть 3
Лекция 10: «Символьное выполнение» Часть 1 / Часть 2 / Часть 3
Лекция 11: «Язык программирования Ur/Web» Часть 1 / Часть 2 / Часть 3
Лекция 12: «Сетевая безопасность» Часть 1 / Часть 2 / Часть 3
Лекция 13: «Сетевые протоколы» Часть 1 / Часть 2 / Часть 3
Лекция 14: «SSL и HTTPS» Часть 1 / Часть 2 / Часть 3
Лекция 15: «Медицинское программное обеспечение» Часть 1 / Часть 2 / Часть 3
Лекция 16: «Атаки через побочный канал» Часть 1 / Часть 2 / Часть 3
Лекция 17: «Аутентификация пользователя» Часть 1 / Часть 2 / Часть 3
Лекция 18: «Частный просмотр интернета» Часть 1 / Часть 2 / Часть 3
Лекция 19: «Анонимные сети» Часть 1 / Часть 2 / Часть 3
Лекция 20: «Безопасность мобильных телефонов» Часть 1 / Часть 2 / Часть 3
Лекция 21: «Отслеживание данных» Часть 1 / Часть 2 / Часть 3

Джеймс Микенс: отлично, давайте начнем. Спасибо, что пришли на лекцию в этот особенный день перед Днем Благодарения. Я рад, ребята, что вы так преданы компьютерной безопасности и уверен, что вы будете востребованы на рынке труда. Не стесняйтесь ссылаться на меня как на источник рекомендаций. Сегодня мы поговорим об отслеживании заражения Taint-tracking, в частности, о системе под названием TaintDroid, которая обеспечивает выполнение этого типа анализа информационных потоков в контексте смартфонов под управлением Android.

bydrvpy2o7ec-sf2gw8utcumsdq.jpeg

Основная проблема, затронутая в лекционной статье, рассматривает тот факт, что приложения могут извлекать данные. Идея заключается в том, что ваш телефон содержит много конфиденциальной информации — список контактов, ваш номер телефона, адрес электронной почты и все такое. Если операционная система и сам телефон не будут осторожны, то вредоносное приложение может быть в состоянии извлечь часть этой информации и отправить её обратно на свой домашний сервер, и сервер сможет использовать эту информацию для всех типов злополучных вещей, о которых мы поговорим позже.

В глобальном смысле статья о TaintDroid предлагает такое решение: отслеживать конфиденциальные данные по мере того, как они проходят через систему, и, по сути, останавливать их перед тем, как они будут переданы по сети. Другими словами, мы должны предотвратить возможность передачи данных в качестве аргумента сетевых системных вызовов. По-видимому, если мы сможем это сделать, то мы по существу можем остановить утечку прямо в тот момент, когда она начнёт происходить.

Вы можете подумать, почему же традиционных разрешений Android недостаточно, чтобы предотвратить извлечение данных этого типа? Причина в том, что эти разрешения не располагают правильной грамматикой, чтобы описать тот тип атаки, которую мы пытаемся предотвратить. Разрешения для Android обычно имеют дело с правами приложений записывать или считывать что-либо с конкретного устройства. Но сейчас мы говорим о том, что находится на другом семантическом уровне. Даже если приложению было предоставлено право считывать информацию или записывать данные в такое устройство, как сеть, может быть опасно позволять приложению читать или записывать определенные конфиденциальные данные на устройство, на взаимодействие с которым у него есть разрешение.

Другими словами, используя традиционные политики безопасности Android, трудно говорить о конкретных типах данных. Гораздо проще говорить о том, получает ли приложение доступ к устройству или нет. Возможно, мы можем решить эту проблему, используя альтернативное решение, я обозначу его звёздочкой.

drdjjzmjzpbkozco9vkzdwwjpd0.jpeg

Это альтернативное решение состоит в том, чтобы вообще никогда не устанавливать приложения, которые могут считывать конфиденциальные данные и/или иметь доступ к сети. На первый взгляд, кажется, что проблема устранена. Потому что если приложение не может делать одновременно обе эти вещи, оно либо не сможет добраться до конфиденциальных данных, либо может их прочитать, но не сможет отправить по сети. Как вы думаете, в чём здесь подвох?

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

Если мы просто скажем, что собираемся предотвратить деятельность такого рода, то фактически запретим работу множества приложений в телефоне, что наверняка не понравится пользователям.
Здесь есть ещё одна проблема — даже если бы мы реализовали это решение, оно не предотвратит утечку данных через кучу различных механизмов сторонних каналов. Например, на прошлых лекциях мы рассмотрели, что кеш браузера, например, может способствовать утечке информации о посещении пользователем конкретного сайта. Поэтому даже при осуществлении такой политики безопасности мы не сможем проконтролировать все сторонние каналы. Чуть позже мы ещё поговорим о сторонних каналах.

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

i9_xwlfibtwlxl5rbqiclpvdity.jpeg

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

Итак, мы очень обеспокоены тем, что конфиденциальные данные покидают телефон. Рассмотрим, что же на практике делают вредоносные приложения для «Андроид». Существуют ли в реальном мире какие-то атаки, которые можно предотвратить отслеживаем заражения Taint-tracking? Ответ — «да». Вредоносные программы становится все большей проблемой для мобильных телефонов. Первое, что могут сделать вредоносные приложения — это использовать ваше местоположение или IMEI для рекламы или навязывания услуг.

Вредоносное ПО может определить ваше физическое местоположение. Например, вы находитесь недалеко от кампуса MIT, значит, вы голодный студент, так почему бы вам не навестить мою закусочную на колёсах, которая расположена совсем рядом?

IMEI — это целое число, представляющее собой уникальный идентификатор вашего телефона. Его можно использовать для вашего отслеживания в разных местах, особенно в тех, где вы бы не хотели «засветиться». Таким образом, в природе существуют вредоносные программы, способные проделывать такие вещи.

Второе, что делают вредоносные программы — это крадут ваши личные данные. Они могут попробовать украсть ваш телефонный номер или список контактов и попытаться загрузить эти вещи на удаленный сервер. Это может быть нужно, чтобы выдать себя за вас, например, в сообщении, которое позже будет использоваться для рассылки спама.

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

5uvn_4mbhbdv0ijcoflyh-ghcwc.jpeg

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

Студент: это вредоносное ПО, которое нацелено на взлом именно ОС Android, или это просто типичное приложение? Если это типичное приложение, то, наверное, мы бы смогли обезопасить его при помощи разрешений?

Профессор: это очень хороший вопрос. Существует оба типа вредоносных программ. Как оказалось, довольно легко заставить пользователей нажимать на разные кнопки. Я приведу вам пример, который касается не столько вредоносного ПО, сколько неосторожного поведения людей.
Есть популярная игра Angry Birds, вы заходите в App Store и ищите её в строке поиска приложений. Первой в результатах поиска вам будет выдана оригинальная игра Angry Birds, а во второй строке может находиться приложение Angry Birdss, с двумя s в конце. И многие люди предпочтут скачать это второе приложение, потому что оно может стоить дешевле оригинальной версии. Далее при установке это приложение напишет, что после установки вы позволите ему делать то-то и то-то, и вы скажете: «конечно, без проблем!», потому что получили желанных Angry Birds за сущие копейки. После этого «бум» — и вы на крючке у хакера!

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

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

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

1cngw_13gmjmwqdhntc0wv07dwg.jpeg

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

Таким образом, Taint-tracking сам по себе не является достаточным решением для предотвращения ситуации, угрожающей захватом вашего телефона.

Давайте рассмотрим, как работает TaintDroid. Как я уже упоминал, TaintDroid будет отслеживать всю вашу конфиденциальную информацию по мере ее распространения в системе. Так, TaintDroid различает то, что называется «источники информации» Information sources и «поглотители информации» Information sinks. Источники информации генерируют конфиденциальные данные. Обычно это датчики — GPS, акселерометр и тому подобное. Это может быть ваш список контактов, IMEI, все то, что может связать вас, конкретного пользователя, с вашим настоящим телефоном. Это устройства, которые генерируют заражаемую информацию, называют источниками зараженных данных — Taint source.

В таком случае поглотители информации — это места, в которые не должны утекать зараженные данные. В случае с TaintDroid главным поглотителем является сеть. Позже мы поговорим о том, что можно представить больше мест, куда утекает информация, но сеть занимает в TaintDroid особое место. В системе более общего назначения, чем телефон, могут быть другие Information sinks, но TaintDroid предназначен для предотвращения утечек именно в сеть.

В TaintDroid для представления заражения Taint используется 32-битный битвектор. Это означает, что вы можете иметь не более 32 отдельных источников заражения.

kjkk6mutrwuy9_fgtmdqt0rxlwu.jpeg

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

Интересно, что 32 источника заражения на самом деле не так уж много. Вопрос заключается в том, является ли это число достаточно большим для этой конкретной системы и достаточно ли оно велико для общих систем, страдающих утечками информации. В частном случае TaintDroid, 32 источника заражения представляют собой разумную величину, потому что это проблема касается ограниченного потока информации.

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

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

Грубо говоря, когда вы смотрите на то, как заражение протекает через систему, в общем смысле оно происходит справа налево. Приведу простой пример. Если у вас есть какой-то оператор, например, вы объявляете целочисленную переменную, которая равна значению широты вашего местоположения: Int lat = gps.getLat (), то по существу вещь, расположенная справа от знака равенства, генерирует значение, которое имеет некую связанную с ней зараженность.

b26vaukq8bjvwn_kq9l9ofklbcq.jpeg

Так что будет установлен какой-то конкретный флаг, который говорит: «эй, это значение, которое я возвращаю, исходит из конфиденциального источника»! Так что зараза придет отсюда, с правой стороны, и пойдет сюда, влево, чтобы заразить эту часть lat. Вот как это выглядит на взгляд человека-разработчика, который пишет исходный код. Однако виртуальная машина Dalvik использует этот регистровый формат на более низком уровне для создания программ, и это на самом деле то, как семантики taint реализованы в реальности.

В таблице к одной из лекционных статей имеется большой список команд с описанием, как заражение влияет на эти типы команд. Например, вы можете представить, что у вас есть операция move-op, которая указывает на пункт назначения dst и источник srs. В виртуальной машине Dalvik, на абстрактном вычислительном движке, это можно считать регистрами. Как я уже сказал, заражение идет с правой стороны в левую сторону, так что в данном случае, когда интерпретатор Dalvik выполняет инструкции с правой стороны, он рассматривает метку taint параметра sourse и присваивает её параметру dst.

Предположим, что у нас имеется ещё одна инструкция в виде бинарной операции binary-op, которая выполняет что-то вроде сложения. У нас имеется один пункт назначения dst и два источника: srs0 и srs1. В этом случае, когда интерпретатор Dalvik обрабатывает эту инструкцию, он берёт taint из обоих источников, объединяет их и затем присваивает это объединение точке назначения dst.

2dcveslgccj4ironmbfjsh5sne8.jpeg

Это довольно просто. В таблице приведены различные типы инструкций, которые вы увидите, но в первом приближении это наиболее распространенные способы распространения заражения в системе. Давайте рассмотрим особо интересные случаи, которые упомянуты в статье. Один из таких особых случаев связан с массивами.

Допустим, у вас есть команда char c, которая присваивает некое значение С. При этом программа объявляет некоторый массив char upper [ ] который будет содержать заглавные буквы «А», «В», «С»: char upper [ ] = [«А», «В», «С»]

Весьма распространенная вещь в коде — это индексирование в массив, подобный этому, используя C напрямую, потому что, как мы все знаем, Керниган и Ричи учат, что в основном символы — это целые числа. Так что вы можете представить, что здесь имеется некий код char upperC, который говорит, что заглавные версии этих символов «А», «В», «С» соответствуют конкретным индексам в этой таблице: char upperC = upper [C]

zoux-ksxga8odvimxb9lcdkncgc.jpeg

Здесь возникает вопрос, какое заражение должен получить upperC в данном случае. Кажется, что в предыдущих случаях у нас было всё просто, но в данном случае у нас много чего происходит. У нас есть массив [«А», «В», «С»], который может иметь тип заражения и у нас есть этот символ С, который тоже может иметь свой тип заражения. Здесь виртуальная машина Dalvik поступает похоже на то, как она поступала в случае binary-op. Она присваивает символу upperC сочетание заражений [C] и массива.

Интуиция подсказывает, что для создания upperC нам нужно знать кое-то о массиве upper [ ]. Мы должны что-то знать об этом индексе [C]. Поэтому я думаю, что эта вещь, upperC должна быть такой же конфиденциальной, как эти обе скомбинированные вещи.

Студент: можете ли вы еще раз объяснить, что именно означает объединение taint для операций move op и binary op?

Профессор: давайте посмотрим на операцию перемещения move op. Представим, что эта операция srs… вообще-то, позвольте мне уточнить. Каждая переменная, я сейчас расскажу, что такое эта переменная, представляет собой целое число, которое имеет кучу битов, установленных в соответствии с имеющимися taint. Представьте, что каждое из этих летающих вокруг значений имеет летающее вокруг него связанное целое число, в котором установлено несколько битов.
Предположим, что этот источник srs имел два набора битов, связанных с тем, что он был заражен двумя вещами. Интерпретатор посмотрит на этот источник srs как на связанное целое число и скажет: «я должен взять это целое число, в котором установлены 2 набора битов, и сделать это число зараженным тегом для srs». Так что это простой случай.

Более сложный случай — это понять, как же выглядит это объединение taint. Представьте, что у нас есть два источника srs0 и srs1, и в каждом содержатся биты заражения taint, которые выглядят таким образом:

prpqgjssatoxmlfbxhmhhj-_j1w.jpeg\

Тогда пункт назначения dst будет содержать в себе все эти биты:

oemfyvxwlundqpjqswv7zf3amds.jpeg\

Как я уже сказал, одна из причин того, почему мы хотим представить все источники заражения в 32-х битах, заключается в том, что в данном случае мы просто совершаем побитовые операции. Таким образом, это действительно сокращает накладные расходы на реализацию этих зараженных битов. Если вам нужно отобразить более обширную вселенную taints, вы можете испытать большие затруднения, потому что уже не сможете использовать эти эффективные побитовые операции.
Итак, то, как работают массивы, немного похоже на обработку операции в случае binary-op. При этом upperC получит объединение битов из [C] и [«А», «В», «С»]. Дизайнерское решение TaintDroid заключается в том, что разработчики связывают отдельную таблицу заражений taint с каждым массивом. Другими словами, они пытаются не заразить отдельные элементы. Это позволяет им сэкономить место для хранения, потому что для каждого массива они объявляют только одну 32-х битную сущность, которая «плавает» вокруг этого массива и представляет все заражения, принадлежащие этому массиву.

Один из вопросов заключается в том, почему безопасно не иметь более точной системы для работы с taint. Ведь массив — это набор данных, так почему бы нам не иметь кучу лейблов для каждой вещи, которая находится в этом массиве? Ответ заключается в том, что связывая только один тег taint с массивом и делая его объединением всех вещей, содержащихся внутри, мы получим переоценку заражения. Другими словами, если у вас есть массив, содержащий внутри себя две вещи, и этот массив заражен объединением заражений этих двух вещей, то такой подход к проблеме является избыточным. Потому что если что-то получает доступ к одной из этих вещей, его не волнует заражение второй вещи.

Однако избыточный подход предпочтительней недостаточного подхода, потому что в данном случае мы будем всегда правы. Другими словами, если мы недооценим количество заражения, которое имеется в каком-то элементе, то случайно можем раскрыть то, что не хотели раскрывать. При переоценке заражения, худшее, что может случиться — это то, что мы предотвратим утечку за пределы телефона того, что можно было спокойно передавать по сети. Так что в данном случае мы совершим ошибку в пользу безопасности.

Еще один пример, о котором упоминается в статье — это своего рода частный случай распространения заражения, при котором упоминаются такие вещи, как Native methods, или машинно-зависимые методы. Native-методы могут существовать внутри самой виртуальной машины. Например, виртуальная машина Dalvik предоставляет такие функции, как system.arraycopy (), так что мы можем что-либо передать через них, и внутри виртуальной машины по соображениям скорости это реализуется в виде кода C или C++. Это один из примеров Native method, которым вы можете располагать.

Ещё вы можете располагать машинно-зависимым методом под названием JNI. JNI, или Java Native Interface — механизм для запуска кода на языке C или C++ на виртуальной машине Java. По существу, он позволяет коду Java вызывать код, который не является Java. Данный интерфейс реализован с помощью x86, ARM или подобных вещей и имеет целую кучу вызовов, позволяющих взаимодействовать двум типам стеков.

Проблема с этими машинно-зависимыми методами с точки зрения отслеживания taint заключается в том, что этот машинный код не может быть выполнен непосредственно интерпретатором Dalvik. Часто это даже не Java-код, а C или C++ код. Это означает, что как только исполняемый поток попадает в один из этих Native-методов, TaintDroid не может распространить заражения так, как он делает это в случае с кодом Java.

wfhzrhl1hwt3sdsyoy0jdkwwq4k.jpeg\

Это кажется немного проблематичным, потому что эти вещи похожи на «черные ящики», и вы хотите убедиться, что в результате применения этих методов всё равно получатся новые taint. Способ, которым авторы решают эту проблему — это ручной анализ. По-существу они говорят, что здесь не так уж много этих методов. Например, виртуальная машина Dalvik предоставляет только определенное количество функций, подобных system.arraycopy (), так что разработчик может просмотреть на это относительно небольшое количество вызовов и выяснить, где должны быть отношения taint. То есть разработчик может посмотреть на arraycopy () и сказать: «так как я знаю семантику этой операции, то я знаю, что мы должны заразить значения, возвращаемые этой функцией, задавая этой функции определенным образом входные значения».

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

Применение машинно-зависимого метода JNI может быть, а может и не быть более хлопотным делом. Авторы статьи приводят некоторые эмпирические данные, которые свидетельствуют о том, что многие приложения не содержат кодов, написанных на C или C++, поэтому это не будет большой проблемой.

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

26:25 мин

Курс MIT «Безопасность компьютерных систем». Лекция 21: «Отслеживание данных», часть 2


Полная версия курса доступна здесь.

Спасибо, что остаётесь с нами. Вам нравятся наши статьи? Хотите видеть больше интересных материалов? Поддержите нас оформив заказ или порекомендовав знакомым, 30% скидка для пользователей Хабра на уникальный аналог entry-level серверов, который был придуман нами для Вас: Вся правда о VPS (KVM) E5–2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps от $20 или как правильно делить сервер? (доступны варианты с RAID1 и RAID10, до 24 ядер и до 40GB DDR4).

VPS (KVM) E5–2650 v4 (6 Cores) 10GB DDR4 240GB SSD 1Gbps до января бесплатно при оплате на срок от полугода, заказать можно тут.

Dell R730xd в 2 раза дешевле? Только у нас 2 х Intel Dodeca-Core Xeon E5–2650v4 128GB DDR4 6×480GB SSD 1Gbps 100 ТВ от $249 в Нидерландах и США! Читайте о том Как построить инфраструктуру корп. класса c применением серверов Dell R730xd Е5–2650 v4 стоимостью 9000 евро за копейки?

© Habrahabr.ru