среда, 22 июня 2011 г.

Kung-Fu Nemerle или безопасNый код


"Внедрение безопасности в ваш продукт полностью зависит от вас. И никто другой не решит все проблемы безопасности за вас, никакое волшебное средство или язык программирования. <...> Это можете сделать только вы сами." (C) Майкл Ховард, "8 простых правил для разработки более безопасного кода"
Язык программирования Nemerle является весьма привлекательным инструментом, в первую очередь благодаря тому, что предоставляет в распоряжение разработчика достаточно мощные и выразительные средства метапрограммирования, позволяя прикладному разработчику влиять на ход компиляции и, практически произвольным образом, манипулировать объектной моделью компилируемого кода. Эти средства, помимо прочего, позволяют вводить в язык абстракции более высокого уровня, избавляющие программиста от рутины кодирования и приближающие его к программированию в терминах предметной области разрабатываемой информационной системы (ИС). Но что если посмотреть на понятие предметной области под несколько иным углом и представить ее, как область написания безопасного кода и разработки ИС, защищенных от реализации информационных угроз? Какие средства, позволяющие достичь этого, уже реализованы в Nemerle и в поставляемых с его компилятором стандартных библиотеках и какие средства этот язык позволяет создавать и использовать разработчику самостоятельно? Ответам на эти вопросы и посвящена данная заметка.

Дисклеймер

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

Так о чем пойдет речь?

В то же время, от языка программирования зависит многое. Если язык не препятствует или даже поощряет использование опасных с точки зрения безопасности конструкций кода, если он заставляет программиста постоянно думать о различных мелких, но многочисленных "особенностях", не имеющих отношения к предметной области в которой тот решает текущую задачу, если вместо простой сущности "список клиентов", язык заставляет помнить о сущности "список указателей на объекты, занятую память которыми необходимо освободить при удалении объекта" и т.п., то обеспечение безопасности кода на таком языке потребует массу затрат со стороны программиста, по сравнению с языком, лишенным этих недостатков. И, если в ряде задач, действительно требующих низкоуровневых средств, просто не остается иного выбора, как засучив рукава, обеспечить эту самую массу затрат, то у разработчиков прикладной области выбор все-таки есть, а если и нет, то это говорит о наличии причин, скорее административного характера, чем технического.
Правильный язык программирования, с точки зрения нашей предметной области, должен не только обеспечивать благоприятное для программистов окружение разработки на нем защищенного от информационных угроз кода, но и предоставлять средства доработки и расширения этого окружения под нужны разработчиков, а также иметь в своем арсенале уже готовые средства, которые помогли бы справляться с наиболее распространенными и часто встречающимися угрозами. И главное достоинство Nemerle в том, что он как раз и является таким языком.
Давайте пройдемся по наиболее часто встречающимся ошибкам, приводящим к информационным угрозам[1] и посмотрим, какие средства нам предоставляет Nemerle для того, чтобы избежать их. А затем, мы немного поразмышляем над тем, какие средства мы можем создать самостоятельно, благодаря поддержки этим языком парадигмы метапрограммирования.

Переполнения, форматные строки

К великому счастью, уязвимости всевозможных переполнений (буфера, кучи, целочисленных и вещественных типов) теоретически неактуальны для управляемых языков и, казалось бы, нет никакой разницы между такими языками, но это не совсем так. Добиться переполнения буфера, к примеру, в C#, все же можно, слегка ошибившись в коде с unsafe-блоками, которые позволяют ностальгирующим по C++ получить все его прелести "не выходя из домика". Вот пример переполнения буфера на C#, взятый отсюда:

