Безопасность Web-приложений
А вот слева вы видите две лучшие книги по безопасности. Но между этими темами - пропасть. Ибо, сама по себе и криптография и SSL (чему посвящена правая книга) - НИКАК не влияет на безопасность. И правая книга хорошо поможет вам разобраться в неких конкретных деталях базовых классов шифрования, например если вы не понимаете режимов паддинга шифруемого потока до границы блока при симметричном шифровании или режимы приложения цифровой подписи к XML-файлу. И не более. Я бы сказал, что она имеет несколько теоретический характер. Если вы хотите узнать что-то более практичное, например описание конкретной технологии WSE3 - то эта книга вам уже не поможет. Отличное описание WSE3 вы найдете здесь. (Ой, ну только не читайте ГЛУПЫХ и вредных описаний WSE3 здесь). Однако вернемся к классическим ASP2-приложениям с пользовательским интерфейсом.
Левая книга - это лучшее, что я вообще видел на тему безопасности в ASP2, она широка по охваченным темам и глубока в каждой теме. Она содержит много полезного даже на тему защиты WEB-сервера, когда конкретное Web-приложение УЖЕ взломано, например CAS-разрешения на сборки ASP2-приложений (частичное доверие к сборкам ASP2-приложений). Это безусловно ценая информация, однако хотелось бы чтобы атаки не заходили так далеко. Поэтому тут мы рассмотрим более ранние и практически полезные методы защиты ASP2-приложений, вовсе упущенные в этой книге.
Прежде всего, давайте рассмотрим, почему именно SSL-коннект никак не влияет на безопасность приложений - а, как мы увидим дальше, даже ее ухудшает:
- К сожалению, идея SSL защищает ТОЛЬКО от перехвата на линии, что совершенно неактуально на сегодня. Ведь не менее 90% взломов - это элементарные текстовые кейлоггеры, которые бывают и аппаратные по цене в несколько долларов, так и программные.
Продвинутые детки сегодня начинают программировать лет в 10 и все-все-все букварики для них основаны именно на написании собственного снифера клавиатуры - кейлоггера. Именно поэтому особенно глупо выглядит на этом фоне попытка добиваться защищенности Web-приложений НА ЛИНИИ, ПОЛАГАЯ ЧТО КЛИЕНТСКИЙ КОМПЬЮТЕР ЯВЛЯЕТСЯ АБСОЛЮТНО ЗАЩИЩЕННОЙ ЗОНОЙ.
К сожалению, на этой нелепой предпосылке основано и ГОСТ'овское шифрование. Которое рассматривает ИСКЛЮЧИТЕЛЬНО математические вопросы взлома шифра на линии (причем исходя из совершенно нелепых предположений, о которых мы поговорим дальше) и совершенно не принимает во внимание, что Боб (или Алиса) - это НЕ доверенная сторона, а компьтер пользователя с установленным кейлоггером.
Как это ни нелепо выглядит, но наш ГОСТ НИКАК не оговаривает маханизм хранения закрытых ключей на клиентском компьютере, чем дает карт-бланш на простое хранение закрытых ключей в обычной файловой системе - программые комплексы, таким образом реализующие ГОСТ'овское шифрование - прекрасно получают сертификат ФСБ. Ну ибо такие чудо-проги, действительно соответствуют стандарту.
Как вы думаете - добавляет ли вам безопасности уверенность в том, что ваша защита удостоверена ФСБ, между тем как любой пятикласник в пять секунд вычитает с зараженного компа логин и пароль и воспроизведет его для входа на сайт?
Этот маразм мне напоминает браузерный элемент <Input type="password"> который прикрывает пароль, который вы вводите ОТ ВАС точечками, а затем передает его в открытом виде через всю сеть!
- Второе еще более бредовое и нелепое предположение SSL состоит в том, что нам НЕИЗВЕСТЕН ЗАРАНЕЕ ответ Web-сервера. Кому мог прийти этот бред в голову?
Ответ web-сервера стандартен, следовательно передавая в сеть его шифрованным - мы одной простой машинной операций XOR над заведомо известным ответом и перехваченным его шифрованным кодом - СРАЗУ ПОЛУЧАЕМ КЛЮЧ ШИФРОВАНИЯ.
Не понимаю, это видно только мне, или это видно всем-всем-всем, но почему-то все молчат о том, что король-то голый!
- Третье, нелепое заблуждение состоит в том, что покупая тафтовский сертификат с ключом немерянной длины мы как-то улучшаем защиту своего приложения. Здесь срабатывает еще одно распространенное заблуждение, какой бы длины не был ассимметричный ключ - браузерная SSL-сессия стандартно работает с симметричным ключом в 40-бит, те пять букавак QWERT, например. А немерянной длины асиимметричный ключ лишь используется в промежуточном алгоритме Диффи — Хеллмана для выработки этого пятибуквенного ключика шифрования.
- Еще больше усугубляет ситуацию, так называемый антифишинг. Если бы его не было - его надо было бы придумать обязательно. Смысл его в том, что все-все-все ваши защищенные входы по SSL в интернет-банки и тд - тут же передаются через весь интернет в микрософт. Фактически, по всему интернету сразу же проходит оповещение, что некто собирается вот прямо сейчас куда-то (якобы тайно) войти с помощью SSL. Что же еше надо для таких нелепых методов защиты, как SSL, кроме оповещения всех-всех-всех, что вы его вот-вот сейчас собираетесь применить?
- Можно ли еще наделать в этой теме какие-нибудь еще бОльшие глупости в дополнение к перечисленным? Не знаю. Ну можно еще доверить написать эту программку в 19 машинных команд гребаному микрасофту, который сумеет допустить штук по десять ошибок в каждой строчке этой крошечной програмки: Microsoft SSL PCT vulnerability, Microsoft SSL Vulnerability, эксполит на этой ашипке.
Итак, мы обсудили, что SSL (а уж тем более ГОСТ'овская юниксовая SSL, нелепо прикрученная к виндузне) - ничего не добавляет к безопасности в интернете. И скорее даже убавляет, засчет рассылки всем-всем-все уведомлений что сейчас будет произведен ввод логина/пароля и последующей публичной трансляции зашифрованного текста - при полностью предсказуемом и заведомо известном зашифрованном тексте.
Для изучения вопросов безопасности нам надо асилить еще пару моментов. Первый из них - чрезвычайная легкость механизмов подбора паролей для входа на сайты. Ведь сайты висят в инете годами, предоставленные сами себе, даже перебирая пароли путем грубого перебора когда-нибудь да получится угадать. Тем более в интернете опубликовано множество баз с паролями, которые люди склонны выбирать.
Каков же механизм перебора паролей? Может он сложен? Ничуть - достаточно загрузить страничку в БраузерКонтрол и в цикле отсылать постбек. Такого кода полно в MSDN и даже моя самая первая прога на NET, когда я шесть лет назад только знакомился с NET - была именно по этой технологи.
А значит, написать такую прогу опять же может любой пятикласник за пару часов. Но я скажу даже больше. Средства для перебора паролей входят даже в состав VS2005. Даже напрягаться и копировать десять строчек кода из MSDN не надо. Надо лишь выполнить пару следующих тычков мышкой:
- Чтобы активировать Web Test Recorder нам придется создать Web Test project. (иначе рекордер в браузере будет неактивен). И добавить в него собственно Load Web test.
- Теперь рекордер активен и можно начинать записывать манипуляции с сайтом. Запись будет видна не только в браузере, но и собственно в студии.
- В дальнейшем записанный код мы можем модифицировать произвольным образом и повторить его любое количество раз. Запустив тест - мы получим залогиненную сессию в защищенный сайт. В VS2005 предусмотрена даже возможность подключения внешних баз с паролями. Как вы понимаете, логинится можно даже в сто потоков одновременно. Внизу этой странички лежит скринчик, сколько потоков на одноврменное логинирование выдерживает некий сайт на моем девелоперском кампе.
- Можно сгенерить непосредственно сборку на бейсике в исходном коде для автономного запуска попытки входа в сайт. Как видите, залогинивание происходит прекрасно и бессбойно. Поддается модификации, подключению базы паролей - и все это даже для таких лентяев, которым лень написать 10 строк кода и сформировать руками webrequest с заполненной коллекцией FORMS.
Бесполезно вести любые дальнейшие разговоры о безопасности в WEB, пока народ не осознает ЛЕГКОСТЬ входа на сайты ДАЖЕ БЕЗ НАПИСАНИЯ СВОИХ ПРОГ ИЛИ ДАЖЕ СКАЧИВАНИЯ ЧУЖИХ, а просто в пять тычков мышкой из VS2005.
Вот поэтому мне и взломали мыло на MAIL.RU раз пять за прошлый год. При этом тут пока рассмотрены самые азы, буквально для десятилетних детишек, в реальности все это гораздо крученее можно делать. Например можно свой адрес замаскировать, чтобы никаких следов входа не осталось. Несложно и самому свой собственный прокси-сервер написать и положить его на любом импортном хостинге (если нету доверия чужим проксям).
Итак, основные аспекты защиты, как мы видим - должны быть сосредоточены на двух участках - участие человека, (ДОКАЗАТЕЛЬСТВО участия человека в процессе постбека) и отсутсвие нажимаемых клавиш на клиентском кампе - только щелчки мышкой. Сама по себе защита от перехвата трафика НА ЛИНИИ (о чем так беспокоится ГОСТ, технология SSL и проч) - тоже имеет некоторое значение, но совершенно второстепенное.
Какую же технологию можно предложить для надежного доказательства того, что с другой стороны находится ЧЕЛОВЕК, а не бот, и для доказательства КАКОЙ ИМЕННО человек?
Я бы предложил следующую технологию. Для даказательства того, что с другой стороны находится человек надо выдать ему ИНДИВИДУАЛЬНЫЙ БИЛЕТ НА ВХОД (Handshake). Причем такой билет должен включать совершенно несложную для человека задачку, но абсолютно непосильную для кампутеров. Например КАПТЧУ с вопросом "сколько будет 2+2?". Человек должен вдолбить ответ - "4". Или какая буква находится на вашей клаве между "Щ" и "Х". Человек вбивает "З". Такие вопросы необременительны, но совершенно непосильны для кампутеров. Во всех предыдущих примерах проверка КАПЧИ, как вы понимаете - была отключена. В изготовлении такой цифровой капчи, как у меня - есть пара хитростей тоже - например ЗАСЕЧКИ, начисто сбивающие распознавалки. Но сейчас не о них.
Доказательство отправки постбека ЧЕЛОВЕКОМ - это лишь самый первых шаг защищенных приложений. Желательно отказаться от нажатия БУКВ на клавиатуре. Поэтому в самом продвинутом виде эта технология мне видится так, как на рисунке.
Разуеется и само слово для выбора цвета - в данном случае КРАСНЫЙ - можно ведь и с разными хитростями отобразить - как например я это делаю для цифр капчи. Как вы понимаете, местоположение красной, синей или прочей требуемой полоски каждый раз и для каждого юзера разное. Иначе смысл участия человека во вводе логина/пароля утрачивается.
Но как избавится собственно от ввода логина и пароля? Я вижу несколько вариантов.
Мне очень нравится вариант со стеганографией. Мы выдаем человеку РИСУНОК. Он может быть очень красивым - со всякими вензелями и замечательной надписью - "Право на вход в сайт GISIS.RU", например. А в этом рисунке стеганографическим способом зашит XML-файлик с цифровой подписью - в котором находятся логин для авторизации. В этом случае - юзер для залогинивания в файл просто производит аплоад на сервер имеющегося у него рисунка с логином. Естественно Аплоад сопровождается капчей, как доказательством, что залогинивание производит человек и возможно еще некоторыми выборами на форме, позволяющими расшифровать зашитый в рисунок авторизационный логин.
Согласитесь, что аплоад такого рисунка с флешки для этой формы - это совершенно ИНОЙ уровень защиты, чем убогое SSL, вообще не рассматривающее доказательства, что на противоположной стороне находится человек. И обычно предусматривающее тупое введение с клавиатуры текстовых логинов/паролей с клиентского кампа прямо в снифер клавиатуры. С точки зрения перехвата на линии - вы видите на линии передачу рисунка или мультимедийного файлика c песенкой. И что с того? Их проходит миллионы и мииллиарды - кто же узнает - какой именно из них приводит именно к залогиниванию на сайт? И как вы определите, что аплоад этого рисунка должен сопровождаться последовательными щелчками на зеленом-красном-черном-синем цвете, ведь на ползунке они все время меняются. Автоматическим образом - точно никак. Ибо воспроизвести сеанс не удастся хотя бы из-за того, что цвета на ползунке каждый раз разные.
Второй вариант - это виртуальные клавиатуры. Этот вариант мы посмотрим подробнее позже - он многое добавляет к защищенности приложения, но не все, что хотелось бы. А кое-что и убавляет, увы - например защищенность от DOS-уязвимостей.
И третий вариант - КЛИЕНТ входа. Те WIN-прога, которую можно просто тупо сгрузить с сайта и запустить на своей машине, и которая будет, шифруя трафик, передавать на сервер логин, пароль и капчу.
Эта технология мне представляется самой надежной. Тем более ее можно скомбинировать с загрузкой стеганографического рисунка c выданными человеку идентификационными данными. Так, как я это реализовал на данный момент - ожидание логина/пароля в зависимости от выданной капчи просиходит на адресе www.gisis.ru/DB56F136-6B03-4598-A6AA-6A933AF2B8F8, что идеально также в плане защищенности от DOS-атак. Здесь важно добиться, чтобы взлом этого клиента, его полная рефлексия в исходных код ровно ничего не давала - все элементы секретности должны быть вынесены в случайные (почти случайные!) числа, генерируемые в процессе обмена клиента входа и web-сервера.
Поясню этот момент подробнее. Получив капчу - никак невозможно по ней опеределить на каком адресе типа www.gisis.ru/DB56F136-6B03-4598-A6AA-6A933AF2B8F8 ожидается ввод логина/пароля - хоть сколько не изучай алгоритмы. А на другой адрес можешь отсылать логины/пароли - сколько хватит денег на оплату электроэнергии - ни к какому результату это не приведет.
И второе - капча является модификатором асимметричного ключа шифрования логина/пароля со стороны клиента.
Здесь мы пока проехали один важный вопрос - ведь выдаваемая капча может служить превосходными материалом DOS-атак. Однако, тут все зависит от реализации. Действительно, если мы хандлер создадим вот так, и будем сохранять капчу в Session - то это будет замечательный подарок всем, кто хочет свалить сайт массовыми запросами на капчу.
<%@ WebHandler Language="VB" Class="SetImage" %> Imports System Imports System.Web Public Class GetImage : Implements IHttpHandler, IRequiresSessionState Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
Поэтому я вижу здесь только один реальный вариант - выносить этот передовой рубеж защиты - с серверного менеджмента на клиентский. Иначе говоря - ложить на клиента куку, в которой ЗАШИФРОВАНА капча. Поэтому правильное решение будет таким. В этом решении много продвинутой специфики - например поддерживается сразу несколько экземпляров разной капчи на одной форме (скорее всего вам так сложно будет не нужно), но главное что надо понять тут - при использовании криптографии надо не забывать ЛОЧИТЬ от многопоточности статические классы с криптографией.
Теперь мы можем идти дальше - добиваться индивидуальности и неповторяемости каждого сеанса. К сожалению, гребаный MS тут как всегда ступил - и напрямую вынес ПОСТОЯННЫЙ ИДЕНТИФИКАТОР СЕАНСА в куку юзера. Я этой тупости понять ну никак не могу. Большей глупости просто представить себе невозможно. Это даже перекрывает глупость MS, Когда она ухитряется делать по 10 ошибок в 19 машинных строках кода или 1700 ошибок в простеньком текстовом редакторе с MDI-интерфесом. Это верхний предел тупости и очередное доказательство ПОЛНОЙ недееспособности MS.
Правильное решение было бы такое, как в моем хандлере - выкладывать на юзера РАНДОМНУЮ КУКУ, в которой закриптован тот самый SessionID, который они тупо отдают юзеру. И при каждом постбеке в модуле сопоставлять номер сеанса - вытаскивать его из рандомной куки и восстанавливать. Таким образом у всех юзеров были бы просто случайные куки, которые ни похищать ни прятать никакого смысла не было бы - это ж просто почти случайный пакет чисел. И только сервер, только правильный WEB-сервер знает, к какому именно сеансу и какого именно залогиненного юзера надо сопоставить эту якобы случайную клиентскую куку.
Эх, ну раз нет пределов глупости микрасофтовским программерам - придется жить в этой среде. Тогда наша задача - как-то сделать сеанс невоспроизводимым - те мы поверх микрасофтовской глупости попробуем что-нибудь надстроить уникальное.
К счастью, тут есть уже кое-что готовое. Правда, теперь уже только для залогиненного юзера. Анонимные сеансы мы можем индивидуализирвать и делать невоспроизводимыми только так, как я делал с капчей. На практике - просто даже положив капчу на MasterPage. Хотя, это самое важное - это индивидуализировать сеансы залогиненных юзеров. И тут нам поможет одна неплохая микрасофтавская приблудка - ViewStateUserKey.
Она вставляется в MasterPage - именно в INIT и сделает ваш браузерный сеанс залогиненного юзера уникальными. Попытка воспроизведения сеанса - даже на той же машине, но в соседней открытой браузерной сессии мгновенно свалит посторонний сеанс. Это произодет не только как в моем случае, когда я использую СОБСТВЕННУЮ аутентификацию, но даже при микрософтавской аутентификации, только в этом случае вы можете проверять проще - HttpContext.Current.User или System.Web.Security.MembershipUser.
Это в-общем то самые необходимые и базовые элементы защиты (доказательство участия человека в процессе постбека, защита от кейлоггеров, неповторяемость сессии, защиты Handshake от DOS-атак). И они дают вполне ощутимый эффект. Но не исключено, что я со временем дополню этот перечень.
Теперь неплохо было бы проиллюстрировать общие рассуждения о защите какой-нибудь конкретикой. Это могла бы быть ЛЮБАЯ тема, например тема аккуратного зашивания в рисунки логинов и паролей или тема снести крышу распознавалкам капчи, но мы, пожалуй остановимся на изготовлении виртуальных клавиатур.
Тема эта важная, ибо текстов опубликованных я пока еще не видел. И контрол это важный. Микрософт оснастил VS2005 множеством таких бесполезных контролов, что я не перестаю удивляться их глупости и бесполезности, а вот контрол виртуальной клавы - почему-то совершенно не включил в комплект студии. Ну, впрочем, как и контрол капчи, как и пейджер для DataList и все прочее-прочее-прочее...
Прежде всего клавиатуры обладают состоянием - те ЗАПОМИНАЮТ, они в русской раскладке или английской, по большим сейчас работают буквам или по малым. Это первой и главное проектное решение при изготовлении контролов клавиатур - КАК они будут поддерживать состояния. И как это состояние будет отображаться на ее фронтальной панели.
Мой выбор - для формы логинов - никак. Никак выбранное состояние нельзя определить по видимому рисунку. Рисунок неизменный для всех состояний. Это может показаться неудобным, но с точки зрения защиты - это лучший выбор. Другое дело, что в ИНЫХ обстоятельствах, я принимаю ИНЫЕ решения и у меня есть множество написанных мною клав, которые МЕНЯЮТ раскладку букв.
Итак, для начала нам надо равномерно разместить клавиши. Кажется, что для этого идеально подходит Excel. Но разместить так буквы РОВНО вряд-ли удасться. Поэтому заранее предусматривайте этап выравнивания букв в фотошопе.
Теперь сделаем самую первую внутренню обертку вокруг этого рисунка - класс, будет загружаться при старте приложения и будет содержать определения зоны каждой буквы.
00001: Imports Microsoft.VisualBasic 00002: 00003: Public Structure ButtonDefinitions 00004: Dim Klava1_Button As Char 00005: Dim X_Start As Integer 00006: Dim X_End As Integer 00007: Dim Y_Start As Integer 00008: Dim Y_End As Integer 00009: Dim IsSpecial As Boolean 00010: End Structure 00011: 00012: 'это полностью реентерабельный класс для мультипоточного режима 00013: Public Class Klava1_Buttons 00014: ''' <summary> 00015: ''' Большие букавки на клаве 00016: ''' </summary> 00017: Public ReadOnly Property H_Buttons() As System.Collections.Generic.List(Of ButtonDefinitions) 00018: Get 00019: H_Buttons = Buttons_H 00020: End Get 00021: End Property 00022: 00023: ''' <summary> 00024: ''' Маленькие букавки на клаве 00025: ''' </summary> 00026: Public ReadOnly Property s_Buttons() As System.Collections.Generic.List(Of ButtonDefinitions) 00027: Get 00028: s_Buttons = Buttons_S 00029: End Get 00030: End Property 00031: 00032: ''' <summary> 00033: ''' Символ перевода в маленький регистр букав 00034: ''' </summary> 00035: Public ReadOnly Property c_ToLowRegister() As ButtonDefinitions 00036: Get 00037: c_ToLowRegister = a_ToLowRegister 00038: End Get 00039: End Property 00040: 00041: ''' <summary> 00042: ''' Символ перевода в большой регистр букав 00043: ''' </summary> 00044: Public ReadOnly Property c_ToHighRegister() As ButtonDefinitions 00045: Get 00046: c_ToHighRegister = a_ToHighRegister 00047: End Get 00048: End Property 00049: 00050: ''' <summary> 00051: ''' Enter - завершение ввода (в регистре больших букв) 00052: ''' </summary> 00053: Public ReadOnly Property c_EnterH() As ButtonDefinitions 00054: Get 00055: c_EnterH = a_EnterH 00056: End Get 00057: End Property 00058: 00059: ''' <summary> 00060: ''' Enter - завершение ввода (в регистре маленьких букв) 00061: ''' </summary> 00062: Public ReadOnly Property c_EnterL() As ButtonDefinitions 00063: Get 00064: c_EnterL = a_EnterL 00065: End Get 00066: End Property 00067: 00068: Dim Buttons_H As New System.Collections.Generic.List(Of ButtonDefinitions) 'определения больших букав 00069: Dim Buttons_S As New System.Collections.Generic.List(Of ButtonDefinitions) 'определения маленьких букав 00070: Dim OneButtons As New ButtonDefinitions 00071: Dim a_ToLowRegister As ButtonDefinitions 00072: Dim a_ToHighRegister As ButtonDefinitions 00073: Dim a_EnterH As ButtonDefinitions 00074: Dim a_EnterL As ButtonDefinitions 00075: 00076: Public Sub New() 00077: ' 00078: Dim Row1_h() As Char = {"~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+"} 00079: Dim Row1_s() As Char = {"~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "+"} 00080: Dim Row1_x_sta() As Integer = {4, 44, 84, 124, 165, 205, 245, 285, 325, 365, 405, 445, 485} 00081: Dim Row1_x_end() As Integer = {42, 82, 122, 162, 202, 242, 282, 322, 362, 402, 442, 482, 522} 00082: Dim Row1_y_sta As Integer = 3 00083: Dim Row1_y_end As Integer = 35 00084: LoadButtonDefinition(Buttons_H, Row1_h, Row1_x_sta, Row1_x_end, Row1_y_sta, Row1_y_end, False) 00085: LoadButtonDefinition(Buttons_S, Row1_s, Row1_x_sta, Row1_x_end, Row1_y_sta, Row1_y_end, False) ..... 00189: End Sub 00190: 00191: Private Sub LoadButtonDefinition(ByVal ButtonsCollections As System.Collections.Generic.List(Of ButtonDefinitions), ByVal CharsArr() As Char, ByVal X_Start() As Integer, ByVal X_End() As Integer, ByVal Y_Start As Integer, ByVal Y_End As Integer, ByVal IsSpecial As Boolean) 00192: For i As Integer = 0 To CharsArr.Length - 1 00193: OneButtons.Klava1_Button = CharsArr(i) 00194: OneButtons.X_Start = X_Start(i) 00195: OneButtons.X_End = X_End(i) 00196: OneButtons.Y_Start = Y_Start 00197: OneButtons.Y_End = Y_End 00198: OneButtons.IsSpecial = IsSpecial 00199: ButtonsCollections.Add(OneButtons) 00200: Next 00201: End Sub 00202: 00203: End Class
Этот класс довольно занудный и я не буду публиковать его полностью. Важно понять, что должно получиться в итоге:
Как видите, это просто коллекции из 104 моих символом (вы можете сделать и иначе) - в большом и малом регистре. Я уже говорил, что у меня вид не меняется - хоть большой, хоть малый - я показываю рисунок один и тот же. Это элемент защиты. Вы можете выбрать другой компромис защита-удобство и показывать разные рисунки для раскладки по большим и маленьким буквам, русским и английским.
Кроме того, тут выделены некоторые символы, как специальные. Они приводят к некоторым действиям во внешних классах, которые пользуются этим определением - например переключают раскладку или завершают ввод.
Теперь рассмотрим более высокий по иерархии класс - который примет координаты X и Y и определит, какой именно был нажат символ. Поскольку у меня две коллекции символов - по большим и маленьким буквам - тут два метода предусмотрено - вернуть ли символ из коллекции больших букв (GetPressCharIn_Klava1_GIF_H) или из малых (GetPressCharIn_Klava1_GIF_s). Этот класс не может знать СОСТОЯНИЕ клавитуры - была ли когда-то раньше нажата Caps-Lock например. Он отдает только один текущий символ по координатам X и Y рисунка:
00001: ''' <summary> 00002: '''Это класс парсинга одного символа виртуальной клавиатуры с рисунка Klava1.GIF и определения посимвольных зон на ней в Application("Klava1_Buttons") 00003: '''Он нереентерабельный, ибо для парсинга создает коллекцию строк поэтому он нужен НОВЫЙ для каждого символа 00004: '''Он проверяет нажатые кнопки на спецсимволы и выставляет признаки соответсвующих спецсимволов 00005: '''Но осмысленные нажатые спецсимволы возвращет тоже, пустым нажатием считается переход с малого регистра в малый или с большого в большой 00006: '''Он работает по заданному ИЗВНЕ нажатому/отжатому CapsLock - по большим или маленьким букавам 00007: ''' </summary> 00008: Public Class OnePressChar 00009: 00010: Public IsEnter As Boolean = False 00011: Public IsToHigh As Boolean = False 00012: Public IsToLow As Boolean = False 00013: 00014: Public Enum Klava1Register 00015: Low = 1 00016: High = 2 00017: End Enum 00018: 00019: Dim Klava1_Buttons_Definition As Klava1_Buttons = HttpContext.Current.Application("Klava1_Buttons") 00020: Dim ButtonsRow As New System.Collections.Generic.List(Of ButtonDefinitions) 00021: 00022: 00023: Public Function GetChar(ByVal X As Integer, ByVal Y As Integer, ByVal CurrentRegister As Klava1Register) As Char 00024: If CurrentRegister = Klava1Register.Low Then 00025: Return GetPressCharIn_Klava1_GIF_s(X, Y) 00026: ElseIf CurrentRegister = Klava1Register.High Then 00027: Return GetPressCharIn_Klava1_GIF_H(X, Y) 00028: End If 00029: End Function 00030: 00031: Private Function GetPressCharIn_Klava1_GIF_H(ByVal X As Integer, ByVal Y As Integer) As Char 00032: Dim PressedChar As ButtonDefinitions 00033: For Each One As ButtonDefinitions In Klava1_Buttons_Definition.H_Buttons 00034: If Y >= One.Y_Start And Y < One.Y_End Then 00035: ButtonsRow.Add(One) 'отобрали все кнопки из нажатой строки 00036: End If 00037: Next 00038: For Each One As ButtonDefinitions In ButtonsRow 00039: If X >= One.X_Start And X < One.X_End Then 00040: PressedChar = One 'нашли нажатую кнопку 00041: Exit For 00042: End If 00043: Next 00044: ButtonsRow.Clear() 00045: If PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_ToLowRegister.Klava1_Button Then 00046: IsToLow = True 00047: GetPressCharIn_Klava1_GIF_H = PressedChar.Klava1_Button 00048: ElseIf PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_ToHighRegister.Klava1_Button Then 00049: IsToHigh = True 00050: 'в большом регистре тыкнули в переключение в большой - возвращать нечего 00051: ElseIf PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_EnterH.Klava1_Button Then 00052: IsEnter = True 00053: GetPressCharIn_Klava1_GIF_H = PressedChar.Klava1_Button 00054: Else 'в остальных случаях просто возвращаем символ, в которых тыкнули 00055: GetPressCharIn_Klava1_GIF_H = PressedChar.Klava1_Button 00056: End If 00057: End Function 00058: 00059: Private Function GetPressCharIn_Klava1_GIF_s(ByVal X As Integer, ByVal Y As Integer) As Char 00060: Dim PressedChar As ButtonDefinitions 00061: For Each One As ButtonDefinitions In Klava1_Buttons_Definition.s_Buttons 00062: If Y >= One.Y_Start And Y < One.Y_End Then 00063: ButtonsRow.Add(One) 'отобрали все кнопки из нажатой строки 00064: End If 00065: Next 00066: For Each One As ButtonDefinitions In ButtonsRow 00067: If X >= One.X_Start And X < One.X_End Then 00068: PressedChar = One 'нашли нажатую кнопку 00069: Exit For 00070: End If 00071: Next 00072: ButtonsRow.Clear() 00073: If PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_ToHighRegister.Klava1_Button Then 00074: IsToHigh = True 00075: GetPressCharIn_Klava1_GIF_s = PressedChar.Klava1_Button 00076: ElseIf PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_ToLowRegister.Klava1_Button Then 00077: IsToLow = True 00078: 'в маленьком регистре тыкнули в переключение в маленький - возвращать нечего 00079: ElseIf PressedChar.Klava1_Button = Klava1_Buttons_Definition.c_EnterL.Klava1_Button Then 00080: IsEnter = True 00081: GetPressCharIn_Klava1_GIF_s = PressedChar.Klava1_Button 00082: Else 'в остальных случаях просто возвращаем символ, в которых тыкнули 00083: GetPressCharIn_Klava1_GIF_s = PressedChar.Klava1_Button 00084: End If 00085: End Function 00086: End Class
Теперь рассмотрим более высокий класс, который ведет СОСТОЯНИЕ клавитуры. И помнит ВСЮ последовательность ранее нажатых символов, например символов перехода в маленький/большой регистр. Тут можно было бы сделать и переход в русскую/английскую раскладку и генерировать события на смену фейса клавиатуры. Но, как я говорил уже - постоянный фейс - это элемент защиты.
00001: ''' <summary> 00002: ''' Это внешний класс виртуальной клавиатуры с рисунка Klava1.GIF и определения посимвольных зон на ней в Application("Klava1_Buttons") 00003: ''' Он ведет в себе состояние нажой/отжатой CapsLock, отбрасывает служебные спецсимволы 00004: ''' И сохраняет в себе всю вводимую строку и по завершению ввода выставляет признак "ввод завершен" 00005: ''' Его экземпляр должен создаваться при начальной загрузке формы с клавой и сохраняться между постбеками в Session 00006: ''' </summary> 00007: Public Class VirtualString 00008: Dim CurrentRegister As OnePressChar.Klava1Register 00009: Dim IsEndEnter As Boolean 00010: Dim SummaryString As New StringBuilder 00011: Public ReadOnly Property IsEND() As Boolean 00012: Get 00013: IsEND = IsEndEnter 00014: End Get 00015: End Property 00016: 00017: Public ReadOnly Property StringSummary() As String 00018: Get 00019: StringSummary = SummaryString.ToString 00020: End Get 00021: End Property 00022: Public Sub New() 00023: CurrentRegister = OnePressChar.Klava1Register.Low 00024: End Sub 00025: Public Sub Add(ByVal X As Integer, ByVal Y As Integer) 00026: Dim ParsePressedButton As New OnePressChar 00027: 'по умолчанию на появившейся клаве включен регистр маленьких букавак 00028: 'в остальных случаях текущий регистр запомнен именно здесь 00029: Dim NewChar As Char = ParsePressedButton.GetChar(X, Y, CurrentRegister) 00030: If ParsePressedButton.IsToHigh Then 00031: CurrentRegister = OnePressChar.Klava1Register.High 00032: ElseIf ParsePressedButton.IsToLow Then 00033: CurrentRegister = OnePressChar.Klava1Register.Low 00034: ElseIf ParsePressedButton.IsEnter Then 00035: IsEndEnter = True 00036: Else 00037: 'в остальных случаях просто дополняем заданную стоку 00038: SummaryString.Append(NewChar) 00039: End If 00040: End Sub 00041: End Class
Теперь, когда у нас есть класс, обрабатывающий уже целую последовательность символов, который загружается первично в секции not Ispostback и к которому в секции IsPostback мы каждый раз обращаемся, передавая очередные нажатые на рисунке координаты X и Y - мы подошли к собственно ASP2-форме.
С таким замечательным - мощным и высокоуровневым классом - наша форма будет проста:
00001: <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Klava1.aspx.vb" Inherits="Klava1" %> 00002: 00003: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 00004: 00005: <html xmlns="http://www.w3.org/1999/xhtml" > 00006: <head runat="server"> 00007: <title>Виртуальная клавиатура GISIS.RU</title> 00008: </head> 00009: <body> 00010: <form id="form1" runat="server" > 00011: <div> 00012: <asp:ImageButton ImageUrl="~/Images/Klava1.gif" ID="Klava1" runat="server" /> 00013: </div> 00014: </form> 00015: </body> 00016: </html>
00001: Partial Class Klava1 00002: Inherits System.Web.UI.Page 00003: Dim VirtualTypeWriter As VirtualString 00004: 00005: Public Event VirtualEnter_Ended() 00006: 00007: Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 00008: If Not IsPostBack Then 00009: ClientScript.RegisterStartupScript(Me.GetType, "Resize", "window.resizeTo(600, 700);", True) 00010: Session("VirtualKeyboard_" & Request.QueryString("i")) = New VirtualString 00011: End If 00012: End Sub 00013: 00014: Protected Sub Klava1_Click(ByVal sender As Object, ByVal e As System.Web.UI.ImageClickEventArgs) Handles Klava1.Click 00015: If e.X > 0 And e.X > 0 Then 00016: Dim VirtualTypeWriter As VirtualString = Session("VirtualKeyboard_" & Request.QueryString("i")) 00017: VirtualTypeWriter.Add(e.X, e.Y) 00018: If VirtualTypeWriter.IsEND Then 00019: Response.Write("<script type='text/javascript'>" & vbCrLf & "<!--" & vbCrLf & "window.close();" & vbCrLf & "// -->" & vbCrLf & "</script>") 00020: RaiseEvent VirtualEnter_Ended() 00021: Response.End() 00022: End If 00023: If System.Configuration.ConfigurationManager.AppSettings("Klava1_ECHO") Then Response.Write(VirtualTypeWriter.StringSummary) 00024: End If 00025: End Sub 00026: End Class
Вызов этой формы с клавиатурой надо выполнять по линку в отдельном окне. Хотя я вообще-то предпочитаю другие варианты входов. Из трех состояний: вход закрыт, вход открыт, вход произошел. Все это входное хозяйство я размещаю на MasterPage в отдельном контроле. Когда нибудь я соберусь с силами и распишу как делать такие логинные контролы на множество состояний. Но а вызов этой конкретной описанной клавитатурной формы должен выглядеть так:
После вызова в соответствующей переменной Session - мы увидим введенную юзером строку логина.
Теперь давайте еще раз просуммируем - чего же мы добились заменой введения букв с клавиатуры клиентского кампа щелчками по виртуальной клавиатуре:
- Полной защиты от самой распространенной (можно даже сказать - учебной) категории вирусов - сниферов клавиатуры - кейлоггеров.
- Осложнили перехват на линии - обо теперь символы логина и пароля уже не ходят по сети голыми - а ходят лишь их координаты. Заметьте, что эта позиция верна лишь в случае, когда клавиатура не выдает ЭХО. Иначе символы логина и пароля опять будут гулять голыми по сети. Ну или с фиговым листком в виде SSL, что то же самое.
Чего нам не удалось добиться с помощью виртуальной клавиатуры? Полной защиты. Ибо рисунок постоянный от сессии к сессии. А координаты можно перехватить. И - прощай денежки.
Рандомная раскладка букв на клавиатуре могла бы несколько осложнить воспроизведение сеанса входа, особенно если клава лежит на MasterPage и в каждом постбеке меняется - но только если сеанс не записан полностью и с самого начала. Но рандомная клава снизит до нуля юзабилити - попробуйте отыскать на рандомной раскладке букву "А". Плюс рисунки с разными раскладками будут иметь РАЗНЫЕ размеры. И привести эти рисунки к единому размеру ой как непросто. К тому же если сеанс записан сначала и полностью - рандомная раскладка тоже не спасет. И здесь мы упираемся в ограниченность технологии виртуальных клавиатур, выйти из этого тупика на лучший уровень защиты поможет только свой собственный клиент входа, скрин которого я приводил выше.
И наконец, раз уж мы говорим на этой страничке о безопасности - я расскажу еще об одной своей технологии. Меня всегда удивляла MS своей тупостью - вместо того, чтобы сделать вместо фиксированного в пределах всей сессии
ASP.NET_SessionId = p2noaoz4qloptxibwqcj0lv1 и уж тем более вместо постоянного в пределах сессии
.ASPXAUTH = 9CD40BFFD97FB546A6E96A90651BF5FB710B8E5531CE36CD4EAF6C3BC25EE66F75639EB7E1D0C2A4F5500F2F0CF441CC73DD2B2BE9E8451B4E2FFD0FDAABC461
которые в голом виде вываливаются на клиента - чисто РАНДОМЫЕ значения - новые для каждого реквеста, в которых ЗАШИФРОВАНЫ РЕАЛЬНЫЕ номера SESSION_ID, которые возможно восстановить лишь на сервере при каждом постбеке. Вместо этого простого действия MS выпускает ГОРУ книг по защите сайтов и каждая книга начинается с рассказов об ужасах воспроизведения сессий на другом кампе путем копирования злоумышленником этих ASP.NET_SessionID и .ASPXAUTH из куков сессии легального юзера и воспроизведения текущей легальной залогиненной сессии на другом кампе с помощью вот этих двух украденных куков.
Так же точно - мне непонятно - как можно было в MS ниасилить такую простую идею - загрузили сайт на хостинг в IIS. На отдельной вкладке прощитали контрольную сумму и защелкнули ею изменения в сайте. И до перезагрузки следующей версии - IIS должен проверяться на то, что сайт по-прежнему сохранен в неизменном виде - с контрольной суммой, установленной при загрузке версии.
Эту простейшую прогу я и опишу как-нибудь позже - так и оставшись в недоумении - зачем было писать столько книг о необходимости защиты сайтов от вирусов и взломов - вместо такой простой проги, которая могла быть стандартно доступной на одной из вкладок IIS.
А пока взгляните на этот скринчик - в нем видна вся цена стандартной SSL-криптографии (именно по SSL/HTTPS работает GMAIL)- "сводка представляет собой просмотр письма" - SSL-переписка была прочитана и приложена к материалам дела.
|