Java-SQLインジェクションを防止するエスケープ文字列


146

私はいくつかのアンチSQLインジェクションをJavaに配置しようとしていますが、「replaceAll」文字列関数を操作するのは非常に困難です。最終的に、既存の\to \\、any "to \"、any 'to \'、およびany \nを変換\\nして、文字列がMySQL SQLインジェクションによって評価されるときにブロックされるようにする関数が必要です。

使用していたコードをいくつかジャッキアップしました\\\\\\\\\\\が、関数内のすべてが目を凝らしています。誰かがこの例を偶然持っているなら、私はそれを大いに感謝します。


1
さて、PreparedStatementsが進むべき道であるという結論に達しましたが、私は当初計画されたとおりに進む必要がある現在の目的に基づいて、とりあえずフィルターを配置し、現在のマイルストーンに到達したら、次のことができます戻って、準備済みステートメントのためにデータベースをリファクタリングします。勢いを維持するための平均時間では、効果的にJavaの特定のMySQLの上記の文字をエスケープする誰かが解決策を持っており、それが正規表現のシステムです....必要なエスケープの数出て仕事に絶対的苦痛あるん
スコット・ボナー

2
「SET ROLE role_name」や「LISTEN channel_name」など、すべてのSQLステートメントがパラメーター化可能であるとは限りません
Neil McGuigan

1
@NeilMcGuiganうん。パラメーター化しようとしている部分が実際にはDMLであってもCREATE VIEW myview AS SELECT * FROM mytable WHERE col = ?、メインステートメントはDDLステートメントなので、ほとんどのドライバーはパラメーター化を拒否します。
SeldomNeedy 2017

回答:


249

PreparedStatementsは、SQLインジェクションを不可能にするための方法です。以下は、ユーザーの入力をパラメーターとして取る簡単な例です。

public insertUser(String name, String email) {
   Connection conn = null;
   PreparedStatement stmt = null;
   try {
      conn = setupTheDatabaseConnectionSomehow();
      stmt = conn.prepareStatement("INSERT INTO person (name, email) values (?, ?)");
      stmt.setString(1, name);
      stmt.setString(2, email);
      stmt.executeUpdate();
   }
   finally {
      try {
         if (stmt != null) { stmt.close(); }
      }
      catch (Exception e) {
         // log this error
      }
      try {
         if (conn != null) { conn.close(); }
      }
      catch (Exception e) {
         // log this error
      }
   }
}

名前と電子メールに含まれる文字に関係なく、それらの文字はデータベースに直接配置されます。これらはINSERTステートメントに影響を与えません。

データ型ごとに異なるsetメソッドがあり、どちらを使用するかはデータベースフィールドが何であるかによって異なります。たとえば、データベースにINTEGER列がある場合は、setIntメソッドを使用する必要があります。 PreparedStatementのドキュメントには、データの設定と取得に使用できるさまざまなメソッドがすべてリストされています。


1
このメソッドを介して、すべてのパラメーターを文字列として扱い、それでも安全ですか?データベースレイヤー全体を再構築せずに既存のアーキテクチャを安全に更新する方法を
Scott Bonner

1
すべてのDynqmic SQLは単なる文字列であるため、これを質問する必要はありません。私はPrepareStatementに詳しくないので、本当の問題は、ExecuteUpdateで実行できるパラメーター化されたクエリを生成するかどうかです。はいの場合、それは良いことです。いいえの場合、それは単に問題を隠しているだけであり、データベースレイヤーの再設計以外の安全なオプションがない可能性があります。SQLインジェクションへの対処は、最初から設計しなければならないことの1つです。後で簡単に追加できるものではありません。
Cylon Cat、

2
INTEGERフィールドに挿入する場合は、「setInt」を使用する必要があります。同様に、他の数値データベースフィールドは他のセッターを使用します。すべてのセッタータイプをリストするPreparedStatementドキュメントへのリンクを投稿しました。
Kaleb Brasee 2009年

2
はいCylon、PreparedStatementsはパラメータ化されたクエリを生成します。
Kaleb Brasee 2009年

2
@Kaleb Brasee、ありがとう。知っておくと便利です。ツールは環境ごとに異なりますが、パラメーター化されたクエリに取り掛かることが基本的な答えです。
Cylon Cat、

46

SQLインジェクションを防ぐ唯一の方法は、パラメータ化されたSQLを使用することです。SQLをハッキングして生計を立てている人よりも賢いフィルターを作成することは不可能です。

したがって、すべての入力、更新、およびwhere句にパラメーターを使用します。動的SQLは、ハッカーのオープンドアにすぎず、ストアドプロシージャに動的SQLが含まれています。パラメーター化、パラメーター化、パラメーター化。