Данный пример выведет на консоль число 99 из-за того, что обращение к несуществующему x.items[16] фактически перезатрет значение x.after. Кто-то может возразить, что нужно сильно постараться, чтобы наступить на такие грабли, программируя на C#. На это, я могу лишь заметить, что постараться тут нужно только в плане появления в проекте unsafe-блоков. Как только это произойдет, стараний потребуется не более, чем при разработке на том же C++. И вот тут то, что некоторые считают недостатком Nemerle с точки зрения разработки, внезапно становится достоинством с точки зрения безопасности: Nemerle не поддерживает unsafe-блоки, а следовательно, сопровождая готовый код на нем, вы не наткнетесь там на подобные скелеты в шкафу. Впрочем, как сказал Влад Чистяков (лидер проекта по разработке компилятора Nemerle) в ответ на вопрос, поддерживает ли этот язык unsafe: "Нет. Но может использовать intrerop (PInvoke и COM-intrerop). С их помощью, к сожалению тоже можно легко привести программу к неработоспособности", что лишний раз подтверждает тезис об отсутствии теоретической возможности у языков программирования, стать когда-нибудь серебряными пулями безопасности.
Что касается форматных строк, то в Nemerle реализована как поддержка традиционных для .NET форматных строк с блэкджеком со StringBuilder'ом и плейсхолдерами, так и ряд средств, облегчающих форматирование строк, и являющихся по сути синтаксическим сахаром для традиционного подхода. В первую очередь, это так называемые splice-строки, позволяющие передать в строку значения и даже выражения на манер PHP:

Nemerle также поддерживает рекурсивные строки вида:

Данный пример является абсолютным аналогом предыдущего. Кроме того, в стандартной макробиблиотеке Nemerle, в пространстве имен Nemerle.IO определен макрос printf, являющийся аналогом одноименной функции форматного вывода языков C/C++:

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

Межсайтовый скриптинг aka XSS

Инъекции данных в целом и межсайтовый скриптинг (в простонародье - "XSS") в частности, являлись и продолжают являться настоящим бичом современных web-приложений. Об этом красноречиво говорит статистика уязвимых приложений в архивах XSSed.com. О борьбе с подобными инъекциями в ASP.NET и смежных с ним фреймворках написаны тонны полезных статей и нет никаких причин для того, чтобы не воспользоваться ими при разработке приложений под ASP.NET, ASP.NET MVC или NRails. В первую очередь, это официальное руководство по искоренению XSS от Microsoft, и использование библиотеки AntiXSS для более эффективного экранирования данных из недоверенных источников перед помещением их в тело HTML или XML документов.
Но в наборе стандартных библиотек, поставляемых с Nemerle есть еще кое-что, что позволяет немного облегчить жизнь разработчику. Не так уж и редко возникает задача генерации какого-либо фрагмента HTML (или XML, в общем случае) на лету, в runtime, на основе входных данных, полученных от пользователя. Возьмем, к примеру, задачу отображения его анкетных данных, ограничившись для простоты только именем. В этом случае, C#-разработчик, еще не знающий о необходимости экранирования данных, напишет нечто подобное:

где name - имя, переданное пользователем. И получит HTML-инъекцию во всей красе, которая приводит к межсайтовому скриптингу, поскольку атакующему ничто не мешает передать в качестве своего имени строку <script>alert(document.cookie);</script> и тем самым заставить сервер возвращать в ответ на запрос данных об этом пользователе следующий документ:

обработка которого браузером повлечет за собой выполнение внедренного javascript-кода. Все, что должен сделать в данном случае[2] C#-разработчик, чтобы устранить уязвимость - это экранировать переменную name перед вставкой в шаблон документа вызовом HttpUtility.HtmlEncode(name). А если данных, подставляемых в шаблон - несколько десятков? А если шаблон собирается из нескольких фрагментов, еще и вложенных друг в друга? Ситуация не такая уж и невероятная, я много раз сталкивался с приложениями, в которых документы собирались именно таким образом, из заинлайненных в код текстовых фрагментов. Вероятность того, что разработчик допустит ошибку и не обеспечит экранирование какой-либо переменной очень высока, а если и обеспечит, то читать потом нагромождения вызовов HtmlEncode() в попытке разобраться, какая из переменных будет подставлена в форматной строке вместо, скажем {7} - занятие не из приятных. Между тем, все, что нужно разработчику, чтобы безопасно решить эту задачу на Nemerle, это написать так:

указав в своей сборке референс на макросборку Nemerle.Xml.Macro. В этом случае, если атакующий попытается передать в name строку, приведенную выше, наш код создаст в переменной html документ, чье текстовое представление выглядит так:

... что совершенно идентично использованию HtmlEncode(). Почему? Потому что xml <#...#> - это макрос из подключенной нами макробиблиотеки, обеспечивающий поддержку так называемых XML-литералов, аналогичных реализованным в языках Scala и Visual Basic. На первый взгляд, может показаться, что этот макрос реализует возможность встраивания фрагментов XML непосредственно в код, оперируя их текстовым представлением, но это не так. На самом деле, он является синтаксическим сахаром для построения XElement-дерева - объектной модели XML-узла, позволяя декларативно описывать эту структуру и предоставляя ряд синтаксических примитивов для передачи в эту структуру данных извне, которые вполне корректно экранируются при преобразовании дерева в строку.

