web-dev-qa-db-ja.com

ステートメントに動的テーブル名がある場合にSQLインジェクションを防ぐ方法は?

私はこのようなコードを持っています。

   final PreparedStatement stmt = connection
                .prepareStatement("delete from " + fullTableName
                    + " where name= ?");
   stmt.setString(1, addressName);

fullTableNameの計算は次のようになります。

 public String getFullTableName(final String table) {
    if (this.schemaDB != null) {
        return this.schemaDB + "." + table;
    }
    return table;
 }

ここで、schemaDBは環境の名前(時間の経過とともに変更可能)であり、tableはテーブル名(修正される予定)です。

schemaDBの値はXMLファイルから取得されるため、クエリはSQLインジェクションに対して脆弱になります。

Query:テーブル名をプリペアドステートメント(この例で使用されているnameなど)として使用する方法がわかりません。これは、SQLインジェクションに対する100%のセキュリティ対策です。

誰かが私に提案してもらえますか、これに対処するための可能なアプローチは何でしょうか?

注:将来的にDB2に移行できるため、ソリューションはOracleとDB2の両方と互換性があります(可能であればデータベースに依存しません)。

9
Vicky

JDBCは、残念ながら、テーブル名をステートメント内のバインドされた変数にすることを許可していません。 (これには理由があります)。

したがって、この種の機能を記述したり、達成したりすることはできません。

connection.prepareStatement("SELECT * FROM ? where id=?", "TUSERS", 123);

そして、TUSERをステートメントのテーブル名にバインドします。

したがって、安全な方法は、ユーザー入力を検証することだけです。ただし、最も安全な方法は、検証せず、ユーザー入力がDBを通過できるようにすることです。これは、セキュリティの観点から、検証よりも賢いユーザーを常に信頼できるためです。ステートメント内に連結された、ユーザーが生成した動的な文字列を絶対に信頼しないでください。

では、安全な検証パターンとは何ですか?

パターン1:ビルド前の安全なクエリ

1)すべての有効なステートメントをコードで一度だけ作成します。

Map<String, String> statementByTableName = new HashMap<>();
statementByTableName.put("table_1", "DELETE FROM table_1 where name= ?");
statementByTableName.put("table_2", "DELETE FROM table_2 where name= ?");

必要に応じて、select * from ALL_TABLES;ステートメントを使用して、この作成自体を動的にすることができます。 ALL_TABLESは、SQLユーザーがアクセスできるすべてのテーブルを返します。また、これからテーブル名とスキーマ名を取得することもできます。

2)マップ内のステートメントを選択します

String unsafeUserContent = ...
String safeStatement = statementByTableName.get(usafeUserContent);
conn.prepareStatement(safeStatement, name);

unsafeUserContent変数がDBに到達しない方法を確認してください。

3)statementByTableNameがスキーマに対して有効であり、将来の進化のためにスキーマに対して有効であり、テーブルが欠落していないことを確認する、ある種のポリシーまたは単体テストを作成します。

パターン2:再確認

1)インジェクションのないクエリを使用して、ユーザー入力が実際にテーブル名であることを検証できます(ここでは疑似SQLコードを入力していますが、実際にチェックするOracleインスタンスがないため、機能するように調整する必要がありますできます) :

select * FROM 
    (select schema_name || '.' || table_name as fullName FROM all_tables)
WHERE fullName = ?

そして、ここでプリペアドステートメント変数としてfullNameをバインドします。結果がある場合、それは有効なテーブル名です。次に、この結果を使用して安全なクエリを作成できます。

パターン3

これは、1と2の混合のようなものです。「TABLES_ALLOWED_FOR_DELETION」などの名前のテーブルを作成し、削除に適したすべてのテーブルを静的に入力します。

次に、検証ステップを

conn.prepareStatement(SELECT safe_table_name FROM TABLES_ALLOWED_FOR_DELETION WHERE table_name = ?", unsafeDynamicString);

結果が出た場合は、safe_table_nameを実行します。安全性を高めるために、このテーブルは標準のアプリケーションユーザーが書き込みできないようにする必要があります。

どういうわけか、最初のパターンの方が良いと感じています。

8
GPI

正規表現を使用してテーブル名を確認することで、攻撃を回避できます。

if (fullTableName.matches("[_a-zA-Z0-9\\.]+")) {
    final PreparedStatement stmt = connection
                .prepareStatement("delete from " + fullTableName
                    + " where name= ?");
    stmt.setString(1, addressName);
}

このような制限された文字セットを使用してSQLを挿入することは不可能です。

また、テーブル名から引用符をエスケープして、クエリに安全に追加することもできます。

fullTableName = StringEscapeUtils.escapeSql(fullTableName);
final PreparedStatement stmt = connection
            .prepareStatement("delete from " + fullTableName
                + " where name= ?");
stmt.setString(1, addressName);

StringEscapeUtilsには、Apacheのcommons-langライブラリが付属しています。

0
elyor
create table MYTAB(n number);
insert into MYTAB values(10);
commit;
select * from mytab;

N
10

create table TABS2DEL(tname varchar2(32));
insert into TABS2DEL values('MYTAB');
commit;
select * from TABS2DEL;

TNAME
MYTAB

create or replace procedure deltab(v in varchar2)
is

    LvSQL varchar2(32767);
    LvChk number;

begin
    LvChk := 0;
    begin
        select count(1)
          into LvChk
          from TABS2DEL
         where tname = v;

         if LvChk = 0 then
             raise_application_error(-20001, 'Input table name '||v||' is not a valid table name');
         end if;


    exception when others
              then raise;
    end;

    LvSQL := 'delete from '||v||' where n = 10';
    execute immediate LvSQL;
    commit;

end deltab;

begin
deltab('MYTAB');
end;

select * from mytab;

行が見つかりません

begin
deltab('InvalidTableName');
end;

ORA-20001: Input table name InvalidTableName is not a valid table name ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 21
ORA-06512: at "SQL_PHOYNSAMOMWLFRCCFWUMTBQWC.DELTAB", line 16
ORA-06512: at line 2
ORA-06512: at "SYS.DBMS_SQL", line 1721
0
ArtBajji

クエリを作成する前に、可能なテーブル名のセットを作成し、このセットに存在するかどうかを確認するのが最善の方法だと思います。

Set<String> validTables=.... // prepare this set yourself

    if(validTables.contains(fullTableName))
    {
       final PreparedStatement stmt = connection
                    .prepareStatement("delete from " + fullTableName
                        + " where name= ?");

    //and so on
    }else{
       // ooooh you nasty haker!
    }
0
Antoniossss