(ASP.NET) ASP.NET (2008 год)

Безопасность Web-приложений

Построению защищенных Web-приложений препятствует несколько укоренившихся заблуждений. Самое дикое и нелепое заблуджение в этой области - что криптография и ее применение в WEB (например в виде SSL-коннектов) как-то усиливает защищенность Web-приложений. Очень много и совершенно бесполезных книг, которые вообще уводят читателя в сторону, запутывая его. Обсуждая например переполнение буфера, начисто отсутствующее в NET. Или например пережевывающие опасность SQL-injection или JavaScript-injection, которые автоматически фильтруются средой ASP2. И чтобы отключить эту фильтрацию, надо еще потрудится. Одной из таких вредных и бесполезных книг является вот эта.

А вот слева вы видите две лучшие книги по безопасности. Но между этими темами - пропасть. Ибо, сама по себе и криптография и SSL (чему посвящена правая книга) - НИКАК не влияет на безопасность. И правая книга хорошо поможет вам разобраться в неких конкретных деталях базовых классов шифрования, например если вы не понимаете режимов паддинга шифруемого потока до границы блока при симметричном шифровании или режимы приложения цифровой подписи к XML-файлу. И не более. Я бы сказал, что она имеет несколько теоретический характер. Если вы хотите узнать что-то более практичное, например описание конкретной технологии WSE3 - то эта книга вам уже не поможет. Отличное описание WSE3 вы найдете здесь. (Ой, ну только не читайте ГЛУПЫХ и вредных описаний WSE3 здесь). Однако вернемся к классическим ASP2-приложениям с пользовательским интерфейсом.

Левая книга - это лучшее, что я вообще видел на тему безопасности в ASP2, она широка по охваченным темам и глубока в каждой теме. Она содержит много полезного даже на тему защиты WEB-сервера, когда конкретное Web-приложение УЖЕ взломано, например CAS-разрешения на сборки ASP2-приложений (частичное доверие к сборкам ASP2-приложений). Это безусловно ценая информация, однако хотелось бы чтобы атаки не заходили так далеко. Поэтому тут мы рассмотрим более ранние и практически полезные методы защиты ASP2-приложений, вовсе упущенные в этой книге.


Прежде всего, давайте рассмотрим, почему именно SSL-коннект никак не влияет на безопасность приложений - а, как мы увидим дальше, даже ее ухудшает:



Итак, мы обсудили, что SSL (а уж тем более ГОСТ'овская юниксовая SSL, нелепо прикрученная к виндузне) - ничего не добавляет к безопасности в интернете. И скорее даже убавляет, засчет рассылки всем-всем-все уведомлений что сейчас будет произведен ввод логина/пароля и последующей публичной трансляции зашифрованного текста - при полностью предсказуемом и заведомо известном зашифрованном тексте.


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

Каков же механизм перебора паролей? Может он сложен? Ничуть - достаточно загрузить страничку в БраузерКонтрол и в цикле отсылать постбек. Такого кода полно в MSDN и даже моя самая первая прога на NET, когда я шесть лет назад только знакомился с NET - была именно по этой технологи.

А значит, написать такую прогу опять же может любой пятикласник за пару часов. Но я скажу даже больше. Средства для перебора паролей входят даже в состав VS2005. Даже напрягаться и копировать десять строчек кода из MSDN не надо. Надо лишь выполнить пару следующих тычков мышкой:

Бесполезно вести любые дальнейшие разговоры о безопасности в 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 в отдельном контроле. Когда нибудь я соберусь с силами и распишу как делать такие логинные контролы на множество состояний. Но а вызов этой конкретной описанной клавитатурной формы должен выглядеть так:

<asp:HyperLink ID="Klava1Link1" Target=_blank CssClass="Main45" runat="server">Логин</asp:HyperLink></td>
Dim LoginParmGuid As String = Guid.NewGuid.ToString
Session("LoginParmGuid") = LoginParmGuid
Klava1Link1.NavigateUrl = MyConfig.SecureBaseURL & "Klava1.aspx?i=" & LoginParmGuid

После вызова в соответствующей переменной Session - мы увидим введенную юзером строку логина.


Теперь давайте еще раз просуммируем - чего же мы добились заменой введения букв с клавиатуры клиентского кампа щелчками по виртуальной клавиатуре:


Чего нам не удалось добиться с помощью виртуальной клавиатуры? Полной защиты. Ибо рисунок постоянный от сессии к сессии. А координаты можно перехватить. И - прощай денежки.

Рандомная раскладка букв на клавиатуре могла бы несколько осложнить воспроизведение сеанса входа, особенно если клава лежит на MasterPage и в каждом постбеке меняется - но только если сеанс не записан полностью и с самого начала. Но рандомная клава снизит до нуля юзабилити - попробуйте отыскать на рандомной раскладке букву "А". Плюс рисунки с разными раскладками будут иметь РАЗНЫЕ размеры. И привести эти рисунки к единому размеру ой как непросто. К тому же если сеанс записан сначала и полностью - рандомная раскладка тоже не спасет. И здесь мы упираемся в ограниченность технологии виртуальных клавиатур, выйти из этого тупика на лучший уровень защиты поможет только свой собственный клиент входа, скрин которого я приводил выше.





И наконец, раз уж мы говорим на этой страничке о безопасности - я расскажу еще об одной своей технологии. Меня всегда удивляла MS своей тупостью - вместо того, чтобы сделать вместо фиксированного в пределах всей сессии ASP.NET_SessionId = p2noaoz4qloptxibwqcj0lv1 и уж тем более вместо постоянного в пределах сессии
.ASPXAUTH = 9CD40BFFD97FB546A6E96A90651BF5FB710B8E5531CE36CD4EAF6C3BC25EE66F75639EB7E1D0C2A4F5500F2F0CF441CC73DD2B2BE9E8451B4E2FFD0FDAABC461
которые в голом виде вываливаются на клиента - чисто РАНДОМЫЕ значения - новые для каждого реквеста, в которых ЗАШИФРОВАНЫ РЕАЛЬНЫЕ номера SESSION_ID, которые возможно восстановить лишь на сервере при каждом постбеке. Вместо этого простого действия MS выпускает ГОРУ книг по защите сайтов и каждая книга начинается с рассказов об ужасах воспроизведения сессий на другом кампе путем копирования злоумышленником этих ASP.NET_SessionID и .ASPXAUTH из куков сессии легального юзера и воспроизведения текущей легальной залогиненной сессии на другом кампе с помощью вот этих двух украденных куков.

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

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


А пока взгляните на этот скринчик - в нем видна вся цена стандартной SSL-криптографии (именно по SSL/HTTPS работает GMAIL)- "сводка представляет собой просмотр письма" - SSL-переписка была прочитана и приложена к материалам дела.



Комментарии к этой страничке ( )
ссылка на эту страничку: http://www.vb-net.ru/asp2/32/index.htm
<На главную>  <В раздел ASP>  <В раздел NET>  <В раздел SQL>  <В раздел Разное>  <Написать автору>  < Поблагодарить>
Московская хельсинская группа   Радио Свобода  Новая газета   The New Times (Новое время)