Внедрение кода SQL aka SQLi

Не думаю, что ошибусь, если назову уязвимости внедрения кода SQL (в простонародье "SQLi") вторым по порядку, но не по значимости бичом современных приложений. Причем, в отличии от XSS, данный класс уязвимостей характерен отнюдь не только для web-приложений и несет в себе гораздо более существенные риски за счет того, что через эксплуатацию SQLi осуществляется атака на серверную часть приложения. Красноречивое подтверждение тому также существует, хотя бы в лице компании Sony, чьи системы и ресурсы были массово разломаны в первом полугодии 2011 года именно благодаря тому, что большинство из них было подвержено этому классу уязвимостей. Несмотря на то, что в любом языке уже давно есть встроенные или библиотечные средства безопасного формирования текста SQL-запросов, разработчики по-прежнему, с упорством достойным фразы братьев Стругацких: "он грыз гранит, не жалея ни зубов, ни гранита", продолжают использовать конкатенацию строк для параметризации SQL-запросов на основе входных данных, полученных из недоверенного источника. Разумеется, использование ORM-фреймворков и LINQ решает проблему SQLi весьма кардинальным образом, но тем не менее, в случае если разработчику понадобится сформировать SQL-запрос врукопашную, ничто не помешает ему воспользоваться той же форматной строкой, чтобы записать всю конструкцию в одну текстовую строку:

вместо написания лишних строк кода, определяющих параметры запроса:

Думаю, понятно, что будет при выполнении такого запроса, если атакующий сможет передать в countryName строку типа from Russia with love'; DROP TABLE Customers-- в первом случае? В стандартной макробиблиотеке Nemerle, в пространстве имен Nemerle.Data реализованы средства, позволяющие решить эту проблему, предоставляя разработчику возможность формировать текст запроса способом, очень похожим на конкатенацию - столь же простым, но в то же время совершенно безопасным:

И здесь также нет никакой магии и поддержки со стороны ядра языка. ExecuteReaderLoop является макросом, который во время компиляции разворачивает эту конструкцию в аналогичную приведенной во втором примере C#-кода, переписывая ее в код создания корректно параметризованного объектами SQL-запроса (типы параметров которого определяются по типам $-переменных, указанных в тексте запроса), его исполнения и итерации по списку полученных записей. Помимо макроса ExecuteReaderLoop в пространстве имен Nemerle.Data определены также макросы: ExecuteScalar для обработки скалярных запросов, возвращающих единственное значение и ExecuteNonQuery для исполнения запросов, не возвращающих ничего.
Как мы видим, в случае с SQLi, средства Nemerle дают уже весьма ощутимое преимущество перед другими языками, предоставляя разработчику возможность формировать SQL-запросы действительно удобным, но в то же время безопасным образом.

Гонки aka Race Conditions

