投稿者: Tomotoshi Sugishita
2008年6月15日 23:47
前回からかなり時間が経っていますが、後編として具体的な例をあげながら対策を検討していきます。
SQL インジェクション
Webアプリケーションに対する攻撃手法として、広く知られている方法の1つです。
一般的には「ユーザーインプット」を利用して不正なSQLステートメントを挿入(インジェクション)することでこれを実行し、データベースへの不正なアクセスを行います。
ここでは、Transact-SQLを使用したデータベース(SQL2000/MSDE2000/SQL2005/SQLExpress)にフォーカスしますが、SQLインジェクションは、MySQLやOrcleといったその他の多くのデータベースアプリケーションにおいても同様の影響を与える脅威です。
例として以下のようなコードを書いたとします。
Dim myQuery As String = _
"SELECT username FROM usertable WHERE password = '" & _
Request.QueryString("txtUsername") & "'"
Dim myCommand As New SqlCommand(myQuery, myConnection)
この例ではユーザーからの要求(Request.QueryString)を元にusertableの参照を行うSELECTステートメントを作成し、コマンドの実行を行っています。
通常であればこれは、default.aspx?txtUsername=admin というURLパラメータを用いることで、問題なくコマンドの実行を行うことができます。
しかし、このURLは利用者によるパラメータの変更が可能なため、非常に危険なコードです。
例えば、default.aspx?txtUsername=admin';DELETE FROM sometable というURLが送信された場合はどうでしょうか?
SQL Serverでは、セミコロン(;)はステートメントの区切り文字であり、このパラメータにより2つのステートメントが実行されることにになります。
となると、この例の実行結果がどうなるかは想像つきますよね?
DotNetNuke の Core Framework では、すべてのデータベースアクセスにおいてパラメータ化されたストアドプロシージャを使用することでこれを回避しています。
モジュール開発においても、できる限りこれを採用すべきです。
ただし、パラメータ化されたストアドプロシージャを利用しても、以下の例のように内部で文字列連結を行ってステートメントを生成し実行するストアドプロシージャではまったく同じ問題が発生しますので、意味のないことはやめましょう。
CREATE PROCEDURE GetSearchResults(@searchTerm nvarchar(50))
AS
DECLARE @sql nvarchar(300)
SET @sql = 'SELECT * FROM searchResults WHERE SearchTerm LIKE ' +
'%' + @searchTerm + '%'''
EXEC sp_executesql @sql
GO
上記、sp_executesqlストアドプロシージャ以外にも、T-SQL関数の EXEC/EXECUTE なども同様です。
最も重要な対策は、「ユーザーインプット」は常に疑うよう心がけることです。
受け取ったパラメータは細分化してそれらをきちんと検証する必要があります。
例えば、数値パラメータをクエリ文字列より受け取る場合は、いったん数値型の変数に代入してから使用すると確実です。
「ユーザーインプット」を検証する手段としては、前編で紹介した、DotNetNukeコアのフィルタリング機能(NoSQLパラメータを使用)を利用するとよいでしょう。
フィルタリング機能を使用して、例題のコード書き換えると以下のようになります。
Dim objSecurity As New PortalSecurity
Dim myFilteredText As String = _
objSecurity.InputFilter( _
Request.QueryString("txtUsername"), _
PortalSecurity.FilterFlag.NoSQL)
Dim myQuery As String = _
"SELECT username FROM usertable" & _
" WHERE password = '" & myFilteredText & "'"
Dim myCommand As New SqlCommand(myQuery, myConnection)
クロスサイトスクリプティング
クロスサイトスクリプティング (XSS)もSQLインジェクション同様、「ユーザーインプット」による脆弱性の1つです。
SQLインジェクションはデータベースサーバーへの攻撃を行うのに対し、XSSはクライアントとなるユーザーのブラウザに対しを悪意のあるスクリプトを送信し攻撃します。
このスクリプトの多くは、ユーザーのクッキーへのアクセスを試みる場合が多いようです。
攻撃者はなりすましによってユーザーの証明書を使用してアプリケーションにアクセスします。
また、フィッシング攻撃と呼ばれ、ユーザーコンテキスト内でスクリプトを実行することでオリジナルのページに酷似したページにリダイレクトさせ、ユーザー情報を得ようとする例などもあります。
問題のあるコード例
Dim mySearchTerm As String = Request.QueryString("txtSearch")
lblSearchtext.Text = "Searching for :" & mySearchTerm
この例に対し、 http://somesite.com/default.aspx?txtSearch=Hello を実行すると、クエリ文字列より取得したhelloがページにレンダリングされます。
では次に、http://somesite.com/default.aspx?txtSearch=<script>alert('unsafe input');</script> を実行するとどうでしょうか?
上記コードが実行され、scriptタグも含めてページにレンダリングされるため、'unsafe input'と表示メッセージが表示されます。
この程度であればさほど問題ではありませんが、スクリプトにもっと危険なコードを含む場合は深刻となるでしょう。またこれらは利用者にわからぬよう、電子メールなどに巧妙に仕掛けられることが多いです。
対策としては、これもSQLインジェクション同様、「ユーザーインプット」を疑うことです。
「ユーザーインプット」によって得られた値をクライアントに対して送信する際は、不正なスクリプト等が実行されないよう、適切なエンコーディングを行う必要があります。
また、送受信の内容だけではなく、場合によりデータベース、セッション、クッキー、その他のアプリケーション内のオブジェクトなどの情報の格納先についても同様に、不適切な内容が含まれていないかどうかを検証すべきでしょう。
インラインコードを実行している場合は、<%= %>を使用している部分もお忘れなく。
この問題に対しても、DotNetNukeコアのフィルタリング機能(NoMarkup パラメータまたは NoScripting パラメータ)が有効です。
修正例
Dim mySearch As String = Request.QueryString("txtSearch")
Dim objSecurity As New PortalSecurity
mySearch = objSecurity.InputFilter(mySearch, _
PortalSecurity.FilterFlag.NoMarkup)
lblSearchtext.Text = "Searching for :" & mySearchTerm
入力内容のHTMLタグを有効とする必要がある場合は、NoScriptingを使用します。
Dim mySearch As String= Request.QueryString("txtSearch")
Dim objSecurity As New PortalSecurity
mySearch = objSecurity.InputFilter(mySearch, _
PortalSecurity.FilterFlag.NoScripting)
lblSearchtext.Text = "Searching for :" & mySearchTerm
しかしながら、NoScriptingによってフィルタリングされるのはSCRIPTに限られるため、対策としては十分ではありません。
モジュールとして、本当にHTMLサポートを行う必要があるのかどうかを検討することがそれ以前に重要な対策です。
不要な攻撃箇所
DotNetNuke はモジュール方式のアーキテクチャを採用しています。
これらのモジュールは通常カスタムコードが記述されたユーザーコントロール(ascxファイル)の集まりです。
ユーザーコントロール(ascxファイル)は単独ではブラウザに直接表示することはできませんので、サイト訪問者がモジュールの機能にアクセスするにはあらかじめ想定された方法をとる必要があります。
しかし、モジュール開発者は、直接的な呼び出しが必要な場合や、RSSフィードを提供する場合など、本来のモジュールの気質とは違うコンテンツを提供する必要がある場合、まれにaspxページを使用することがあります。
(DotNetNukeコアでも、インストールを行うための "Install.aspx" や、RSSフィードを提供する "RSS.aspx" などを使用しています。)
DotNetNukeのモジュール開発では、様々な保護機能が有効なascxコントロールのみを使用した上でコード中でも適切なユーザー検証を行うことが理想的です。
例外として、ASP/ASPXページなどの直接的な呼出しが可能なファイルを採用する必要がある場合は、攻撃者から不正な参照が行われないよう慎重に検証し、コードが確実に意図した動作を行うことを確認してください。
特にモジュールで使用されているユーザーやロール権限、ファイルの種類の確認などには注意すべきです。
ファイルタイプの検証
DotNetNuke などの Web アプリケーションは、一般的にファイルのアップロード機能を提供しており、ユーザーが文書や画像ファイルをアプリケーションフォルダに送信することができます。
この機能の提供において、可能なファイルタイプをまったく考慮しなかった場合、非常に危険な問題が発生する可能性があります。
例えば、aspxファイルやaspファイルなどサーバー上でコードの実行が可能なファイルがアップロード可能な場合、攻撃者による権限の乗っ取りやWebサイト全体の乗っ取りを許可している事と同じです。
DotNetNukeでファイルのアップロード機能を提供する場合は、コア機能の UploadFileメソッドを使用してください。
仮にカスタムコードを実装する必要がある場合は、以下の例のようにサイト設定ページで設定可能なファイルタイプの検証を行うようにしてください。
Dim _portalSettings As PortalSettings = _
PortalController.GetCurrentPortalSettings
Dim objPortalController As New PortalController
Dim strFileName As String = Path.GetFileName(objHtmlInputFile.FileName)
Dim strExtension As String = Replace(Path.GetExtension(strFileName), ".", "")
If InStr(1, _
"," & _portalSettings.HostSettings("FileExtensions").ToString.ToUpper, _
"," & strExtension.ToUpper) <> 0 Then
'Do not allow upload
End If
さらにカスタムコードでは、DoS攻撃などによるリソースの占有を排除するために、ユーザーの権限やアップロードファイルの最大サイズについても考慮する必要があります。
以下は、利用可能なディスクスペース([hostspace=0]による無制限許可も含む)の確認を行っている単純な条件式の例です(ホストメニュー配下のメニューより呼び出された場合は例外)。
If (objPortalController.GetPortalSpaceUsed( _
_portalSettings.PortalId) + _
(objHtmlInputFile.ContentLength) / 1000000) _
<= (_portalSettings.HostSpace) Or _
(_portalSettings.HostSpace = 0) Or _
(_portalSettings.ActiveTab.ParentId = _portalSettings.SuperTabId) Then
...
End If
ファイルのリダイレクト
様々なモジュールで、ファイル一覧からファイルをダウンロードする機能を提供しています。
例えば以下のようなコードでファイルのダウンロード機能を提供した場合、
Dim strFilename As String = Request("download")
Response.AppendHeader("content-disposition", _
"attachment; filename=" + strFileName)
Response.ContentType = Request.QueryString("contenttype").ToString()
Response.WriteFile(strLink)
Response.End()
通常であれば、/default.aspx?download=safefile.jpg のようなURLパラメータでファイルのダウンロードが可能ですが、以前の例のようにこれは「ユーザーインプット」による操作が可能なため、
/default.aspx?download=../../web.config といったアクセスを行うことができ、web.config内の重要な情報を閲覧される可能性があります。
このような例においてはパラメータとして扱うのはファイル名に限定し、フォルダを含めるべきではありません。
また、クロスアプリケーションマッピングや親パスへのアクセスを制限しているコア関数を使用してください。
Dim objPortal As PortalInfo = objPortalController.GetPortal(intPortalId)
txtHomeDirectory.Text = objPortal.HomeDirectory
txtFileLocation.Text = txtHomeDirectory.Text & _
QueryStringDecode(Request("download"))
電子メールリンクの取扱い
検索エンジンのようにサイト上のコンテンツの一部の情報を索引化して収集するようなアプリケーションが存在します。
特に電子メールアカウントについては、攻撃者や悪意のある利用者によってスパムメールの送信先一覧に利用される可能性があるため、電子メールアドレスのリンクを提供する場合には注意する必要があります。
Response.Write( _
"<a href='mailto:someemail@somewhere.com'>someemail@somewhere.com</a>")
このような出力を行う場合は、DotNetNukeが提供している機能(コア関数の FormatEmainメソッド)を利用することをお勧めします。
Response.Write(FormatEmail("somemail@somewhere.com"))
これはJavaScriptによって電子メールアドレスへのハイパーリンクを提供し、このようなアプリケーションによる電子メールアドレスの収集を困難にすることができます。
共通のベストプラクティス
最後に、モジュールの開発時に留意すべきいくつかの共通のベストプラクティスについて説明したいと思います。
これらの活用は、保護的な役割を果たすと共に、製品全体の品質向上にもつながるでしょう。
変数の型チェック
Webアプリケーションのデータは通常、要求のヘッダーやボディを通して送信されますが、これらのデータは文字列型となります。
このデータに対し数値型などの特定のデータ型を期待する場合は、あらかじめ期待されるデータ型として妥当であるかどうかの確認を行いましょう。
ASP.NET 1.1 の場合
Try
Int32.Parse(request("moduleID")).
Catch
…
End Try
ASP.NET 2.0 の場合
If Int32.TryParse(request("moduleID")) = True Then
…
End If
コントロールの属性(プロパティ)の利用
多くのHTMLコントロールやサーバーコントロールは、入力を制限するための属性を持っています。
ReadOnly属性やMaxLength属性を活用しましょう。また、以下の例のようにテキストボックスでパスワードを扱う場合はパスワードが見えないように設定しましょう。
検証コントロール
ASP.NET は、性質の違ういくつかの検証コントロールを搭載しています。
これらは、クライアント側およびサーバー側で入力値の有効性を検証するのに役立ちます。
検証コントロールの使用方法については、以下のリンク先を参照してください。
ASP.NET 1.1 の場合
ASP.NET 2.0 の場合
さて、2回にわたって DotNetNuke公式「Secure Module Development」を解説してきました。
ここに掲載されているのは、ごく基本的な点ばかりですので、これだけで自身で開発したモジュールの安全が保証されるものではありませんが、
これから DotNetNuke のモジュール開発をはじめる方への基礎となれば幸いです。また、既に開発を行われている方も良い機会ですので、もう一度自身のモジュールが上記を満たしているか確認してみるのも良いでしょう。