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

Защита параметров странички от подделки

На этой страничке я расскажу, как защитить свою страничку от взлома. Таких методов много. Самый лучший из них - написание своего HttpModule, который будет делать URL Rewriting котором все ссылки проекта станут просто бессмысленными - типа http://MyDomain/GUID.

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


http://SH.RU/MyPage.aspx?id1=2140F86D35B6F403C2C26848ACA5521F&id2=4017564F3C13ABFA4EBD260F57067392

В данном случае две ссылки, приведенные выше, скрывают всего-навсего символы 1 и 2, которые мне бы очень не хотелось показывать юзеру в открытом виде http://SH.RU/MyPage.aspx?id1=1&id2=2 и уж тем более не хотелось бы, чтоб он их подменил. Номера эти всегда получаются разные и при малейшей попытке подделать параметры странички, хитрец сразу же редиректится на страничку с предупреждением или логином.

Защита инициализируется четырьмя строчками в GLOBAL.ASAX:

        Application("ParmProtector8") = New ParmProtector8
        CType(Application("ParmProtector8"), ParmProtector8).RedirectUrl = "AccessDenied.aspx"
        CType(Application("ParmProtector8"), ParmProtector8).ConfigKey = "CryptURLParm"
        CType(Application("ParmProtector8"), ParmProtector8).SpecialChar = "*"
и в любой момент может быть выключена из WEB-Config и
        <add key="CryptURLParm" value="True" />
Собственно защита параметра происходит при вызове метода Mask:
        Dim id1 As String="1", id2 As String="2"
        Dim Tag As String = "<a href='MyPage.aspx?id1=" & _
        CType(Application("ParmProtector8"), ParmProtector8).Mask(id1) & _
        "&id2=" & CType(Application("ParmProtector8"), ParmProtector8).Mask(id2) & "'>.......</a>"
Соответственно восстановление параметра происходит при вызове метода UnMask:
        id1 = CType(Application("ParmProtector8"), ParmProtector8).UnMask(Request.QueryString("id1"))

Последний параметр в инициализации - SpecialChar, является уникальным модификатором алгоритма, позволяющим внести в него индивидуальность. На тот случай, если взломщик даже точно знает, что для защиты ключей использован этот мой модуль и взломщик УЖЕ имеет полный текущий ASP2-код этой защиты, минимум, что ему придется сделать для подбора защищенного ключа - это написать свое собственное приложение, имитирующую мой алгоритм, но при этом ему все равно взламывать сайт придется только путем полного перебора модификаторов. Однако для критических приложений важно зафиксировать факт атаки. И что мешает произвести более жесткую реацию на попытку взлома после нескольких подряд попаданий на страничку AccessDenied?

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

Кроме того, как вы видите, в строке 58 я уложил в созданный криптографический идентификатор и SessionID, однако не использовал его в строке 80 при проверке подлинности полученного идентификатора. Это сделать можно, но такое ужесточение этого алгоритма может привести к проблемам, когда одна форма порождает множество ASP2-форм. Можно ввести параметр "более или менее жесткая" проверка подлинности полученного криптографического идентификатора. Впрочем расширять это алгоритм возможно в любую сторону до бесконечности.

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