Не секрет, что гонки возникают там, где имеют место побочные эффекты. Также не секрет, что побочные эффекты имеют место там, где используются изменяемые переменные или поля классов. И уж совсем не секрет, что изменяемые переменные или поля классов используются в том же C# повсеместно, да и вообще-то являются там таковыми по умолчанию. И в данном случае, Nemerle опять оказывается на шаг впереди, вынуждая разработчика явным образом объявлять изменяемость переменных через их определение различными ключевыми словами. Поля классов в Nemerle по умолчанию являются неизменяемыми, если обратное не указано явно соответствующим ключевым словом. И коварные поляки, разрабатывая язык, придумали незатейливый, но очень хитрый план по принуждению разработчика к использованию неизменяемых сущностей в конструируемом коде. Этот план играет на святая святых разработчиков - их лени. Для того, чтобы определить неизменяемую переменную, нужно воспользоваться ключевым словом def при ее объявлении. Для того, чтобы объявить неизменяемое поле класса его нужно просто объявить. А вот для объявления изменяемых сущностей, необходимо и в том и в другом случае использовать ключевое слово mutable, которое более чем в два раза длиннее слова def! Разумеется, ленивый разработчик будет использовать def, где только можно, а поля класса объявлять изменяемыми только там, где это действительно необходимо.
Вас наверняка это улыбнет и покажется вам забавной и незначительной, а возможно и раздражающей, мелочью... Не спешите с выводами. Уже через несколько недель активного программирования на Nemerle к разработчику приходит понимание того, насколько редко на самом деле нужны изменяемые сущности в коде. По прошествии же еще некоторого количества времени, разработчик начинает очень придирчиво изучать места, в которых использование изменяемых сущностей ему ранее казалось необходимым, на предмет рефакторинга кода с целью посильного искоренения подобных мест. Но самое страшное, что разрабатывая после этого на C#, такой разработчик чувствует постоянный осадочек из-за того, что он мог забыть где-то воткнуть readonly или const (превозмогая лень, заметьте!), одновременно осознавая, насколько этого недостаточно, чтобы получить те же гарантии, которые он имел в Nemerle, совершенно не напрягаясь. Кроме шуток: многопоточный Nemerle-код имеет гораздо меньше потенциальных мест возникновения гонок по сравнению с аналогичным C#-кодом, только за счет этой, "забавной и незначительной, а возможно и раздражающей, мелочи". Разумеется, кто-то возразит, что если уж разработчик задастся целью ввести в код изменяемую сущность, то необходимость использования более длинного ключевого слова его не остановит. Конечно, это так. Но сам стиль кодирования на Nemerle, диктуемый дизайном языка, склоняет пишущего код к тому, что бы он постоянно задавался вопросом - а действительно ли объявление изменяемой сущности в каждом конкретном месте - это именно то, что нужно для решения текущей задачи? И это реально работает.
Кроме того, в арсенале макросов, поставляемых вместе с компилятором Nemerle есть нечто, хотя и неявно, но способствующее разработке безопасного многопоточного кода - это библиотека Nemerle.ComputationExpressions, реализующая концепцию построения цепочек вычислений на базе функционального паттерна "монада", предназначенного как раз для инкапсуляции функций с побочным эффектом от чистых функций, а точнее их выполнений от вычислений. Computation Expressions заимствована из F# и предоставляет в распоряжение разработчика набор так называемых "билдеров", позволяющих комбинировать вычисления тем или иным способом. В числе билдеров, уже реализованных в библиотеке, присутствует билдер Async, предназначенный для реализации многопоточных вычислений. Более подробно с данной библиотекой и примерами использования билдера Async можно познакомиться в статье Дениса Рысцова, посвященной асинхронному программированию с использованием этой библиотеки.

Контроль данных