11
また、パラメータ化されたSQLでさえ、100%の保証ではありません。しかし、それは非常に良いスタートです。
duffymo 2009年

2
@duffymo、私は100%安全なものはないことに同意します。パラメータ化されたSQLでも機能するSQLインジェクションの例はありますか?
Cylon Cat、

3
@Cylon Cat:確かに、SQLのチャンク(@WhereClauseや@tableNameなど)がパラメーターとして渡され、SQLに連結されて動的に実行される場合。SQLインジェクションは、ユーザーにコードを記述させるときに発生します。それらのコードをパラメーターとしてキャプチャーするかどうかは関係ありません。
スティーブカッス

16
ところで、これがこれ以上言及されない理由はわかりませんが、PreparedStatementsを使用する方がはるかに簡単で読みやすくなってます。それだけでおそらく、それらについて知っているすべてのプログラマーにとって、それらがデフォルトになります。
Edan Maor

3
一部のデータベースのPreparedStatementsは作成に非常にコストがかかるため、それらの多くを実行する必要がある場合は、両方のタイプを測定してください。
するThorbjörnRavnアンデルセン

36

防御オプション1:準備済みステートメント(パラメーター化されたクエリ)または防御オプション2:ストアドプロシージャを実際に使用できない場合は、独自のツールを構築せず、OWASPエンタープライズセキュリティAPIを使用してください。Google CodeでホストされているOWASP ESAPIから:

独自のセキュリティコントロールを記述しないでください。すべてのWebアプリケーションまたはWebサービスのセキュリティ制御を開発する際に車輪を再発明すると、時間の浪費と巨大なセキュリティホールにつながります。OWASPエンタープライズセキュリティAPI(ESAPI)ツールキットは、ソフトウェア開発者がセキュリティ関連の設計と実装の欠陥を防ぐのに役立ちます。

詳細については、JavaでのSQLインジェクションの防止SQLインジェクション防止に関するチートシートを参照してください。

防御オプション3:OWASP ESAPIプロジェクトを導入するすべてのユーザー提供入力のエスケープ」に特に注意してください。


4
ESAPIは現在のところ機能していません。AWSには、SQLインジェクション、XSSなどに役立つWAFがあります。この時点で他の代替手段はありますか?
ChrisOdney

@ChrisOdney WAFは簡単にバイパスできます。ほとんどのフレームワークは、独自のSQLインジェクション防止機能をすでに実装しており、独自にパラメータを自動的にエスケープします。従来のプロジェクトのための代替:owasp.org/index.php/...
ジャワR.

19

(これは、元の質問の下でのOPのコメントへの回答です。PreparedStatementが正規表現ではなく、このジョブのためのツールであることに完全に同意します。)

と言うとき\nは、シーケンス\+ nまたは実際の改行文字を意味しますか?それの場合は\+ n、タスクは非常に簡単です:

s = s.replaceAll("['\"\\\\]", "\\\\$0");

入力内の1つのバックスラッシュに一致させるには、4つを正規表現文字列に入れます。1つのバックスラッシュを出力に入れるには、4つのバックスラッシュを置換文字列に入れます。これは、Java文字列リテラルの形式で正規表現と置換を作成していることを前提としています。それらを他の方法で作成する場合(たとえば、ファイルから読み取るなど)、ダブルエスケープをすべて実行する必要はありません。

入力に改行文字があり、それをエスケープシーケンスで置き換えたい場合は、次のように入力を2回目に渡すことができます。

s = s.replaceAll("\n", "\\\\n");

または、2つのバックスラッシュが必要な場合もあります(これについてはあまり明確ではありません)。

s = s.replaceAll("\n", "\\\\\\\\n");

1
コメントをありがとう、私はあなたがすべての文字を1つにまとめる方法が好きです、私はそれをそれぞれのすべてを置き換える正規表現の少ない方法にしようとしていました...この質問に答えを割り当てる方法が今はわかりません。最終的にPreparedStatementsが答えですが、私の現在の目的では、あなたの答えは私が必要とする答えです。前に準備したステートメントの答えの1つに答えを出した場合、あなたは動揺しますか、それともカップルの間で答えを共有する方法はありますか?
Scott Bonner、

1
これは単なる一時的な問題なので、準備して、PreparedStatementの回答の1つを受け入れます。
アランムーア

12

PreparedStatementsはほとんどの場合に使用できる方法ですが、すべての場合に使用できるわけではありません。クエリまたはその一部を作成して、後で使用するために文字列として保存する必要がある場合があります。詳細とさまざまなプログラミング言語のAPIについては、OWASPサイトSQLインジェクション防止チートシートをご覧ください。