00001: Public Class ParmProtector8
00002:     Dim DES As New System.Security.Cryptography.DESCryptoServiceProvider
00003:     Dim _Delimiter As Char = "*"
00004:     Dim _RedirectURL As String
00005:     Dim _ConfigKey As String
00006: 
00007:     Public Sub New()
00008:         DES.GenerateKey()
00009:     End Sub
00010: 
00011:     ''' <summary>
00012:     ''' Модификатор алгоритма (один спецсимвол)
00013:     ''' </summary>
00014:     Public Property SpecialChar() As char
00015:         Get
00016:             SpecialChar = _Delimiter
00017:         End Get
00018:         Set(ByVal value As char)
00019:             _Delimiter = value
00020:         End Set
00021:     End Property
00022: 
00023:     ''' <summary>
00024:     ''' URL станички для редиректа при попытке взлома параметра
00025:     ''' </summary>
00026:     Public Property RedirectUrl() As String
00027:         Get
00028:             RedirectUrl = _RedirectURL
00029:         End Get
00030:         Set(ByVal value As String)
00031:             _RedirectURL = value
00032:         End Set
00033:     End Property
00034: 
00035:     ''' <summary>
00036:     ''' имя параметра конфига TRUE/FALSE - надо ли шифровать
00037:     ''' </summary>
00038:     Public Property ConfigKey() As String
00039:         Get
00040:             ConfigKey = _ConfigKey
00041:         End Get
00042:         Set(ByVal value As String)
00043:             _ConfigKey = value
00044:         End Set
00045:     End Property
00046: 
00047:     ''' <summary>
00048:     ''' Параметр для шифрования - не более восьми символов длиной ()
00049:     ''' </summary>
00050:     Public Function Mask(ByVal RawParm As String) As String
00051:         If Not CBool(System.Configuration.ConfigurationManager.AppSettings(_ConfigKey)) Then Return RawParm
00052:         If RawParm.Length > 8 Then Throw New Exception("Шифрование параметров длины более 8 символов не поддерживается." & vbCrLf & RawParm)
00053:         Dim [In] As New System.Text.StringBuilder
00054:         [In].Append(Now.Ticks.ToString.Substring(10, 2).Replace("0", "#"))
00055:         [In].Append(_Delimiter)
00056:         [In].Append(RawParm)
00057:         [In].Append(_Delimiter)
00058:         [In].Append(System.Web.HttpContext.Current.Session.SessionID.ToString)
00059:         [In].Length = 12  'эта длина соотвествует буферу для расшифровки в 16 байт.
00060:         Dim CryptBytes As Byte() = Encrypt([In].ToString)
00061:         Dim [Out] As New System.Text.StringBuilder
00062:         For Each One As Byte In CryptBytes
00063:             [Out].Append(One.ToString("X2"))
00064:         Next
00065:         Return [Out].ToString
00066:     End Function
00067: 
00068:     ''' <summary>
00069:     ''' Расшифровка параметра с редиректом на страницу ошибок. 
00070:     ''' </summary>
00071:     Public Function UnMask(ByVal CryptParm As String) As String
00072:         If Not CBool(System.Configuration.ConfigurationManager.AppSettings(_ConfigKey)) Then Return CryptParm
00073:         If CryptParm.Length <> 32 Then System.Web.HttpContext.Current.Response.Redirect(_RedirectURL)
00074:         Dim Buf(15) As Byte 'буфер для расшифровки
00075:         For Z As Integer = 1 To CryptParm.Length - 1 Step 2
00076:             Dim b As Byte = Byte.Parse(Mid(CryptParm, Z, 2), Globalization.NumberStyles.HexNumber)
00077:             Buf((Z - 1) / 2) = b
00078:         Next
00079:         Dim Out As String = Decrypt(Buf)
00080:         If Out.IndexOf(_Delimiter) = 2 Then
00081:             Dim EndParmPos As Integer = Out.IndexOf(_Delimiter, 3)
00082:             If EndParmPos > 0 Then
00083:                 Return Out.Substring(3, EndParmPos - 3)
00084:             Else
00085:                 System.Web.HttpContext.Current.Response.Redirect(_RedirectURL)
00086:             End If
00087:         Else
00088:             System.Web.HttpContext.Current.Response.Redirect(_RedirectURL)
00089:         End If
00090:     End Function
00091: 
00092:     'Encrypt the string to array of bytes
00093:     Private Function Encrypt(ByVal PlainText As String) As Byte()
00094:         Dim ms As New System.IO.MemoryStream()
00095:         Dim encStream As New System.Security.Cryptography.CryptoStream(ms, DES.CreateEncryptor(), System.Security.Cryptography.CryptoStreamMode.Write)
00096:         Dim sw As New System.IO.StreamWriter(encStream)
00097:         sw.WriteLine(PlainText)
00098:         sw.Close()
00099:         encStream.Close()
00100:         Dim buffer As Byte() = ms.ToArray()
00101:         ms.Close()
00102:         Return buffer
00103:     End Function
00104: 
00105: 
00106:     'Decrypt the byte array to string
00107:     Private Function Decrypt(ByVal CypherText() As Byte) As String
00108:         Dim ms As New System.IO.MemoryStream(CypherText)
00109:         Dim encStream As New System.Security.Cryptography.CryptoStream(ms, DES.CreateDecryptor(), System.Security.Cryptography.CryptoStreamMode.Read)
00110:         Dim sr As New System.IO.StreamReader(encStream)
00111:         Dim val As String
00112:         Try
00113:             val = sr.ReadLine()
00114:         Catch ex As System.Security.Cryptography.CryptographicException
00115:             System.Web.HttpContext.Current.Response.Redirect(_RedirectURL)
00116:         End Try
00117:         sr.Close()
00118:         encStream.Close()
00119:         ms.Close()
00120:         Return val
00121:     End Function
00122: 
00123: End Class

Сгрузить этот класс в готовом виде и сразу же использовать его в своих проектах - вы можете отсюда.




Просуществовав в таком виде два года, впоследствии я подверг этот код нижеследующей доработке. Которая нужна в том случае, когда странички с защифрованными линками УКЛАДЫВАЮТСЯ В БАЗУ.




Естественно при рестарте приложения достать странички из базы и расшифровать их - будет невозможно. Поэтому класс пришлось дооснастить вот такими методами:

00001:     ''' <summary>
00002:     ''' При рестарте приложения ключи будут новые и все старое расшифровать не удастся
00003:     ''' Этот метод позволяет сохранить ключи в базу. Методу надо отдать имя строки коннекта к базе
00004:     ''' CREATE TABLE [dbo].[ParmProtector8](
00005:     ''' [i] [int] IDENTITY(1,1) NOT NULL,
00006:     ''' [key] [binary](8) NOT NULL,
00007:     ''' [IV] [binary](8) NOT NULL,
00008:     '''CONSTRAINT [PK_ParmProtector8] PRIMARY KEY CLUSTERED 
00009:     ''' ( [i] ASC ) ON [PRIMARY]
00010:     ''' ) ON [PRIMARY]
00011:     ''' </summary>
00012:     Public Function SaveKeyToSQL(ByVal ConnectionStringName As String) As Integer
00013:         Dim CN As New System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings(ConnectionStringName).ConnectionString)
00014:         Dim CMD As New System.Data.SqlClient.SqlCommand("INSERT [ParmProtector8]([key],[IV],[Date]) VALUES (@Key,@IV, GetDate()); select scope_identity() as [i]")
00015:         CMD.CommandType = Data.CommandType.Text
00016:         CMD.Parameters.Add("Key", Data.SqlDbType.Binary, 8)
00017:         CMD.Parameters.Add("IV", Data.SqlDbType.Binary, 8)
00018:         CN.Open()
00019:         CMD.Connection = CN
00020:         CMD.Parameters("Key").Value = DES.Key
00021:         CMD.Parameters("IV").Value = DES.IV
00022:         Dim RDR = CMD.ExecuteReader(System.Data.CommandBehavior.SingleRow)
00023:         If RDR.Read Then
00024:             Restart_DB_ID = RDR("I")
00025:         End If
00026:         RDR.Close()
00027:         CN.Close()
00028:         CMD = Nothing
00029:     End Function
00030: 
00031:     ''' <summary>
00032:     ''' Метод инициализирует класс шифрования. Принимает на вход номер записи в базе с ключами и имя строки коннекта к базе
00033:     ''' </summary>
00034:     Public Sub LoadKeyFromSQL(ByVal ConnectionStringName As String, ByVal DB_ID As Integer)
00035:         Restart_DB_ID = DB_ID
00036:         Dim CN As New System.Data.SqlClient.SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings(ConnectionStringName).ConnectionString)
00037:         Dim CMD As New System.Data.SqlClient.SqlCommand("SELECT [key],[IV] From [ParmProtector8] where i=" & DB_ID.ToString)
00038:         CMD.CommandType = Data.CommandType.Text
00039:         CN.Open()
00040:         CMD.Connection = CN
00041:         Dim RDR = CMD.ExecuteReader(System.Data.CommandBehavior.SingleRow)
00042:         If RDR.Read Then
00043:             DES.Key = RDR("key")
00044:             DES.IV = RDR("IV")
00045:         End If
00046:         RDR.Close()
00047:         CN.Close()
00048:         CMD = Nothing
00049:     End Sub
00050: 
00051:     Private Restart_DB_ID 'номер записи в базе, куда сохранен ключ и IV симметричного шифрования (защита от рестарта)
00052:     Public ReadOnly Property DB_ID() As Integer
00053:         Get
00054:             Return Restart_DB_ID
00055:         End Get
00056:     End Property

И номер сеансового ключа в базе ДОБАВЛЯТЬ к параметру. Это, кстати, позволит корректно обрабатывать запросы из референсов, сохраненных в клиентских браузерах.

Теперь, соответственно изменится и процедура инициализации этого класса - создавая каждый раз новый "сеансовый" ключ (до следующего авторестарта приложения) и инициализируя им наш класс шифрования:




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

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




Этот класс можно переделать из статического - если производительность этого моего весьма экономного решения будет сдерживать работу вашего приложения. Дальнейший путь совершенствования этого класса - докручивание в нем асинхронности. И если первая задача является чисто технической - вторая (в хандлере!) - представляет собой настоящую игру извращенного ума!

Желаю удачи!



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