ASP.NET(VB)でトランザクショントークンを実装する

struts

Strutsで標準装備されているアレを実装したいっていうお話です。


トランザクショントークンとは?

・表示する画面の中にhiddenでランダムなID(トークン)を埋め込んでおき、そのトークンをサーバー側でもセッション内に保持しておく。
・その画面でサブミットされると、hiddenに埋められていたトークンがリクエストに入れられてサーバーに届く。
・サーバーでは、届いたhiddenのトークンとセッション内に保持していたトークンを比較する。一致していれば正しい遷移と判断する。
・別の画面のhiddenには別のトークンが埋められている為、意図しない画面から来た場合はセッション内のトークンとは一致しないので 誤った遷移であると判断できる。
・比較直後にセッション内からトークンを削除しておけば、重複サブミットの二度目の処理を防止できる。
・つまり、連続してサブミットボタンが押されたような場合には同一のトークンが再び届くが、セッションのトークンは既に存在しないので一致しない。すなわち不正なサブミットだと判断できる。

用途としては、

  • 重複サブミット抑止
  • 1クライアントからの複数のブラウザ(タブブラウザ)からのリクエストによるセッション共有抑止
  • CSRF対策

などです。(※必ずしも最適解ではないですが。。)

実際には、全てのページを対象とするか否か?(更新系だけとか)、Ajaxの非同期リクエストやモーダルウィンドウからのリクエストの考慮などが必要です。

ポイント

  • トークンの生成方法はStruts準拠です。
    • 乱数発生は行いません。
    • セッションIDとタイムスタンプからMD5ハッシュを生成します。
  • hiddenの直接操作は行わずViewStateを使います。
    • セキュリティレベルはViewStateレベルということになります。

実装例

Public Class TransactionToken
    Private Const TOKEN_NAME As String = "transaction-token"

    Private Shared lock As New Object
    Private Shared previous As Long

    Private Shared Function GenerateTocken(ByVal id As String) As String
        Dim current As Long = Now.Ticks
        If current = previous Then current += 1
        previous = current
        Dim data As Byte() = System.Text.Encoding.Default.GetBytes(id & current.ToString)
        Dim md5csp As New System.Security.Cryptography.MD5CryptoServiceProvider()
        data = md5csp.ComputeHash(data)
        Return Bytes2Hex(data)
    End Function

    Private Shared Function Bytes2Hex(ByVal bytes() As Byte) As String
        Dim result As New System.Text.StringBuilder
        For Each b As Byte In bytes
            result.Append(b.ToString("x2"))
        Next
        Return result.ToString
    End Function

    Public Shared Sub SaveToken(ByVal v As StateBag, ByVal s As HttpSessionState)
        SyncLock lock
            Dim token As String = GenerateTocken(s.SessionID)
            v.Add(TOKEN_NAME, token)
            s.Remove(TOKEN_NAME)
            s.Add(TOKEN_NAME, token)
        End SyncLock
    End Sub

    Public Shared Function IsValidToken(ByVal v As StateBag, ByVal s As HttpSessionState) As Boolean
        If v(TOKEN_NAME) Is Nothing OrElse s(TOKEN_NAME) Is Nothing Then Return True
        Return s(TOKEN_NAME).Equals(v(TOKEN_NAME))
    End Function
End Class

使用例

例えば、全ページの共通基底クラス PageBase を準備しているとします。
この場合、PreRenderとLoadにSaveTokenとIsValidTokenを組み込むと全ページに適用できます。

Imports System.Data

Public Class PageBase
    Inherits System.Web.UI.Page

    Private Sub Page_PreRender(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.PreRender
        TransactionToken.SaveToken(Me.ViewState, Me.Session)
    End Sub

    Private Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        If Not TransactionToken.IsValidToken(Me.ViewState, Me.Session) Then Me.Server.Transfer("/IllegalOperation.html")
    End Sub
End Class


Work! Enjoy it!