クッキー改ざんアタックも簡単なiRuleでブロックできます。この例ではユーザへ返すレスポンスを自由自在に処理するiRule独自の能力を利用します。

課題
HTTPにおけるアクセスは複数のTCPセッションを利用するケースがほとんどのため、Webサーバがアクセスしているユーザを認識するためには、クッキーが重要です。Webサーバがクライアントにクッキーを渡します。新しいTCPセッションで接続した場合、クライアントのブラウザクッキーをそのまま返す仕様です。しかし、クッキー自体はクライアントPCに保存されるファイルとなりますので、悪意のハッカーなどがクッキーの内容を変えることによって、本来のアクセス権限以外のコンテンツにアクセスしてきます。

例えば、図1のケースでは、クッキーに含まれたID番号を変えてウェブサーバへ返すケースを示します。

図1.クッキー改ざん



図中の「ウェブ」を「Web」へ変更

クッキーの改ざん対策が取られていないWebサーバであれば、ID番号の変更で他のユーザのログイン・ページへのアクセスが可能になるかもしれません。

iRuleでの対策
この問題はよく知られているため、多くのサーバやアプリケーション・ソフトウエアが対策を実施済みですが、クッキー・コンテンツ自体が人間に理解できないものの場合、改ざんがより難しくなります。
クッキー内容を暗号化することにより、アタッカーはどこの部分を改ざんすれば有効的なアタックを起こせるかが分からなくなります。このような対策はiRuleを用いて簡単に実現できます。対策のイメージは以下の通りになります:

図2.クッキー改ざんの対策



図中の「ウェブ」を「Web」へ変更


この対策では、プレーンテキストとして出力されたクッキーをBIG-IPが暗号化しクライアントへ送ります。もちろん、BIG-IPはクライアントから渡されたクッキーを複合化しWebサーバへ渡します。暗号化には固定鍵を利用するため、クライアントがクッキーをそのまま返せば、Webサーバが出したクッキーと同じものに復号されます。クッキー改ざんが行われた場合、ウェブサーバが認識できない内容になり、サーバはエラーを出力します。

実際のiRuleは以下の通りになります。

 when RULE_INIT {
set ::key [AES::key]
}
when HTTP_RESPONSE {
if { [HTTP::cookie exists "MyCookie"] } {
set decrypted [HTTP::cookie "MyCookie"]
HTTP::cookie remove "MyCookie"
set encrypted [b64encode [AES::encrypt $::key $decrypted]]
HTTP::cookie insert name "MyCookie" value $encrypted
}
}
when HTTP_REQUEST {
if { [HTTP::cookie exists "MyCookie"] } {
set encrypted [HTTP::cookie "MyCookie"]
HTTP::cookie remove "MyCookie"
set decrypted [AES::decrypt $::key [b64decode $encrypted]]
HTTP::cookie insert name "MyCookie" value $decrypted
}
}


このiRuleは3つのイベントを利用します。

RULE_INITイベント
まず最初に固定鍵を作成する必要があります。iRuleの初期化イベントであるRULE_INITにてグローバル変数として設定します。RULE_INITで設定すれば、BIG-IPを再起動やiRule内容を変更しない限り、この鍵は変わりません。

このイベントで利用したコマンドは2つです。

 set <変数名> <値>
AES::key [128 | 192 | 256]


グローバル変数の場合、変数名の前にコロンを2回つけます(::)。AES::keyはAES方式の鍵を生成するコマンドで、鍵長も任意に指定できます。指定しない場合はデフォルトで128ビットを利用します。

set ::key [AES::key]


よって上記のコマンドは::keyというグローバル変数に128ビットのAES方式の鍵が作成され保存されます。

HTTP_RESPONSEイベント
サーバからのレスポンスがトリガーとなるHTTP_RESPONSEイベントでは、クッキーを暗号化します。まず、当サーバのすべてのURIはクッキーを設定するものではない可能性がありますので、ifコマンドによりクッキーが存在するか否かを確認します。

if { [HTTP::cookie exists "MyCookie"] } {


利用するのは、HTTP::cookie existsのコマンドです。

HTTP::cookie exists “クッキー名”


クッキーが存在する場合、TRUEが返されます。次、クッキーが存在する場合、クッキーの内容を変数で保存してからプレーンテキストのクッキーを削除します。下記の2つのコマンドを利用します。

 set decrypted [HTTP::cookie "MyCookie"]
HTTP::cookie remove "MyCookie"


ここでは、HTTP::cookieコマンドの2種類が利用されます。「HTTP::cookie “クッキー名”」のコマンドは指定したクッキーの内容を返します。また「HTTP::cookie remove “クッキー名”」のコマンドは指定したクッキーを削除します。

最後に、下記の2つのコマンドでクッキーをAESキーで暗号化し、再度HTTPデータに追加します。

 set encrypted [b64encode [AES::encrypt $::key $decrypted]]
HTTP::cookie insert name "MyCookie" value $encrypted


まず、「AES::encrypt <キー> <データ>」というコマンドで保存したクッキーの内容をRULE_INITで作成したキーにおいて暗号化します。その際、暗号化されたデータにASCII以外の文字が含まれる可能性もありますので次にBase 64でエンコードを行います。そのコマンドは「b64encode <データ>」です。

上記の暗号化されたデータをencryptedという変数に保存し、次に「HTTP::cookie insert name “クッキー名” value <データ>」というコマンドでそのデータを削除したクッキーと同じクッキー名でHTTPデータに追加します。クライアントやサーバから見ると、クッキーが一旦削除、追加されることは一切わかりません。

ちなみにリソースを最小限するには、この4行を1行にまとめることもできます。

HTTP::cookie insert name "MyCookie" value [encrypted [b64encode [AES::encrypt $::key [HTTP::cookie “MyCookie”]]]]


HTTP_REQUESTイベント
クライアントからのリクエストに対する処理はHTTP_REQUESTイベントで実施します。既にサーバからクッキーをもらったクライアントは同じクッキーをHTTPリクエストに入れます。このクッキーは上記のRESPONSEイベントで暗号化されたので、ここで復号化してサーバへプレーンテキストとして渡します。

RESPONSEと同様にまずクッキーが存在するか否かを確認します。最初のリクエストにはクッキーが含まれていない場合が多いからです。

次の2行ではRESPONSEのときと同じようなプロセスです。ユーザ・データに含まれた(暗号化された)クッキーを変数に保存し、クッキーを削除します。

 set encrypted [HTTP::cookie "MyCookie"]
HTTP::cookie remove "MyCookie"


そして最後に、Base 64でエンコードされたデータをデコードし、RULE_INITで作成したキーでAES::decryptを使って複合化します。そのデータを含むクッキーを再びHTTPデータに追加します。

 set decrypted [AES::decrypt $::key [b64decode $encrypted]]
HTTP::cookie insert name "MyCookie" value $decrypted
※F5ネットワークスジャパンでは、サンプルコードについて検証を実施していますが、お客様の使用環境における動作を保証するものではありません。実際の使用にあたっては、必ず事前にテストを実施することを推奨します。