На протяжении всей заметки я неоднократно употреблял в различных формах понятие "входные данные, полученные из недоверенного источника" или "недоверенные входные данные" и тому есть веская причина. Эти данные являются единственными векторами потенциальных атак на приложение, манипулируя которыми, атакующий может попытаться повлиять на поток выполнения кода и, тем самым, реализовать информационные угрозы. Эти данные, пересекающие границы доверия различных типов в модели угроз ИС, требуют пристального контроля со стороны конструируемого кода. Вторым эшелоном защиты, о котором часто забывают разработчики, является контроль данных, пересекающих границы доверия в обратном направлении, т.е. выходных данных. Грамотная реализация обоих контролей часто позволяет компенсировать возможные ошибки в реализации каждого из них и минимизировать риски, связанные с обработкой таких данных, как внутри ИС, так и вне нее.
Контроль как входных, так и выходных данных можно условно разделить на валидацию и экранирование. Валидация заключается в проверке данных на соответствие ряду критериев таких, как соответствие типов, формата, ограничений на значения и т.п. Непосредственной задачей, которую решает валидация, является обеспечение гарантии того, что дальнейший код, сможет корректно обработать данные, прошедшие валидацию, а данные, для которых процесс валидации завершился неудачно, не будут допущены к обработке, о чем валидатором возможно будет сделана запись в журнале приложения в мере "достаточной для реконструкции событий безопасности в дальнейшем"[3]. Валидация не вмешивается в данные, проверяя их соответствие заданным требованиям. Этот контроль должны проходить все, без исключения, недоверенные входные данные, непосредственно после их получения и, желательно, все выходные, непосредственно перед их отправкой во внешнюю по отношению к ИС среду. Экранирование же, напротив, предназначено для преобразования либо упаковки данных в формат, достаточный для обеспечения их корректной обработки и осуществляется по месту непосредственного использования этих данных тем или иным образом.
Задача экранирования вряд ли может иметь обобщенное, универсальное решение на все случаи жизни, тем более, поддерживаемое средствами языка. Хотя бы, потому что логика экранирования сильно зависит от того, где мы планируем использовать эти данные. Это достаточно хорошо видно на приведенных выше примерах экранирования данных для HTML и SQL (да-да, объектная параметризация SQL-запросов тоже является одним из способов экранирования, хотя и не изменяющего данные, но передающего его во внешнюю среду безопасным для нее образом). Преимущество, которое дает Nemerle в данном случае, заключается в том, что прикладной программист имеет возможность самостоятельно ввести в язык средства, аналогичные XML-литералам и макросам SQL-запросов, инкапсулируя в них логику экранирования.
Задача валидации же, напротив - вполне формализуема и поддается обобщению. Одним из таких обобщений является контрактное программирование aka Design by Contract (DbC). Поддержка данной концепции была реализована в .NET 4 и о том, что она из себя представляет, как выглядит C#-код, реализующий контракты и какие шаги необходимо проделать разработчику, чтобы обеспечить поддержку контрактов в своем C#-проекте, можно подсмотреть в статье Романа Калита "Code Contracts в .NET 4.0". Между тем, поддержка контрактного программирования в Nemerle появилась еще задолго до выхода .NET 4.0 и мало чем отличается от реализации Microsoft в плане предоставляемых возможностей (по крайней мере в части, касающейся контроля соблюдения контрактов в runtime), но имеет и ощутимое преимущество, заключающееся в том, что реализация DbC в Nemerle позволяет декларативно описывать контракты, органично вписываясь в основной синтаксис языка. Как вы наверное уже догадались, это также стало возможным благодаря поддержки Nemerle метапрограммирования, а конструкции, которые мы рассмотрим ниже, являются ни чем иным, как макросами.
Давайте рассмотрим, что нам может дать поддержка DbC в Nemerle в плане валидации данных на простеньком примере. Допустим, перед нами стоит задача получить от пользователя его имя и URL его домашней страницы и вернуть ему HTML-документ с приветствием и ссылкой. Реализуем метод, принимающий на вход две строки (допустим, что они выбираются прямехонько из параметров HTTP-запроса и никак не обрабатываются до попадания в наш метод) и возвращающий сформированный текст документа, который необходимо передать в ответ на запрос:

Обратите внимание, что аргумент page попадает внутрь тега <a> и средств экранирования, предоставляемых нам XML-литералом уже недостаточно для того, чтобы избежать угрозы реализации XSS (хотя бы, потому что атакующий может передать в этом параметре строку типа javascript:alert(0); или использующую URI-схему data: и выполнить этот код в браузере жертвы при щелчке по такой ссылке), поэтому сформулируем более жесткие требования к содержимому аргументов рассматриваемого метода. Пусть name может содержать в себе только латинские буквы, цифры и символ подчеркивания и длина этого аргумента должна лежать в диапазоне 5-20 символов. Пусть page должен быть правильно сформированным URL, использующим схему HTTP. Опишем эти требования с помощью средств DbC:
Здесь requires - это макрос, определенный в пространстве имен Nemerle.Assertions стандартной макробиблиотеки, позволяющий определить предусловия метода в терминах DbC и добиться результата, который мы ожидали. Разумеется, особой необходимости выносить реализацию условий в отдельные методы нет, но это позволяет разгрузить синтаксис определения условия и собрать в одном месте реализацию всей логики валидации. И раз уж мы ударились в паранойю, то нужно биться до конца. Что, если в реализации экранирования XElement, или методе IsWellFormedUriString, и в нашем регулярном выражении обнаружатся ошибки, которые позволят атакующему обойти все наши защитные механизмы, передать в наш документ malformed-строку и, тем самым, нарушить его структуру (то есть заставить XElement.ToString() вернуть не валидный XML-документ)? Что, если другие разработчики проекта реализуют в этом методе код, который изменяет уже преобразованный в строку XElement и сводит на нет все наши усилия по экранированию? Мы должны быть уверены в том, что мы не отдадим такой документ обратно пользователю. Вот, как может выглядеть метод, обеспечивающий нам такую уверенность:

