Когда-то давно я думал, что было бы неплохо научиться играть в гольф. Перед тем как записаться на соответствующие занятия в своих краях, я никогда не брал в руки клюшку для гольфа. На первом занятии инструктор спросил меня, брал ли я когда-нибудь уроки по игре в гольф и вообще пробовал ли я играть в гольф. Когда я ответил ему отрицательно, он сказал: «Хорошо! Нам не придется сначала избавляться от старых привычек при взмахе».
Веб-разработчики, переходящие с браузера на приложения Windows Store, несут с собой определенные привычки. Хотя они могут гордиться своими познаниями в JavaScript, некоторые возможности совершенно новые и требуют перемен в образе мышления. Безопасность — одно из таких фундаментальных различий. У многих веб-разработчиков сложилась привычка перекладывать защиту приложений на сервер из-за таких причин, как «Зачем этим заморачиваться? JavaScript можно легко обойти». На стороне веб-клиента средства защиты рассматриваются как функционал для большего удобства в использовании без улучшения общей безопасности веб-приложения.
JavaScript в случае Windows 8 играет важную роль в общей безопасности вашего приложения, предоставляя необходимые средства для защиты данных, проверки ввода и отделения потенциально зловредного контента. В этой статье я покажу, как переломить некоторые привычки, приносимые из веб-разработки, и тем самым создавать более безопасные приложения Windows Store с использованием HTML5, JavaScript и средств защиты Windows Runtime.
Проверка допустимости ввода
Веб-разработчик JavaScript-проверка предназначена для удобства в использовании и ничего не дает для защиты самого приложения.
Разработчик для Windows 8 Проверка допустимости ввода средствами HTML5 и JavaScript — первая линия обороны от зловредного контента, попадающего в ваше приложение.
В традиционных веб-приложениях JavaScript зачастую является не более чем воротами, открывающими путь к серверу. Все важные операции с данными вроде проверки ввода и хранилищ выполняются на сервере. Атакующие могут отключить JavaScript в своих браузерах или напрямую передавать написанные вручную HTTP-запросы для обхода любой защиты на клиентской стороне. В приложении Windows Store разработчик не может полагаться на сервер в очистке пользовательского ввода до операций над данными, потому что нет никакого сервера. Когда дело доходит до проверки допустимости ввода, JavaScript и HTML5 предоставляются сами себе.
В защите программного обеспечения проверка ввода является критически важным для поддержания целостности данных элементом. Без нее атакующие могут использовать любое поле ввода как вектор возможной атаки на приложение Windows Store. Во втором издании книги «Writing Secure Code» (Microsoft Press, 2003) авторы Майкл Говард (Michael Howard) и Стив Липнер (Steve Lipner) написали фразу, которая стала одной из мантр в управлении вводом: «Весь ввод является злонамеренным, пока не доказано обратное».
Вы не должны доверять данным, пока не доказано, что они являются «известными хорошими». При создании приложения разработчик знает, какими должны быть данные в конкретном поле (т. е. список разрешенных элементов) или, по крайней мере, какими они не могут быть (т. е. список отклоняемых элементов). При проверке ввода всегда по возможности используйте список разрешенных элементов для ограничения ввода до известных хороших данных. Разрешая ввод только таких данных, вы уменьшаете вероятность нахождения злоумышленниками нового или пока неизвестного способа представления вредоносных данных.
Ограничивай, отклоняй и дезинфицируй
Как разработчики уменьшают риск для своих пользователей, ограничивая ввод известными хорошими данными? Они используют три этапа проверки ввода, показанные на рис. 1, чтобы свести к минимуму риск проникновения вредоносного контента в приложение.
Рис. 1. Проверка ввода (схема основана на рис. 4.4 из главы 4 «Design Guidelines for Secure Web Applications» документа «Improving Web Application Security: Threats and Countermeasures», доступного по ссылке bit.ly/emYI5A)
Constrain | Ограничение |
Allow known good data | Разрешение известных хороших данных |
Reject | Отклонение |
Reject known bad data | Отклонение известных вредных данных |
Make potentially malicious data safe | Потенциально вредные данные превращаются в безопасные |
Проверка данных начинается с ограничения данных теми, которые считаются «известными хорошими». Веб-разработчики, знакомые с HTML5, могут использовать существующие знания новых типов и атрибутов input для ограничения данных, поступающих в их приложения Windows Store. Ключевое различие между Web и Windows 8 в том, что «за кулисами» приложения Windows Store нет сервера, который проверял бы ввод. Ограничение данных должно происходить в HTML5 или JavaScript.
В случае HTML5 каждое поле можно легко ограничить известными хорошими данными. Для примеров в этой статье я воспользуюсь вымышленным приложением Contoso Health, которое хранит медицинскую информацию о пациентах. На странице Profile этого приложения вводятся имя пациента (пользователя), адрес электронной почты, вес и рост и имеется поле для заметок, куда заносится информация общего характера. Как разработчик я знаю (в целом), как выглядят хорошие данные для каждого из этих полей.
- Name Только буквы с несколькими специальными символами; общая длина не может превышать 45 знаков. Данный критерий основан на том, что целевым рынком этого приложения будет США.
- E-mail Address Ввод должен соответствовать допустимому формату адреса электронной почты.
- Weight и Height Числа с сопоставленными метками для отображения данных в футах и дюймах для роста и фунтах для веса.
- Notes HTML-контент, создаваемый с помощью стандартного HTML-редактора Contoso.
Для input-элемента Name мне нужно ограничить, какие символы допустимы в этом поле, а также какой длины может быть значение. Это можно сделать с помощью двух новых атрибутов тега input: pattern и title.
Pattern — это регулярное выражение, которому должны удовлетворять вводимые данные. MSHTML (механизм рендеринга, применяемый для приложений HTML5 в Windows 8) проверяет, что данные, вводимые в поле, соответствуют регулярному выражению. Если пользователь вводит данные, не отвечающие регулярному выражению pattern, передача формы закончится неудачей, и пользователю будет указано исправить недопустимые данные в поле. Например, поле Name может состоять из букв и пробелов и должно быть длиной от трех до 45 символов. Для этого используется следующее значение pattern:
<input type="text" id="txtName" name="txtName"
pattern="^[A-Za-z ]{3,45}$" title="" />
С помощью title пользователь информируется о том, что ожидает система. В данном случае нечто вроде «Имя должно быть длиной от трех до 45 символов и содержать только буквы и пробелы» исчерпывающе поясняет ожидаемый шаблон. Ничто так не сбивает пользователей с толку, как неправильный ввод, без знания правильного шаблона ввода. Будьте внимательны к своим пользователям и подсказывайте им, что именно допускается. Атрибут title именно это и делает; это сообщение, поясняющее, что ожидается в данном поле.
Шаблоны для полей данных, включающие допустимые символы и длину, могут быть трудны в определении. Образцы регулярных выражений можно найти во многих онлайновых ресурсах, но всегда консультируйтесь с группой безопасности в своей организации, чтобы выяснить, имеется ли стандарт, которого вы должны придерживаться. Если у вас нет группы безопасности или если у этой группы нет стандартов, такие ресурсы, как RegExLib.com, дадут вам великолепную библиотеку регулярных выражений, которые вы сможете задействовать при проверках данных.
Некоторые поля имеют специфические типы данных, например числа, даты и адреса электронной почты. HTML5 помогает и здесь, предоставляя массу новых input-типов, такие как email, phone, date, number и многие другие. Используя эти input-типы данных, MSHTML проверяет, допустимы ли введенные пользователем данные; при этом не нужны никакие регулярные выражения или JavaScript-код. Новые типы данных обрабатывает атрибут type элемента input. (Подробнее о новых типах и их применении см. по ссылке bit.ly/OH1xFf.) Скажем, чтобы получить адрес электронной почты для страницы Profile, я указал бы атрибут type равным email, как в следующем примере:
<input type="email" id="txtEmail" name="txtEmail" />
Это поле принимает значение, только если оно удовлетворяет формату адреса электронной почты. Если MSHTML не распознает ввод как допустимый адрес электронной почты, в поле выводится ошибка проверки, когда пользователь пытается отправить форму. Применение новых input-типов HTML5 ограничивает данные ожидаемым подмножеством безо всякой возни со сложными проверками на основе JavaScript.
Некоторые из новых input-типов также поддерживают ограничения диапазонов с помощью новых атрибутов min и max. В качестве примера у людей рост не может выходить за пределы от трех до восьми футов. Тогда в поле height могут быть использованы следующие ограничения диапазона:
<input type="number" id="txtHeight" name="txtHeight" min="3" max="8" />
В предложенных примерах используются четыре способа ограничения данных с помощью HTML5-тега input. Проверяя длину (pattern), формат (и вновь pattern), тип данных (новые input-типы) и диапазон (атрибуты min/max), вы можете ограничивать ввод до известных хороших данных. Не все атрибуты и типы предлагают внести коррективы перед отправкой. Обязательно проверяйте содержимое формы методом checkValidity (bit.ly/SgNgnA) — по аналогии с Page.IsValid в ASP.NET. Вероятно, вас интересует, можно ли похожим образом ограничивать данные, используя только JavaScript. Да, можно, но использование HTML5-атрибутов сокращает общий объем кода, который разработчику приходится управлять самостоятельно, и позволяет передавать всю черновую работу механизму MSHTML.
Reject отклоняет известный вредный ввод (т. е. это список отклоняемых элементов). Хороший пример reject — создание списка отклоняемых IP-адресов, с которых нельзя подключаться к вашему вею-приложению. Списки отклоняемых элементов полезны, когда у вас есть некие фиксированные области, определенные для тех элементов, которые вы хотите блокировать. В качестве примера возьмем список рассылки сообщений электронной почты какой-либо группе (скажем, вашей группе разработки), из которого вы намеренно исключаете определенных лиц. В этом примере вы знаете, какие почтовые адреса следует отклонять из списка адресов группы разработки. В защищенном программном обеспечении предпочтение следует отдавать ограничению (списку разрешенных элементов), а не отклонению (списку отклоняемых элементов). Всегда помните, что известные вредные данные постоянно меняются, так как взломщики изобретают все более изощренные способы обхода средств защиты. В предыдущем примере вообразите, что к группе присоединяются новые разработчики и они должны пройти некий испытательный период перед тем, как вы включите их в список почтовой рассылки. В долгосрочном плане управлять ограничениями гораздо легче, а список разрешенных элементов проще поддерживать в противоположность списку запрета с тысячами элементов.
Не забывайте о рекомендациях, относящихся к конфиденциальным данным, например о доступе к таким данным только при необходимости и об их хранении вне кеша.
Иногда ввод содержит известные хорошие и вредные данные. Примером тому является HTML-контент. Некоторые теги разрешается отображать, тогда как другие — нет. Процесс фильтрации или отключения известных вредных данных и пропуск одобренных данных известен как дезинфекция ввода (sanitizing the input). Поле заметок в приложении Contoso Health как раз является отличным примером этому. Пользователи могут вводить HTML-теги через HTML-редактор, но, когда ввод отображается приложением, визуализируются лишь определенные HTML-теги. Процесс дезинфекции данных принимает данные, которые могут быть вредоносными и делает их безопасными, удаляя небезопасный контент. В приложения Windows Store можно задать значение HTML-элемента, используя innerText (вместо innerHTML), что приводит к рендерингу HTML-контента как текста, а не HTML-кода. (Заметьте: если в приложении innerText тега script задать JavaScript, вы получите исполняемый скрипт.) JavaScript также предоставляет еще одно полезное средство дезинфекции: toStaticHTML.
Вот пример кода из обработчика btnSave_Click страницы Profile:
function btnSave_Click(args) {
var taintedNotes = document.getElementById("txtNotes").value;
var sanitizedNotes = window.toStaticHTML(taintedNotes);
document.getElementById("output").innerHTML = sanitizedNotes;
}
Если пользователь вводит в txtNotes строку:
<strong>testing!</strong><script>alert("123! ");</script>
метод window.toStaticHTML удаляет тег script и оставляет только одобренный тег strong. Вызов toStaticHTML удаляет любой тег, который отсутствует в списке одобренных безопасных тегов (еще один пример использования списка разрешенных элементов), а также любой неизвестный атрибут. В выводе метода toStaticHTML остаются только известные хорошие данные. Полный список одобренных тегов, атрибутов, CSS-правил и свойств см. по ссылке bit.ly/KNnjpF.
Проверка ввода уменьшает риск попадания в систему вредоносных данных. С помощью HTML5 и toStaticHTML приложение может ограничивать ввод известными хорошими данными и удалять или отключать потенциально вредоносный контент без участия сервера.
Теперь, когда приложение Contoso Health получает допустимые данные, обсудим, как обращаться с конфиденциальными данными, к которым относится медицинская или финансовая информация.
Хранилище конфиденциальных данных
Веб-разработчик Никогда не храните конфиденциальные данные на клиенте, так как у него нет защищенного хранилища.
Разработчик для Windows 8 Конфиденциальные данные можно шифровать и безопасно хранить средствами Windows Runtime.
В предыдущем разделе приложение Contoso Health получало общую профильную информацию. По мере разработки заказчиком была затребована форма медицинской карты (medical history form). В эту форму вводятся все значимые с медицинской точки зрения события, происходящие на протяжении жизни пациента, например самый последний визит к врачу. Согласно старым правилам веб-разработки, хранение конфиденциальной информации, такой как медицинская карта пациента, на клиенте — плохая идея из-за потенциальной утечки этих данных. В приложении Windows Store конфиденциальные данные можно хранить локально, используя средства защиты в Windows Runtime.
Для защиты медицинской карты пациента Contoso Health использует WinRT Data Protection API. Шифрование должно быть единственным элементом вашей стратегии защиты данных (продумывайте эшелонированную оборону, а не что-то одно вроде шифрования). Не забывайте о рекомендациях, относящихся к конфиденциальным данным, например о доступе к таким данным только при необходимости и об их хранении вне кеша. Отличный ресурс, в котором вы найдете много соображений по хранению конфиденциальных данных, — документ «Improving Web Application Security: Threats and Countermeasures» в MSDN Library (bit.ly/NuUe6w). Хотя в этом документе основное внимание уделяется рекомендациям в области веб-разработки, он дает много превосходных фундаментальных знаний, применимых к любому типу разработки.
На странице Medical History в приложении Contoso Health есть кнопка btnAddItem. Когда пользователь щелкает ее, приложение шифрует данные, введенные в форму Medical History. Чтобы зашифровать информацию Medical History, приложение использует встроенный WinRT Data Protection API. Это простая система шифрования, позволяющая разработчикам быстро шифровать данные без издержек, связанных с управлением ключами. Начните с пустого обработчика событий click для btnAddItem. Приложение Contoso Health собирает информацию с формы и сохраняет ее в JSON-объекте. В обработчик событий я добавляю код для быстрого формирования этого JSON-объекта:
var healthItem = {
"prop1": window.toStaticHTML(document.getElementById("txt1").value),
"prop2": window.toStaticHTML(document.getElementById("txt2").value)
};
Объект healthItem представляет запись Medical History, введенную пользователем в форму. Шифрование healthItem начинается с создания экземпляра DataProtectionProvider:
var dataProtectionProvider =
Windows.Security.Cryptography.DataProtection.DataProtectionProvider("
LOCAL=user");
Конструктор DataProtectionProvider (для шифрования) принимает строковый аргумент, который определяет, с чем сопоставляется Data Protection. В данном случае я шифрую контент для локального пользователя. Вместо локального пользователя можно было бы выбрать машину, набор веб-удостоверений, участника подсистемы защиты Active Directory или некоторые другие варианты. Список вариантов вы найдете в разделе «Protection Descriptors» в Dev Center (bit.ly/QONGdG). Какой дескриптор защиты (protection descriptor) вы будете использовать, зависит от требований приложения. На данный момент Data Protection Provider готов к шифрованию данных, но данные нужно немного изменить. Алгоритмы шифрования работают с буферами, а не с JSON-объектами, поэтому на следующем этапе healthItem преобразуется в буфер:
var buffer =
Windows.Security.Cryptography.CryptographicBuffer.convertStringToBinary(
JSON.stringify(healthItem),
Windows.Security.Cryptography.BinaryStringEncoding.utf8);
В CryptographicBuffer имеется много объектов и методов, которые работают с буферами, используемыми в шифровании и дешифровании. Первый из этих методов — convertStringToBinary, который принимает строку (в данном случае строковую версию JSON-объекта) и преобразует ее в зашифрованный буфер. Используемая кодировка задается с помощью объекта Windows.Security.Cryptography.BinaryStringEncoding. В этом примере я использую UTF8 в качестве системы кодировки своих данных. Метод convertStringToBinary возвращает буфер, основанный на строковых данных и указанной кодировке. Как только буфер готов к шифрованию и создан экземпляр Data Protection Provider, я могу вызвать метод protectAsync для шифрования этого буфера:
dataProtectionProvider.protectAsync(buffer).then(
function (encryptedBuffer) {
SaveBufferToFile(encryptedBuffer);
});
Аргумент encryptedBuffer является выводом метода protectAsync и содержит зашифрованную версию буфера. Иначе говоря, это зашифрованные данные, готовые к сохранению. После этого encryptedBuffer передается в метод SaveBufferToFile, который записывает шифрованные данные в файл в локальную папку приложения.
Шифрование для healthItem сводится к трем строкам кода.
- Создание экземпляра Data Protection Provider.
- Преобразование данных в буфер.
- Вызов protectAsync для шифрования данных.
Дешифрование данных осуществляется так же легко. Отличия заключаются лишь в том, что вы используете пустой конструктор для DataProtectionProvider и вызываете метод unprotectAsync вместо protectAsync. Метод GetBufferFromFile загружает переменную encryptedBuffer из файла, созданного методом SaveBufferToFile:
function btnLoadItem_Click(args) {
var dataProtectionProvider =
Windows.Security.Cryptography.DataProtection.DataProtectionProvider();
var encryptedBuffer = GetBufferFromFile();
dataProtectionProvider.unprotectAsync(encryptedBuffer).then(
function (decryptedBuffer) {
// TODO: работа с расшифрованными данными
});
}
Можно ли использовать шифрование в случаях с JavaScript, отличных от WinRT? Да! Так же это легко? Нет! На пути шифрования в браузере возникает многочисленные трудности, в частности хранение секретного ключа, а также управление размером файла алгоритмов, необходимых для качественного шифрования. WinRT Data Protection API, равно как и другие средства шифрования в пространстве имен Windows.Security.Cryptography, упрощают защиту ваших данных. Применяя средства защиты Windows Runtime, разработчики могут безопасно хранить конфиденциальные данные в приложениях Windows Store, в то же время легко управляя своими шифровальными ключами.
Локальные контексты в сравнении с веб-контекстами
Веб-разработчик Веб-приложения выполняют ссылки на внешние скрипты в том же источнике, откуда вызываются эти скрипты.
Разработчик для Windows 8 Приложения Windows Store отделяют пакет локального приложения от ссылок на внешние скрипты.
Web 2.0 научила разработчиков тому, что контент может поступать с их сайтов, с других сайтов или через взаимодействие с пользователями. В Web контент практически доступен для всех, при этом разработчики используют ссылки на скрипты и API-данные от третьих сторон. Сети доставки контента (content delivery networks, CDN) и онлайновые сервисы вроде Bing Maps избавляют от издержек управления библиотеками кода или большими репозитариями данных, позволяя легко расширять функциональность веб-приложений. Меньшие издержки — штука хорошая, но тут есть потенциальный риск.
В качестве примера вообразим, что один из партнеров Contoso в индустрии медицинского ПО — Litware Inc. Компания Litware выпускает новый Exercise API и предоставляет разработчикам Contoso Health ключи для использования ежедневно обновляемого канала данных. Если бы Contoso Health было веб-приложением, группа разработки могла бы реализовать Exercise API, используя примерно такую ссылку на скрипт:
<script src="https://api.litware.com/devkey/exercise.js"></script>
Разработчики в Contoso доверяют Litware, которая предоставляет важный контент, и знают, что у этой компании большой опыт в защите данных. К сожалению, серверы Litware были скомпрометированы одним недовольным разработчиком, и exercise.js был изменен так, что стартовый скрипт в нем выводит всплывающее окно с сообщением «Contoso Health нуждается в техническом обслуживании; пожалуйста, загрузите следующее приложение для обслуживания». Пользователь, уверенный в полной легитимности этого сообщения, загружает вредоносное ПО. Разработчики Contoso озадачены: Litware использует отличные процедуры проверки, как же такое могло случиться?
В Web скрипты, на которые ссылаются так, как только что было описано, выполняются с тем же источником (origin), что и скрипт на том же сайте. Это означает, что exercise.js (выполняемый как JavaScript-код) получает несанкционированный доступ к DOM-дереву, а также к любому скриптовому объекту. Как было показано ранее, это может приводить к серьезным проблемам в защите. Чтобы ослабить риск таких проблем, Windows 8 разбивает ресурсы приложения на два контекста, представленных на рис. 2.
Рис. 2. Функции локального контекста в сравнении с веб-контекстом (взято из «Features and Restrictions by Context» [bit.ly/NZUyWt] и «Secure Development with HTML5» [bit.ly/JOoMOS])
Local Context | Локальный контекст |
Trusted Resources from App Package | Доверяемые ресурсы из пакета приложения |
• Access to WinRT Libraries | • Доступ к библиотекам WinRT |
• Access to Windows Library for JavaScript | • Доступ к Windows Library for JavaScript |
• Use of Cross-Domain XHR Requests | • Использование кросс-доменных XHR-запросов |
Web Context | Веб-контекст |
Remote Resources | Удаленные ресурсы |
• Limited Use of Windows Library for JavaScript | • Ограниченное использование Windows Library for JavaScript |
• Use of JavaScript URIs | • Использование JavaScript URI-идентификаторов |
• Use of External Script References | • Применение ссылок на внешние скрипты |
Both Have Access to W3C API | Оба контекста имеют доступ к W3C API |
Локальный контекст позволяет обращаться к Windows Runtime, а также к любым ресурсам, включенным в пакет приложения (например, к HTML, скриптам, CSS и данным приложения, хранящимся в каталогах его состояния), но из него недоступен удаленный HTML, JavaScript или CSS (как в предыдущем примере с exercise.js). Приложение верхнего уровня в Windows 8 всегда выполняется в локальном контексте. На рис. 2 ms-appx:// используется для разрешения контента в локальном контексте. Эта схема применяется для ссылок на контент в пакете приложения, выполняемом в рамках локального контекста. Зачастую используется третий слеш (ms-appx:///), чтобы ссылаться на полное имя пакета. Для веб-разработчиков этот подход аналогичен использованию протокола file://, где третий слеш ссылается на локальную файловую систему (file:/// предполагает file://КОМПЬЮТЕР КОНЕЧНОГО ПОЛЬЗОВАТЕЛЯ/ вместо file://УДАЛЕННЫЙ КОМПЬЮТЕР/).
Веб-контекст позволяет разработчикам использовать удаленный контент в приложениях Windows Store через iframe. Подобно iframe в веб-браузере контент, выполняемый в iframe, не получает доступа к внешним ресурсам, таким как Windows Runtime и некоторые средства Windows Library for JavaScript. (Полный список см. по ссылке bit.ly/PoQVOj.) Предназначение веб-контекста — дать возможность разработчикам ссылаться на сторонние API, например Bing Maps, или извлекать какую-то библиотеку из CDN в свое приложение.
Использование http:// или https:// в качестве источника iframe автоматически преобразует контент iframe в веб-контекст. Кроме того, iframe может быть ресурсом в пакете приложения, когда вы используете ms-appx или ms-appx-web. Если источник iframe ссылается на схему ms-appx://, содержимое iframe выполняется в локальном контексте. Это позволяет встраивать ресурсы пакета приложения в iframe, в то же время сохраняя доступ к функциональности локального контекста (например, Windows Runtime, Windows JavaScript API и др.). Другая схема — ms-appx-web://, позволяющая выполнять контент локального пакета приложения в веб-контексте. Эта схема полезна, когда вам нужно встроить удаленный контент в свою разметку, например добавить в приложение Contoso Health результат поиска местных больниц на основе местонахождения пациента, полученный через Bing Search (от Bing Search API). Попутно отмечу: всякий раз, когда какие-то iframe упоминаются вместе с HTML5, помните, что вы можете использовать атрибут sandbox как дополнительную защиту своего приложения, ограничивая скриптовое выполнение контента рамками iframe. Подробнее об атрибуте sandbox см. по ссылке bit.ly/Ppbo1a.
В табл. 1 показаны различные схемы, применяемые в локальных и веб-контекстах, наряду с примерами их использования.
Табл. 1. Схемы с примерами контекста
Схема | Местонахождение контента | Контекст | Пример | Когда используется |
ms-appx:// | Пакет приложения | Локальный | <iframe src="ms-appx:///1.html" ></iframe> | Загружает контент в iframe, которому нужен доступ к Windows Runtime или полному Windows JavaScript API |
ms-appx-web:// | Пакет приложения | Веб | <iframe src="ms-appx-web:///2.html" ></iframe> | Использует контент из удаленного источника как часть интерфейса приложения Windows Store, например показывает картографический виджет или результаты поиска |
http:// | Удаленный источник | Веб | <iframe src="http://host/3.html" ></iframe> | Ссылается на удаленный контент, такой как веб-страница или файл скрипта на другом сервере |
К какому контексту принадлежит iframe, зависит от того, как он ссылается на контент. Другими словами, контекст определяется схемой. Подробнее о схемах, применяемых в Windows 8, см. по ссылке bit.ly/SS711o.
Помните сценарий взлома Litware, с которого мы начали этот раздел? Разделение контекстов в Windows 8 поможет ограничить атаку с использованием кросс-сайтовых скриптов веб-контекстом, где злоумышленник не получит доступа ни к Windows Runtime, ни к данным приложения Contoso Health. В веб-контексте нельзя модифицировать локальный контекст. Взаимодействие между контекстами возможно, но вы получаете контроль за тем, какой тип взаимодействия будет осуществляться.
Взаимодействие между контекстами
Как документ верхнего уровня взаимодействует с iframe, выполняемым в веб-контексте? Используя средства postMessage в HTML5, приложения Windows Store могут передавать данные между контекстами. Это позволяет разработчикам структурировать то, как два источника взаимодействуют между собой, и разрешать доступ к локальному контексту только известным хорошим провайдерам (и снова список разрешенных элементов). Ссылки на страницы, которые должны выполняться в веб-контексте, осуществляются с применением iframe в комбинации с атрибутом src, задаваемым как http://, https:// или ms-appx-web://.
В случае Contoso Health система получает необходимые подсказки от Litware Exercise API. Группа разработки Contoso Health создала страницу litwareHelper.html, используемую для взаимодействия с Exercise API через jQuery-объект $ajax. Поскольку exercise.js является удаленным ресурсом, litwareHelper.html должна выполняться в веб-контексте, т. е. внутри iframe. Настройка iframe ничем не отличается от таковой для любых других веб-приложений, кроме способа ссылки на страницу. Так как страница litwareHelper.html — часть локального пакета приложения, но должна работать в веб-контексте, вы загружаете ее, используя ms-appx-web:
<iframe id="litwareHelperFrame” src="ms-appx-web:///litwareHelper.html"></iframe>
Группа разработки добавляет следующую функцию в страницу локального контекста, которая отправляет запрос данных странице веб-контекста:
function btnGetFitTips_Click() {
var msg = {
term: document.getElementById("txtExerciseSearchTerm").
value, itemCount: 25 }
var msgData = JSON.stringify(msg);
var domain = "ms-appx-web://" + document.location.host;
try {
var iframe = document.getElementById("litwareHelperFrame");
iframe.contentWindow.postMessage(msgData, domain);
}
catch (ex) {
document.getElementById("output").innerText ="Error has occurred!";
}
}
Эта функция сначала формирует объект msg, который и отправляется документом верхнего уровня (всегда выполняемым в локальном контексте) в iframe (выполняемым как веб-контекст) для обработки. Данные, посылаемые через postMessage могут быть JavaScript-типом (например, string или number), двоичным объектом или файлом, но не JSON-объектом. Для передачи через postMessage JSON-объект преобразуется в строку. Получив ссылку на litwareHelperFrame, приложение вызывает postMessage, чтобы отправить строковую версию объекта msg в iframe, и указывает домен-получатель этого сообщения. Вот и все, что нужно в Contoso Health для отправки данных из локального контекста странице веб-контекста.
Разделение по умолчанию локальных и веб-контекстов снижает риск случайного выполнения кода из внешнего источника.
Теперь страница litwareHelper.html должна использовать сообщение. В код litwareHelper.js первым делом добавляется слушатель события message. Это событие инициируется, когда сообщение принимается другим окном или фреймом:
(function () {
'use strict';
function initialize() {
window.addEventListener('message', receiveMsg, false);
}
function receiveMsg(e) {
...
}
document.addEventListener("DOMContentLoaded",
initialize, false);
})();
Метод receiveMsg обрабатывает сообщение из локального контекста. Аргументом receiveMsg являются данные, переданные в событие postMessage (в данном случае — переменная msgData), наряду с мишенью сообщения, его источником и некоторой другой информацией, как показано на рис. 3.
Рис. 3. Обработка с помощью receiveMsg
function receiveMsg(e) {
if (e.origin === "ms-appx://" + document.location.host) {
var output = null;
var parameters = JSON.parse(e.data);
var url = "https://api.litware-exercise.com/data/" +
parameters.term +
"/count/" + parameters.itemCount;
var options = {
dataType: "jsonp",
jsonpCallback: "jsonpCallback",
success: function (results) {
output = JSON.stringify(results.items);
window.parent.postMessage(output, "ms-appx://" +
document.location.host);
},
error: function (ex) {
output = ex;
}
};
$.ajax(url, options);
}
}
В receiveMsg сначала проверяется источник postMessage. Это крайне важная для безопасности проверка, гарантирующая, что сообщение поступает оттуда, откуда и должно. Помните, что e.origin проверяет домен и схему того, кто отправил postMessage, и вот почему вы выполняете проверку на ms-appx (адрес локального контекста). Собрав JSON-данные от Litware API, приложение передает результаты обратно в window.parent, используя команду postMessage. В receiveMsg обратите внимание на то, что домен задан как ms-appx. Это адрес того, «кому» куда отправляется сообщение, и он показывает, что данные возвращаются в локальный контекст. Данные от iframe должны использоваться ресурсами в локальном контексте. Группа разработчиков добавляет функцию processResult для использования данных от веб-контекста в локальном контексте:
function processResult(e) {
if (e.origin === "ms-appx-web://" + document.location.host) {
document.getElementById("output").innerText = e.data;
}
}
И вновь всегда проверяйте источник события message, чтобы гарантировать обработку только данных из одобренных источников (зарегистрированных в списке разрешенных элементов). Заметьте, что источником является схема веб-контекста: ms-appx-web в методе processResult.
Наконец, чтобы вернуть данные из веб-контекста в страницу локального контекста, для события message добавляется обработчик. В метод app.onactivated добавьте слушатель события к объекту window:
window.addEventListener('message', processResult, false);
Разделение по умолчанию локальных и веб-контекстов снижает риск случайного выполнения кода из источника, который находится вне приложения Windows Store. Используя postMessage, разработчики могут обеспечить коммуникационный канал между внешним скриптом и локальными скриптами, из которых состоит приложение.
Из Web в Windows 8: новые средства для старых привычек
Теперь веб-разработчики могут пользоваться привычными и новыми средствами при создании защищенных приложений Windows Store. Используя существующие навыки, например в проверке ввода средствами HTML5, вы гарантируете целостность данных, вводимых в приложение. Новые средства, такие как Data Protection API (новые для Windows Runtime), защищают конфиденциальные данные пользователя стойким шифрованием, простым в реализации. Применение postMessage позволяет приложениям задействовать тысячи библиотек JavaScript и унаследованный веб-код, в то же время оберегая пользователей от попыток злонамеренного встраивания вредного кода. Вместе все эти элементы дают такую важную вещь, которую часто упускают из виду в JavaScript: безопасность.
Windows 8 дает шанс веб-разработчикам пересмотреть некоторые из их старых привычек. JavaScript больше не является фасадом для сервера, применяемым для удобства в использовании и ни для чего другого. JavaScript, Windows Runtime и MSHTML теперь предоставляют средства, необходимые для встраивания средств защиты в ваши приложения Windows Store — никакие серверы больше не требуются. Как веб-разработчики мы можем опираться на свой накопленный опыт, но должны с оглядкой относиться к старым привычкам и активно осваивать новый мир Windows 8.