
SQLインジェクション
SQLインジェクションを分析する時が来た。長きにわたり、OWASPトップ10の不動の王者であり、何年も連続でその座を守ってきた。その歴史の長さ(20年以上)や、リストの首位からわずかに後退したにもかかわらず、今なお非常に広く普及し、危険な脆弱性であることに変わりはない。
ウェブセキュリティの脆弱性として、SQLインジェクション(SQLi)は攻撃者が最も頻繁に利用するハッキング手法の一つであり、データベースを操作して重要な情報を抽出することを可能にします。 さらに懸念すべき点は、攻撃者がデータベースサーバーの管理者権限を取得し、データベースの破壊、トランザクションの改ざん、データの漏洩、さらなる脆弱性の発生といった甚大な被害を引き起こす可能性があることです。
さっと見てみましょう、それがどのように起こるのか
SQL(構造化問い合わせ言語)は、リレーショナルデータベースと通信するために使用される言語です。これは、開発者、データベース管理者、アプリケーションが、日々生成される膨大な量のデータを管理するために使用する問い合わせ言語です。
アプリケーション内には、データ用とコード用の2つのコンテキストが存在する。コードコンテキストはコンピュータに実行すべき内容を指示し、処理対象のデータから分離する。SQLインジェクションは、攻撃者がSQLインタプリタが誤ってコードとして扱うデータを挿入した際に発生し、これにより攻撃者はアプリケーションから貴重な情報を収集できる。
SQLインジェクション攻撃の影響
SQLインジェクションはあらゆるウェブアプリケーションに甚大な損害を与え得る攻撃手法であり、攻撃者に重要なデータへの不正アクセスを可能にするため、数多くの著名な侵害事件の背後で好んで用いられてきた。攻撃者はユーザー名やパスワードからクレジットカード情報、個人識別番号に至るまで、膨大な情報を閲覧できる。
これらのデータへのアクセス権を取得した後、攻撃者はアカウントを乗っ取り、パスワードをリセットし、長期間にわたりオンライン購入を行い、あるいは(さらに深刻な)他の種類の詐欺を実行することが可能となります。
しかしSQLiの最も恐ろしい点は、攻撃者が検知されなければ、長期間にわたりシステムにバックドアを維持できることです。ご想像の通り、バックドアが開いたままの状態が続く限り、データ漏洩が繰り返されることになります。本当に恐ろしい話です。
いくつかの例を見て、これが実際にどのように機能するかをよりよく理解しましょう。
SQLインジェクションの例
SQLインジェクションには、様々な状況に対応できる複数の脆弱性技術が含まれます。以下は、SQLインジェクションの最も一般的な例の一部です:
SQLインジェクションの種類
さて、それでは3種類のSQLインジェクションを見ていきましょう。
SQLインジェクション
これは最も一般的で単純かつ効率的なSQLインジェクションの一種である。この種の攻撃では、攻撃と結果の取得に同じ通信チャネルが使用される。
以下の2種類がSQLインジェクション攻撃のバンドタイプです:
- 結合ベースのSQLインジェクション- 結合ベースの攻撃は、結合演算子を使用して2つ以上のSQLクエリ(SELECT文など)を結合し、目的の情報を取得してHTTP GETレスポンスを生成します。
- エラーベースSQLインジェクション - 攻撃者はデータベースのエラーメッセージを利用してその構造を把握する。この攻撃では、攻撃者が偽のリクエストを送信したり操作を行ったりすることでサーバーにエラーメッセージを表示させ、データベース情報を取得することが可能となる。そのため開発者は本番環境でエラーやログメッセージを送信せず、代わりにアクセス制限付きの場所に保存することが重要である。
SQLインフェレンシャル
推論型SQLインジェクション攻撃(ブラインド型)はより複雑で、攻撃の実行に時間がかかる場合があります。さらに、攻撃者は攻撃の結果を即座に得られないため、これはブラインド攻撃となります。
攻撃者はHTTPリクエストを介してデータベースサーバーにペイロードを送信し、ユーザーのデータベースを再構築します。その後、応答とアプリケーションの動作を監視し、攻撃が成功したかどうかを確認します。
以下はSQLインジェクションの推論攻撃の2種類です:
- ブール値ベースのブラインドSQLインジェクション- この攻撃では、ブール値(真または偽)の結果を得るためにデータベースにクエリを送信し、攻撃者はHTTPレスポンスを観察してブール値の結果を予測します。
- 時間ベースのブラインドSQLインジェクション- この攻撃では、攻撃者はデータベースにクエリを送信し、数秒間待機してから応答を送信させ、HTTPリクエストの応答時間に基づいてクエリの結果を判断します。
SQLインジェクション(SQLi)のオフバンド
これは、データベースサーバーの有効化された機能に依存する、より珍しいタイプのSQLi攻撃です。 攻撃者が他の種類の攻撃を実際に利用できない場合に発生します。
例えば、インバンド攻撃のために同じ通信チャネルを使用できない場合や、HTTPレスポンスがクエリの結果を計算するのに十分な明確さを持たない場合などです。
さらに、必要なデータを攻撃者に送信するためにHTTPまたはDNSリクエストを実行するデータベースサーバーの能力に大きく依存するため、あまり一般的ではありません。
SQLインジェクションから身を守る方法
幸いなことに、SQLインジェクションがこれほど古くから存在し、広く普及していることの利点は、その発生を防ぐ方法が確立されていることです。こうした予防技術を活用することは、優れたプログラミング習慣であるだけでなく、組織のSQLiに対するセキュリティ強化にもつながります。
この種の攻撃からデータベースサーバーを保護する方法はいくつかあります。例えば、入力の検証、ウェブアプリケーションファイアウォール(WAF)の使用、データベースの保護、サードパーティ製セキュリティ機器やシステムの導入、そして安全なSQLクエリの作成などです。
PythonでSQLインジェクションを防止する方法の例を見てみましょう。前述のセキュリティ対策の1つを活用します。
Pythonの例
この例では、攻撃者はブール値に基づくブラインドSQLインジェクションを利用して、システムから重要な情報を取得します。
Python: 脆弱性あり
データベースに「sample_data」というテーブルがあると仮定します。このテーブルはアプリケーションのユーザー名とパスワードを格納しています。
次に、ユーザーが以下のコマンドを使用してこのデータベーステーブル内で値を検索できるようにします:
import mysql.connector
db = mysql.connector.connect(
)Práctica #Bad. ¡Evita esto! Esto es solo para aprender.
(host="localhost", user="newuser", passwd="pass", db="sample")
cur = db.cursor()
name = raw_input('名前を入力: ')
cur.execute("SELECT * FROM sample_data WHERE Name = '%s';" % name) for row in cur.fetchall(): print(row)
db.close()
SQLインジェクション
ここで、ユーザーが検索に名前(例:アリシア)を入力した場合、結果に問題はありません。
しかし、ユーザーが「Alicia」のようなものを入力した場合、`DROP TABLE sample_data;` はデータベースに重大な影響を与えます。
Python: リメディエーション
SQLインジェクション攻撃を防ぐため、SQL文を以下の内容に変更する必要があります:
cur.execute(「SELECT * FROM sample_data WHERE Nombre = %s;」, (nombre,))
現在、システムはユーザー入力文字列を文字列として扱い、ユーザーがSQLクエリを挿入しようとしても、ユーザー入力は名前プロパティの値としてのみ扱われます。
この簡単な変更により、将来のクエリにおける悪意のある活動を防止し、ユーザーの入力による攻撃からシステムを保護できます。
Javaの例
この例では、アプリケーションのユーザーデータを格納する「sample_data」というデータベーステーブルも使用します。
基本的なログインページはユーザー名とパスワードを受け取り、サーブレット(LogInServlet)であるJavaファイルがデータベースでそれらを検証し、ログイン操作を許可します。
Java: 脆弱な例
データベースの「sample_data」テーブルを使用する場合、システムはユーザーが認証情報を入力としてログイン操作を実行することを許可します。
LogInServletファイルには、ログイン操作を処理するためのクエリが存在します。それは以下の通りです:
//Mal ejemplo. No utilice la concatenación de cadenas.
String query = «seleccione * de sample_data donde username='» + username + «'y password ='» + password + «'»;
Conexión conn = nula;
Sentencia stmt = nulo;
prueba {
conn = DriverManager.getConnection («jdbc:mysql: //127.0.0. 1:3306 /user», «raíz», «raíz»);
stmt = conn.createStatement ();
ResultSet rs = stmt.ExecuteQuery (consulta);
si (rs.next ()) {
//Inicio de sesión exitoso si se encuentra una coincidencia
éxito = verdadero;
}
} catch (Excepción e) {
por ejemplo, printStackTrace ();
} finalmente {
prueba {
stmt.close ();
conn.close ();
} catch (Excepción e) {}
}
si (éxito) {
response.sendRedirect (» home.html «);
} otra cosa {
response.sendRedirect (» login.html? error = 1 pulgada);
}
}
以下にユーザーログイン用のクエリを示します:
sample_data テーブルから username='username' かつ password ='password' のレコードを選択してください
SQLインジェクション
入力が有効であれば、システムは正常に動作します。例えば、ユーザー名が再びAliciaで、パスワードがsecretaであると仮定しましょう。
システムはこれらの認証情報でユーザーのデータを返します。しかし、攻撃者はPostmanやcURLを使用してユーザーのリクエストを操作し、SQLインジェクションを実行できます。
例えば、ハッカーは架空のユーザー名(アリシア)とパスワード「or」=「1」を送信する可能性があります。
この場合、ユーザー名とパスワードは一致しませんが、条件 '1'=' 1' は常に真となるため、ログイン操作は正常に実行されます。
Java: 予防
予防策として、LogInvalidationコードを変更し、クエリ実行にStatementの代わりにPreparedStatementを使用する必要があります。この変更により、ユーザー名とパスワードがクエリ内で連結されることを防ぎ、設定データとして扱うことでSQLインジェクションを回避します。
以下にLogInvalidation用に修正したコードを示します:
Consulta de cadena = «seleccione * de sample_data donde username=? y contraseña =?» ;
Conexión conn = nula;
PreparedStatement stmt = nulo;
prueba {
conn = DriverManager.getConnection («jdbc:mysql: //127.0.0. 1:3306 /user», «raíz», «raíz»);
stmt = conn.prepareStatement (consulta);
stmt.setString (1, nombre de usuario);
stmt.setString (2, contraseña);
ResultSet rs = stmt.executeQuery ();
si (rs.next ()) {
éxito = verdadero;
}
rs.close ();
} catch (Excepción e) {
por ejemplo, printStackTrace ();
} finalmente {
prueba {
stmt.close ();
conn.close ();
} catch (Excepción e) {
}
}
この場合、PreparedStatement、セッター、および基盤となるJDBC APIがユーザー入力の処理を担当し、SQLインジェクションを防止します。

例
次に、実際の動作をよりよく理解するために、いくつかの言語での追加例を見ていきましょう。
C# - 不安全
この例は`FromRawSql`の使用により安全ではありません。このメソッドはパラメータをバインドせず、エスケープ処理も行いません。したがって、このメソッドは絶対に避けるべきです。
var blogs = contexto.Publicaciones
.fromRawSQL («SELECCIONA * DE LAS PUBLICACIONES EN LAS QUE ESTADO = {0} Y autor = {1}», estado, autor)
.toList ();
C# - 安全
この例は、補間された値を受け取りパラメータ化する`FromSqlInterpolated`のおかげで安全です。
これは一般的に安全ですが、安全ではない`FromRawSql`と非常に似ているリスクがあります。
var blogs = contexto.Publicaciones
.fromSqlInterpolated ($"SELECT * FROM POSTS WHERE state = {state} AND author = {author}»)
.toList ();
Java - セキュア: Hibernate - 名前付きクエリ + ネイティブクエリ
Hibernateは、`ネイティブクエリ`と`名前付きクエリ`を通じて安全にクエリを構築する2つの方法を提供します。どちらもパラメータの配置場所を指定できます。
@NamedNativeQuery (
nombre = «find_post_by_state_and_author»,
consulta =
«SELECCIONAR *" +
«DE Post» +
«WHERE state =:state» +
«Y autor =:autor»,
Clase de resultado = Post.class)
java
Listar <Post>publicaciones = session.createNativeQuery (
«SELECCIONAR *" +
«DE Post» +
«WHERE state =:state» +
«Y autor =:autor»)
.addEntity (Post.class)
.setParameter («estado», estado)
.setParameter («autor», autor)
.lista ();
Java: 安全: jplq
jplqリポジトリインターフェースに`Query`属性を記述する際、それらは複数の形式を取り、パラメータ化されます。
@Query(「SELECT p FROM PUBLICATION p WHERE u.state = ?1 AND u.author = ?2 IN (
) Publicar Buscar publicación por estado y autor (estado de cadena, autor int);
@Query(「SELECT p FROM PUBLICATION P WHERE u.state = :state AND u.author = :author」)
User findPostByStateAndAuthor(@Param("state") String state, @Param("author") int author);
Javascript - 安全: pg
`pg`ライブラリを使用する場合、`query`メソッドは第二引数を通じてパラメータ値を提供することでパラメータ化を可能にします。
const {posts} = await db.query('SELECT * FROM 投稿 WHERE 状態 = $1 AND 著者 = $2', [状態, 著者])
JavaScript - 安全: Sequelize
`sequelize`ライブラリは、クエリのパラメータ化を可能にする方法を提供します。これはクエリ設定を受け取る第二引数を通じて行われます。これには、名前またはインデックスでクエリにパラメータとしてバインドできる値のリストが含まれます。