Здесь мы добавили еще один метод IsValidXmlSyntax(), проверяющий корректность синтаксиса XML в передаваемой ему строке и использовали новый макрос ensures, предназначенный для определения постусловий контракта нашего метода. Идентификатор value используется для ссылки на значение, возвращаемое методом. Но что произойдет, если будут нарушены пред- или постусловия контракта нашего метода, т.е. если он будет вызван с аргументами, не удовлетворяющими предусловию или попытается вернуть данные, не удовлетворяющие постусловию? В этом случае, будет брошено исключение Nemerle.AssertionException с подробной информацией о том, что и где было нарушено. Но это не всегда является приемлемым вариантом, иногда целесообразнее бросить иное исключение, иногда желательно вообще обойтись без них, используя вместо переданных аргументов некие значения по умолчанию. Синтаксис макросов requires и ensures предусматривает опциональное использование после логического выражения ключевого слова otherwise и следующего за ним выражения, определяющего поведение кода в случае нарушения контракта. В качестве такого выражения может быть и код, выбрасывающий какое-либо исключение и вызов метода, обеспечивающего обработку данной ситуации и, возможно, пытающегося исправить ее тем или иным способом:

В данном примере, в случае нарушения предусловия, вызывается метод PreConditionFail, делающий запись в консоль о произошедшем событии и возвращающий значения по умолчанию для аргументов name и page которыми перезаписываются их реальные значения, после чего метод продолжает свою работу.
Реализация DbC в Nemerle также поддерживает контроль так называемых инвариантов класса. Если бы перед нами стояла задача сохранить name и page из предыдущего примера в объекте модели и обеспечить соответствие их значений озвученным выше условиям на протяжении всего жизненного цикла объекта, то соответствующий класс можно было бы реализовать так:

Макрос invariant как раз и обеспечивает контроль соблюдения условий, заданных в нем, на протяжении всей жизни экземпляра класса.
Как мы видим, и для задачи по обеспечению контроля данных, полученных из недоверенных источников в стандартной макробиблиотеке Nemerle нашлись средства, позволяющие решить ее весьма простым способом не загромождая при этом код бизнес-логики конструкциями, не имеющими к ней ни малейшего отношения, как в случае использования DbC в C#.

И это все?

Не совсем ;) Дело в том, что все рассмотренные выше сценарии противодействия угрозам приведены не в качестве побуждения к их немедленному использованию (хотя причин не использовать их, лично я не вижу), а как примеры того, что может реализовать прикладной программист в рамках обозначенной нами предметной области. Все перечисленные выше средства, способствующие разработке безопасного кода являются лишь следствием из главного достоинства этого языка: его расширяемости. Все, без исключения конструкции, приведенные здесь - являются макросами, а следовательно, могли быть реализованы не только командой, разрабатывающей компилятор Nemerle, но и любыми другими программистами, которые бы возможно захотели облегчить себе жизнь за счет введения в язык новых абстракций, конструкций, паттернов и т.п. Магия Nemerle заключается не столько в том, что уже разработано его авторами, сколько в том, что может быть разработано сторонними программистами на том же самом уровне. Его главным достоинством является то, что он практически уравнивает в правах тех и других, предоставляя равные возможности по развитию языка и его расширению под конкретные задачи. Например, основа библиотеки Nemerle.ComputationExpressions была реализована сторонним разработчиком just for fun в попытке ответить на свой собственный вопрос: "а можно ли так же, как на F#, только в Nemerle?". Теперь вот и мы, в этой заметке, задались вопросом на тему безопасного кода... ;)

В заключение