1
OWASPチートシートはGitHubに移動されました。SQLインジェクションのチートシートが今ここにある:github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/...
theferrit32

10

Prepared Statementsが最良のソリューションですが、手動で行う必要がある場合StringEscapeUtilsは、Apache Commons-Langライブラリのクラスを使用することもできます。これには、escapeSql(String)使用できるメソッドがあります。

import org.apache.commons.lang.StringEscapeUtils; … String escapedSQL = StringEscapeUtils.escapeSql(unescapedSQL);


2
参考までに:commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/…とにかく、このメソッドは引用符をエスケープするだけで、SQLインジェクション攻撃を防ぐことはできないようです。
Paco Abato

11
これは、単一引用符をエスケープするだけだったため、最新バージョンから削除されました
Pini Cheyni

2
この回答はSQLインジェクションを妨げないため、削除する必要があります。
ジャワR.

9

正規表現を使用してSQLインジェクションを引き起こす可能性のあるテキストを削除すると、SQLステートメントがを介してStatementではなく経由でデータベースに送信されているように聞こえますPreparedStatement

そもそもSQLインジェクションを防ぐ最も簡単な方法の1つは、を使用することですPreparedStatement。これは、プレースホルダーを使用してSQLステートメントに置換するデータを受け入れ、データベースに送信するSQLステートメントを作成するために文字列連結に依存しません。

詳細については、プリペアドステートメントを使用して、からのJavaチュートリアルを開始するには良い場所でしょう。


6

レガシーシステムを扱っている場合、または短期間でに切り替える場所が多すぎるPreparedStatement場合-つまり、他の回答で提案されているベストプラクティスを使用するのに障害がある場合は、AntiSQLFilterを試すことができます。


5

以下のコードが必要です。一見すると、これは私が作成した古いコードのように見えるかもしれません。しかし、私がしたことは、http://grepcode.com/file/repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.31/com/mysql/jdbc/PreparedStatementのソースコードを確認することでした。 java。その後、setString(int parameterIndex、String x)のコードを注意深く調べて、エスケープする文字を見つけ、これを自分のクラスにカスタマイズして、必要な目的に使用できるようにしました。結局のところ、これがOracleがエスケープする文字のリストである場合、これを知ることはセキュリティ面で本当に快適です。たぶん、Oracleは次のメジャーJavaリリースのためにこれに似たメソッドを追加するための微調整を必要としています。

public class SQLInjectionEscaper {

    public static String escapeString(String x, boolean escapeDoubleQuotes) {
        StringBuilder sBuilder = new StringBuilder(x.length() * 11/10);

        int stringLength = x.length();

        for (int i = 0; i < stringLength; ++i) {
            char c = x.charAt(i);

            switch (c) {
            case 0: /* Must be escaped for 'mysql' */
                sBuilder.append('\\');
                sBuilder.append('0');

                break;

            case '\n': /* Must be escaped for logs */
                sBuilder.append('\\');
                sBuilder.append('n');

                break;

            case '\r':
                sBuilder.append('\\');
                sBuilder.append('r');

                break;

            case '\\':
                sBuilder.append('\\');
                sBuilder.append('\\');

                break;

            case '\'':
                sBuilder.append('\\');
                sBuilder.append('\'');

                break;

            case '"': /* Better safe than sorry */
                if (escapeDoubleQuotes) {
                    sBuilder.append('\\');
                }

                sBuilder.append('"');

                break;

            case '\032': /* This gives problems on Win32 */
                sBuilder.append('\\');
                sBuilder.append('Z');

                break;

            case '\u00a5':
            case '\u20a9':
                // escape characters interpreted as backslash by mysql
                // fall through

            default:
                sBuilder.append(c);
            }
        }

        return sBuilder.toString();
    }
}

2
このコードは、上記のリンクにあるソースコードの逆コンパイルバージョンだと思います。新しいmysql-connector-java-xxxcase '\u00a5'およびcase '\u20a9'ステートメントが削除されたようです
zhy

私はあなたのコードでsqlmapを試しましたが、最初の攻撃から私を守りませんでした `タイプ:ブールベースのブラインドタイトル:ANDブールベースのブラインド-WHEREまたはHAVING句ペイロード:q = 1% 'AND 5430 = 5430 AND'% ' = '`
シャリーフ

申し訳ありませんが動作しましたが、最後に保存されたセッション結果を表示していました..今後同様のコメントを残しました..
shareef

