9

Android ドキュメントにあるように、rawQuery メソッドの selectionArgs パラメータは文字列として解析されます。

SQLiteDatabase.rawQuery(String sql, String[] selectionArgs)

selectionArgs: クエリの where 句に ?s を含めることができます。これは、selectionArgs の値に置き換えられます。値は文字列としてバインドされます。

しかし今日、私は 1 日の大部分を占める問題に直面しました。次のクエリを想像してください。

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15

COLUMN_A は INTEGER です。テーブルには、その基準に対応する約 10 行があります。データベース エディターでクエリを実行すると、常に正しい結果が得られましたが、スマートフォンでは、ステートメントは常に行を返しませんでした。

しばらくして、クエリを次のように変更しました。

SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= '15'

エディターは Android と同じように行を返しませんでした。したがって、クエリを次のように変更します。

SELECT * FROM TABLE_A WHERE CAST(IFNULL(COLUMN_A, 0) as INTEGER) >= '15'

問題を解決しました。行われた別のテストは次のとおりです。

SELECT * FROM TABLE_A WHERE COLUMN_A >= '15'

また、正しい結果が返されました。

これは、Android が IFNULL 句を使用してパラメーターを (文字列として) クエリにバインドする方法に関連する問題のようです。

それで、なぜこれが起こったのか誰か知っていますか?クエリで CAST を使用せずにこれを解決するための提案はありますか?

4

2 に答える 2

11

値をクエリにバインドする理由は、 SQLインジェクション攻撃を防ぐためです。

基本的に、クエリ(プレースホルダーを含む)をデータベースに送信し、「次のクエリはこの形式になり、他にはありません!」と言います。次に、攻撃者が別のクエリ文字列を(たとえば、フォームフィールドを介して)挿入すると、データベースは「ねえ、それはあなたが送信すると言ったクエリではありません!」と言います。そしてあなたにエラーを投げます。

コマンド(リンクされた記事に示されているように実際のクエリに挿入できる)は文字列であるため、string-datatypeはより「危険な」ものです。ユーザーが数値のみを受け取るコードをフィールドに挿入しようとし、入力を整数にキャスト/解析しようとすると(値をクエリに入力する前に)、すぐに例外が発生します。文字列では、そのような種類のセキュリティはありません。そのため、おそらく彼らは逃げなければなりません。これ、バインド値がすべて文字列として解釈される理由である可能性があります。

上記は偽物です!バインドする引数が文字列であるか整数であるかは関係ありません。それらはすべて同じように危険です。また、コード内の値を事前にチェックすると、多くの定型コードが生成され、エラーが発生しやすく、柔軟性がありません。


アプリケーションがSQLインジェクションを実行しないようにし、(異なる値で同じクエリを使用して)複数のデータベース書き込み操作を高速化するには、「プリペアドステートメント」を使用する必要があります。AndroidSDKでデータベースに書き込むための正しいクラスはですSQLiteStatement

プリペアドステートメントを作成するには、-objectの-methodcompileStatement()使用し、正しい-method(から継承される)を使用SQLiteDatabaseして対応する値(?クエリでは-marksに置き換えられます)をバインドします。bindXX()SQLiteProgram

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO SomeTable (name, age) values (?,?)");
// Careful! The index begins at 1, not 0 !!
stmt.bindString(1, "Jon");
stmt.bindLong(2, 48L);
stmt.execute();
// Also important! Clean up after yourself.
stmt.close();

この古い質問からの例:AndroidのSQliteでプリペアドステートメントを使用するにはどうすればよいですか?


残念ながら、SQLiteStatementを返すオーバーロードがないため、-ステートメントCursorには使用できませんSELECTrawQuery(String, String[])それらについては、次の-methodを使用できますSQLiteDatabase

int number = getNumberFromUser();
String[] arguments = new String[]{String.valueOf(number)};
db.rawQuery("SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= ?", arguments);

rawQuery()-methodは、引数値にString-arrayを使用することに注意してください。これは実際には問題ではありません。SQLiteは自動的に正しいタイプに変換します。文字列表現がSQLクエリで期待するものと等しい限り、問題はありません。

于 2012-07-04T00:00:31.503 に答える
1

私が推測するのとまったく同じ問題があります。あなたが言及しようとしているのは、sqlite データベースに関して実際の計算を行うことができないということです。問題はあなたが言及したものよりも大きいことがわかりました。SQLite データベースは、フィールド値に関するフィールド タイプを認識していないようです。つまり、INTEGER フィールド タイプに文字列値を挿入しようとしても、挿入は文句を言いません。

したがって、ご覧のとおり、問題はさらに大きくなります。整数のみを含む列があり、次のような where ステートメントを作成することを見てきましたが、 where id = 1 without ' ' の場合、結果データセットがあります。したがって、次のステートメントが機能しないかどうかを確認する場合があります: "SELECT * FROM TABLE_A WHERE IFNULL(COLUMN_A, 0) >= 15". しかし、where id >= '15' は機能します。これは、実際の 2 つの Unicode 文字 (!!!) である id の文字列表現を取得し、適用される演算子 >= to '15' を作成しようとするためです。

これらの問題に初めて遭遇したときは驚きました。パラメーターをバインドせずに SQL を動的に作成し、文字列全体として実行することにしました。パラメーターをバインドせずにデータベースにアクセスするのが最善の方法ではないことはわかっていますが、データベースは保護されており、データベースへのアクセス方法はアプリケーションにプライベートですが、セキュリティ上の理由はそれほど重要ではないため、これは解決策であり、良い方法です。 . 「侵入者」が root 電話でデータベースにアクセスできる場合は、それを SQLITE Studio に入れるだけで完了です。

于 2012-07-04T00:10:54.023 に答える