Давайте, напоследок, немного поразмышляем над тем, чему бы стоило появиться в Nemerle в ближайшее время с точки зрения выбранной нами в начале предметной области? Какие средства, аналогичные рассмотренным может реализовать прикладной программист в целях повышения безопасности своего кода в частности и всей разрабатываемой системы в целом? Каких средств не хватает в существующих библиотеках?
Несмотря на то, что даже та малость, которая была продемонстрирована здесь - это уже больше, чем есть в современных языках, человеку всегда и везде всего будет мало. Поэтому, с пониманием того, насколько мощным средством являются макросы Nemerle, приходит также и ощущение острой нехватки всего, что можно было бы создать, используя это средство :) Например, возможное дальнейшее развитие идеи XML-литералов в направлении безопасности, заключается в построении на их основе движка шаблонов XML/HTML, интегрированного с библиотекой AntiXSS либо реализующего ее функционал, поскольку между ним и традиционными методами экранирования, применяемыми в System.Xml.Linq (поверх которого и построены XML-литералы), есть одна небольшая, но существенная разница, аналогичная разнице с экранированием при помощи HttpUtility. Подобный движок был бы достойным конкурентом всевозможным Razor'ам по части защищенности от угроз XSS.
Другим интересным направлением была бы реализация концепции, аналогичной taint-check'ам в языке Perl. Учитывая практически неограниченные возможности макросов по анализу кода на этапе компиляции, было бы довольно здорово получить возможность отслеживания "загрязнения" кода недоверенными данными и контролю их очистке, как в compile-, так и в runtime. Продолжением этого направления является реализация возможности подключать в состав проекта, модели угроз, аналогичные TMT'шным и формировать логику контроля данных в коде на основе информации о пересечении ими границ доверия, заданных в моделях.
В репозитории Nemerle есть несколько примеров весьма изящной реализации design-паттернов на базе макроатрибутов. Ничто не мешает создать макробиблиотеку, реализующую некоторые из security-паттернов ([1],[2]), аналогичным образом.
Помимо рассмотренной задачи валидации входных данных, с помощью средств DbC можно довольно красиво решить задачу контроля прав доступа. Но еще красивее, выглядел бы фреймворк, позволяющий декларативно разметить объекты доступа (классы модели), субъекты доступа и транзакции доступа (классы бизнес-логики и их методы), определить класс контроллера доступа и получить на этапе компиляции генерацию всего необходимого кода, реализующего вызовы контроллера доступа при каждой попытке со стороны субъектов доступа осуществить какие-либо действия над объектами доступа. Это было бы просто нереально круто, на самом деле. Отсюда, кстати - один шаг к реализации мандатной модели контроля доступа в отдельно-взятом приложении.
Я могу продолжать бесконечно, на самом деле, поэтому, с вашего позволения, я все же остановлюсь :) Отмечу лишь, что в приведенных выше "хотелках" нет, ровным счетом, ничего фантастического, все это можно реализовать на макросах Nemerle прямо сейчас, было бы кому и было бы когда.

На правах эпилога

Разумеется, многие угрозы остались за бортом, не вписавшись в рамки этого обзора. Открою небольшой секрет, данная заметка была написана по, еще весьма сырым, наработкам серии статей для журнала RSDN, посвященных разработке защищенного кода на Nemerle, в которых будут охвачены все известные на сегодняшний день "смертные грехи компьютерной безопасности" (с)[4]. Сюда же, попали только те возможности языка и его библиотек, которые уже реализованы и которые являются результатами использования средств метапрограммирования, демонстрируя возможности по расширению языка в направлении нашей предметной области. Конечно же, еще предстоит большая работа по созданию более развитых и формализованных средств обеспечения безопасности кода и ИС, разрабатываемых на Nemerle. Я очень надеюсь, что этот обзор подтолкнет разработчиков к тому, чтобы задуматься над реализацией этих средств в составе собственных библиотек или фреймворков. И, кто знает, быть может одна из таких библиотек и станет, если не серебряной пулей, то осиновым колом безопасного кода? Все, что необходимо для ее появления на свет, в Nemerle уже реализовано...


[1] - во избежание разбухания объема и без того немаленькой заметки, рассматриваемые угрозы не описываются в ней сколь-нибудь подробно. За развернутым описанием каждой из них, можно обратиться к классификации угроз по версии WASC или по версии OWASP
[2] - здесь подразумевается случай, когда данные вставляются между тегами. Если данные попадают внутрь тега, внедренного клиентского сценария или стиля, то следует воспользоваться библиотекой AntiXSS, либо соответствующими методами класса HttpUtility из пространства имен System.Web.
[3] - процитированная фраза является вольным изложением требований абсолютно всех действующих стандартов и рекомендаций разработки защищенных ИС к подсистемам журналирования событий безопасности.
[4] - "24 смертных греха компьютерной безопасности", М. Ховард, Д. Лебланк, Дж. Вьега, 2010 г., ISBN 978-5-49807-747-5, 978-0071626750.

Комментариев нет:

Отправить комментарий

Пожалуйста, будьте вежливы к автору и остальным посетителям этого блога