org.ostermiller.utils.StringHelper.escapeSQL()またはを使用できますcom.aoindustries.sql.SQLUtility.escapeSQL()
Mohamed Ennahdi El Idrissi 2016年

1
これに出くわした人のために、これがコピーされた元のコードのGPLv2ライセンスに注意することが重要です。私は弁護士ではありませんが、このライセンスコードを含めることの意味を十分に理解していない限り、プロジェクトでこの回答を使用しないことを強くお勧めします。
Nick Spacek

0

準備されたステートメントをどこにでも適用できないレガシーシステムの場合、sqlmapによるsqlインジェクションを防ぐためのテスト用の多くのソリューションを検索した後。

java-security-cross-site-scripting-xss-and-sql-injectionトピックは ソリューションでした

@Richardのソリューションを試しましたが、私の場合はうまくいきませんでした。フィルターを使った

このフィルターの目的は、要求を独自にコード化されたラッパーMyHttpRequestWrapperにラッパーすることです。

特殊文字(<、>、 '、…)を含むHTTPパラメータをorg.springframework.web.util.HtmlUtils.htmlEscape(…)メソッドを介してHTMLコードに変換します。注:Apache Commonsにも同様のクラスがあります:org.apache.commons.lang.StringEscapeUtils.escapeHtml(…)Apache Commonsクラスorg.apache.commons.lang.StringEscapeUtilsを介したSQLインジェクション文字( '、“、…)。 escapeSql(…)

<filter>
<filter-name>RequestWrappingFilter</filter-name>
<filter-class>com.huo.filter.RequestWrappingFilter</filter-class>
</filter>

<filter-mapping>
<filter-name>RequestWrappingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>




package com.huo.filter;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletReponse;
import javax.servlet.http.HttpServletRequest;

public class RequestWrappingFilter implements Filter{

    public void doFilter(ServletRequest req, ServletReponse res, FilterChain chain) throws IOException, ServletException{
        chain.doFilter(new MyHttpRequestWrapper(req), res);
    }

    public void init(FilterConfig config) throws ServletException{
    }

    public void destroy() throws ServletException{
    }
}




package com.huo.filter;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

import org.apache.commons.lang.StringEscapeUtils;

public class MyHttpRequestWrapper extends HttpServletRequestWrapper{
    private Map<String, String[]> escapedParametersValuesMap = new HashMap<String, String[]>();

    public MyHttpRequestWrapper(HttpServletRequest req){
        super(req);
    }

    @Override
    public String getParameter(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        String escapedParameterValue = null; 
        if(escapedParameterValues!=null){
            escapedParameterValue = escapedParameterValues[0];
        }else{
            String parameterValue = super.getParameter(name);

            // HTML transformation characters
            escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

            // SQL injection characters
            escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

            escapedParametersValuesMap.put(name, new String[]{escapedParameterValue});
        }//end-else

        return escapedParameterValue;
    }

    @Override
    public String[] getParameterValues(String name){
        String[] escapedParameterValues = escapedParametersValuesMap.get(name);
        if(escapedParameterValues==null){
            String[] parametersValues = super.getParameterValues(name);
            escapedParameterValue = new String[parametersValues.length];

            // 
            for(int i=0; i<parametersValues.length; i++){
                String parameterValue = parametersValues[i];
                String escapedParameterValue = parameterValue;

                // HTML transformation characters
                escapedParameterValue = org.springframework.web.util.HtmlUtils.htmlEscape(parameterValue);

                // SQL injection characters
                escapedParameterValue = StringEscapeUtils.escapeSql(escapedParameterValue);

                escapedParameterValues[i] = escapedParameterValue;
            }//end-for

            escapedParametersValuesMap.put(name, escapedParameterValues);
        }//end-else

        return escapedParameterValues;
    }
}

いいjava-security-cross-site-scripting-xss-and-sql-injection topicですか?レガシーアプリケーションのソリューションを見つけようとしています。
caot

0

差出人:[ソース]

public String MysqlRealScapeString(String str){
  String data = null;
  if (str != null && str.length() > 0) {
    str = str.replace("\\", "\\\\");
    str = str.replace("'", "\\'");
    str = str.replace("\0", "\\0");
    str = str.replace("\n", "\\n");
    str = str.replace("\r", "\\r");
    str = str.replace("\"", "\\\"");
    str = str.replace("\\x1a", "\\Z");
    data = str;
  }
return data;

}


0

PL / SQLを使用しDBMS_ASSERT ている場合は、それを使用して入力を無害化することもできるため、SQLインジェクションを心配することなく使用できます。

たとえば、この回答を参照してください:https : //stackoverflow.com/a/21406499/1726419

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.