SQLスクリプト注入式攻撃の剖析---from MSDN


SQLスクリプト注入攻撃のプロファイリング
アプリケーションでフィルタされていないユーザー入力値(前述を参照)を受け入れると、悪意のあるユーザーは、エスケープ記号を使用して独自のコマンドを追加できます.
ユーザーの入力が社会保障番号であることを望んでいるSQLクエリーを考えてみましょう.例えば172-32-xxxxです.最後にクエリー形式は以下の通りです.
SELECT au_lname, au_fname FROM authors WHERE au_id = '172-32-xxxx'  

悪意のあるユーザーは、次のテキストをアプリケーション入力フィールド(テキストボックスコントロールなど)に入力できます.
' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)  -  

この例にはINSERT文が入力されています(ただし、SQL Serverに接続するアカウントで許可されている文はすべて実行できます).アカウントがsysadminロールのメンバー(xp_cmdshellを使用するshellコマンドを実行できる)であり、SQL Serverが使用するドメインアカウントが他のネットワークリソースへのアクセス権を持っている場合、このコードの破壊は特に大きい.
上記のコマンドは、SQL文字列の組合せを生成します.
SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100) --  

この場合、悪意のある入力の先頭の'(単一引用符)文字は、SQL文の現在の文字列を中止します.現在の文を閉じるのは、次の場合のみです.次に分析されるタグは、現在の文の継続タグではなく、新しい文の開始タグを意味します.
SELECT au_lname, au_fname FROM authors WHERE au_id = ' '  

;(セミコロン)文字は、SQLが新しい文を開始していることを示します.後続は悪意のあるSQLコードです.
; INSERT INTO jobs (job_desc, min_lvl, max_lvl) VALUES ('Important Job', 25, 100)  

注記SQL文を分離するには、必ずしもセミコロンは必要ありません.これはベンダー/インプリメンテーション・メソッドに関係しますが、SQL Serverは必要ありません.たとえば、SQL Serverは、次の文を2つの独立した文として解析します.
SELECT * FROM MyTable DELETE FROM MyTable

最後に、--(二重ダッシュ)文字列はSQLコメント記号であり、テキストの残りの部分を無視するようSQLに伝えます.この例では'(単一引用符)という終了文字を無視します(そうでないとSQLアナライザエラーになります).
上記の文の結果として、SQLが実行するすべてのテキストは次のとおりです.
SELECT au_lname, au_fname FROM authors WHERE au_id = '' ; INSERT INTO jobs (job_desc,min_lvl,max_lvl) VALUES ('Important Job',25,100) --'

ソリューション
アプリケーションからSQLを安全に呼び出すには、次のいくつかの方法があります.
  • SQL文の構築時にParametersコレクションを使用します.
    SqlDataAdapter myCommand = new SqlDataAdapter(
            "SELECT au_lname, au_fname FROM Authors WHERE au_id= @au_id", 
            myConnection);
    
    SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                                                 "@au_id",
                                                 SqlDbType.VarChar, 11);
    parm.Value= Login.Text;  
    
  • は、ストアド・プロシージャを呼び出すときにParametersのセットを使用する.
    // AuthorLogin is a stored procedure that accepts a parameter named Login
    SqlDataAdapter myCommand = new SqlDataAdapter("AuthorLogin", myConnection);
    myCommand.SelectCommand.CommandType = CommandType.StoredProcedure;
    SqlParameter parm = myCommand.SelectCommand.Parameters.Add(
                                    "@LoginId", SqlDbType.VarChar,11);
    parm.Value=Login.Text;  
    
    パラメータセットを使用すると、悪意のあるユーザーが入力にどのような内容が含まれているかにかかわらず、入力はテキストで処理されます.Parametersコレクションを使用するもう一つの利点は、タイプと長さのチェックを実行できることです.範囲外の値が例外をトリガーします.これは安全な奥行き防御の例です.
  • ユーザー入力からSQL文字をフィルタします.次の方法では、単純なSQL比較(等しい、小さい、大きい)文で使用される文字列が安全であることを確認する方法を示します.これは、文字列で使用される任意のアポトーシスにアポトーシスを追加してエスケープすることによって行われます.SQL文字列では、2つの連続するアポトーシスは、区切り文字ではなく文字列内のアポトーシス文字のインスタンスと見なされます.
    private string SafeSqlLiteral(string inputSQL)
    {
      return inputSQL.Replace("'", "''");
    }
    …
    string safeSQL = SafeSqlLiteral(Login.Text);
    SqlDataAdapter myCommand = new SqlDataAdapter(
           "SELECT au_lname, au_fname FROM authors WHERE au_id = '" + 
           safeSQL + "'", myConnection);
    

  • その他のベストプラクティス
    次に、セキュリティ・ホールを削減し、発生する可能性のある損失を一定の範囲に制限するための他の措置を示します.
  • は、入力のサイズとタイプを制限することによって、ゲートウェイ(フロントエンドアプリケーション)で不正なコンテンツの入力を防止する.入力の大きさやタイプを制限することで、危害の可能性を大幅に低減できます.たとえば、データベースの表示フィールドが10文字で、すべてが数値で構成されている場合、このルールは強制的に実装されます.
  • SQLコードは、最小限の権限を持つアカウントで実行されます.これにより、損失を大幅に低減することができる.たとえば、ユーザーがSQLを注入してデータベースのテーブルを削除しようとしたが、SQL接続で使用されているアカウントに適切な権限がない場合、SQLコードは失敗します.これはsaアカウント、sysadmin、またはdb_をownerのメンバーがアプリケーションのSQL接続に使用されるもう一つの理由.
  • SQLコードに異常エラーが発生した場合、エンドユーザーにデータベースによるSQLエラーを開示しないでください.エラーメッセージを記録し、ユーザーフレンドリーな情報のみを表示します.これにより、攻撃者に役立つ可能性のある不要な詳細を漏らすことを回避できます.

  • 保護モード一致文
    入力がLIKE句の文字列で使用される場合、文字(アポストロフィを除く)には特殊なパターンマッチングの意味もあります.
    たとえば、LIKE句では、%文字は「0または複数の文字に一致する」ことを意味し、入力中のこのような文字を特別な意味のない文字文字として使用するために、エスケープも必要です.特殊な処理を行わない場合、クエリはエラー結果を返し、文字列の先頭または先頭付近の非エスケープモードで文字を一致させるとインデックスが破壊される可能性があります.
    SQL Serverの場合、入力内容が有効であることを確認するには、次の方法を使用します.
    private string SafeSqlLikeClauseLiteral(string inputSQL)
    {
      // Make the following replacements:
      // '  becomes  ''
      // [  becomes  [[]
      // %  becomes  [%]
      // _  becomes  [_]
    
      string s = inputSQL;
      s = inputSQL.Replace("'", "''");
      s = s.Replace("[", "[[]");
      s = s.Replace("%", "[%]");
      s = s.Replace("_", "[_]");
      return s;
    }