
インジェクションSQL
SQLインジェクションに移りましょう。長年にわたり、OWASPトップ10の王座を揺るぎなく守り続け、何年も首位を維持してきました。約20年という歴史を持ち、現在では首位からやや順位を落としているものの、依然として非常に広く普及し、危険な脆弱性です。
Webセキュリティの脆弱性として、SQLインジェクション(SQLi)は攻撃者が最も頻繁に利用する「ハッキング」手法の一つであり、データベースを操作して重要な情報を抽出することを可能にする。 さらに深刻なことに、攻撃者はデータベースサーバーの管理者になりすまして、データベースの破壊、トランザクションの改ざん、データの漏洩、その他の問題への脆弱性付与など、甚大な被害をもたらす行為を実行できる。
その様子をちょっと見てみよう
SQL(構造化問い合わせ言語)は、リレーショナルデータベースとの通信に使用される言語です。これは、開発者、データベース管理者、アプリケーションが毎日生成される膨大な量のデータを管理するために使用する問い合わせ言語です。
アプリケーション内には、データ用とコード用の2つのコンテキストが存在します。コードコンテキストはコンピュータに実行すべき内容を指示し、処理対象のデータから分離します。 SQLインジェクションは、攻撃者がSQLインタプリタによって誤ってコードとして処理されるデータを挿入した際に発生し、これにより攻撃者はアプリケーションから貴重な情報を収集することが可能となる。
SQLインジェクション攻撃の影響
SQLインジェクションはあらゆるWebアプリケーションにとって極めて危険であり、攻撃者が許可なく重要なデータにアクセスすることを可能にするため、多くの注目を集めた侵害事件の根源として好まれる手法でした。攻撃者はユーザー名やパスワード、クレジットカード情報、個人識別番号など、様々な情報を閲覧することが可能です。
これらのデータにアクセスした後、攻撃者はアカウントを乗っ取り、パスワードをリセットし、長期間にわたりオンライン購入を行い、あるいは(さらに悪質な)他の種類の詐欺を実行することが可能となります。
しかしSQLiに関して最も憂慮すべき点は、攻撃者が検知されなければ、システム内に長期間バックドアを維持できることです。ご想像の通り、バックドアが開いたままの状態が続く限り、データ侵害が繰り返されることになります。恐ろしい話です。
いくつかの例を見て、実際にどのように機能するかをよりよく理解しましょう。
SQLインジェクションの例
SQLインジェクションには、様々な状況に対応できる複数の脆弱性技術が含まれます。以下に最も一般的なSQLインジェクションの例をいくつか示します:
SQLインジェクションの種類
さて、それでは3種類の異なるSQLインジェクションを見ていきましょう。
SQLi イントラバンド
これは最も一般的で、最も単純かつ効果的なSQLインジェクションの一種である。この種の攻撃では、攻撃と結果の取得に同じ通信チャネルが使用される。
以下の2種類のイントラバンドSQLi攻撃があります:
- ユニオンベースのSQLインジェクション- ユニオンベースの攻撃は、ユニオン演算子を使用して2つ以上のSQLクエリ(SELECT文など)を結合し、目的の情報を取得してHTTP GETレスポンスを得るものです。
- エラーベースSQLインジェクション - 攻撃者はデータベースのエラーメッセージを利用してその構造を把握します。 この攻撃では、攻撃者は偽の要求を送信したり操作を行ったりしてサーバーにエラーメッセージを表示させ、データベース情報を取得します。 そのため、開発者は本番環境でエラーを送信したりメッセージをログに記録したりせず、アクセス制限付きの場所で保管することが重要です。
推論型SQL
推論型またはブラインド型SQLインジェクション攻撃はより複雑であり、その実行にはより長い時間を要する場合がある。さらに、攻撃者は攻撃の結果を即座に得られないため、これはブラインド型攻撃となる。
攻撃者はHTTPリクエストを介してデータベースサーバーにペイロードを送信し、ユーザーのデータベースを再構築します。その後、応答とアプリケーションの動作を監視し、攻撃が成功したかどうかを確認します。
推論型SQLインジェクション攻撃には2種類存在する:
- ブール値に基づくブラインドSQLインジェクション- この攻撃では、ブール値(真または偽)の結果を得るためにデータベースにクエリが送信され、攻撃者はHTTPレスポンスを観察してブール値の結果を予測します。
- 時間ベースのブラインドSQLインジェクション- この攻撃では、攻撃者はデータベースにクエリを送信し、数秒間待機させてから応答を送信させます。攻撃者はHTTPリクエストの応答時間に基づいてクエリの結果を評価します。
SQLインジェクション(外部処理)
これは、データベースサーバーの有効化された機能に依存する、より稀なタイプのSQLインジェクション攻撃です。攻撃者が他のタイプの攻撃を実際に利用できない場合に発生します。
例えば、インバンド攻撃と同じ通信チャネルを利用できない場合や、HTTP応答がクエリ結果を計算するのに十分な明確さを持たない場合などが挙げられます。
さらに、この手法は、攻撃者に必要なデータを送信するためにデータベースサーバーがHTTPやDNSクエリを実行できる能力に大きく依存しているため、あまり一般的ではありません。
SQLインジェクション攻撃から身を守る方法
幸いなことに、SQLインジェクションの利点は、非常に古くから存在し、また非常に一般的であるため、その発生を防ぐ手段が存在する点にあります。この種の予防技術の使用は、単なる良いコーディング習慣であるだけでなく、組織のSQLiに対するセキュリティを強化することにもつながります。
データベースサーバーをこの種の攻撃から保護する方法は複数存在します。例えば、入力の検証、Webアプリケーションファイアウォール(WAF)の使用、データベースのセキュリティ強化、サードパーティのセキュリティチームやシステムの活用、そして堅牢なSQLクエリの作成などが挙げられます。
上記のセキュリティ対策の1つを使用して、PythonにおけるSQLインジェクション防止の例を見てみましょう。
Pythonの例
この例では、攻撃者はブール値型ブラインドSQLインジェクションを使用して、システムから重要な情報を取得します。
Python : 脆弱性あり
データベースに「sample_data」というテーブルが存在すると仮定します。このテーブルには、アプリケーションのユーザー名とパスワードが含まれています。
次のコマンドを使用して、ユーザーがこのデータベーステーブルから値を検索することを許可します:
importer mysql.connector
base de données = mysql.connector.connect
Pratique #Bad. Evite ça ! C'est juste pour apprendre.
(host="localhost », user="newuser », passwd="pass », db="sample »)
cur = db.cursor ()
name = raw_input ('名前を入力してください: ')
cur.execute (« SELECT * FROM sample_data WHERE Name = '%s' ; » % name) pour la ligne dans cur.fetchall () : print (row)
db.fermer ()
インジェクションSQL
ここで、ユーザーが検索に名前(例:アリシア)を入力した場合、出力に問題はありません。
ただし、ユーザーが「Alicia」のようなものを入力した場合、`DROP TABLE sample_data` はデータベースに重大な影響を与えます。
Python : 修正
SQLインジェクション攻撃を防ぐためには、SQL文を以下のように変更する必要があります:
cur.execute(「SAMPLE_DATA から Nom = %s の * を選択;」, (name,))
今後、システムはユーザー入力文字列を文字列として処理します。たとえユーザーがSQLインジェクションを試みても、ユーザー入力は名前プロパティの値のみとして扱われます。
この単純な変更により、今後のリクエストにおける悪意のある活動を防止し、ユーザーによる入力操作攻撃からシステムを保護できます。
Javaの例
この例では、アプリケーションのユーザーデータを格納する「sample_data」という名前のデータベーステーブルも使用します。
基本的なログインページはユーザー名とパスワードを使用し、サーブレット(LoginServlet)であるJavaファイルがデータベースに対してこれらを検証し、ログイン操作を許可します。
Java : 脆弱な例
データベースの「sample_data」テーブルを使用して、システムはユーザーが自身の認証情報を入力として使用して接続操作を実行できるようにします。
LoginServletファイルには、ログイン操作に適したリクエストが含まれています。具体的には:
//Mauvais exemple. N'utilisez pas la concaténation de chaînes.
String query = « select * from sample_data where username=' » + username + « 'and password =' » + password + « '» ;
Connexion conn = null ;
Déclaration stmt = null ;
essayez {
conn = DriverManager.getConnection (« jdbc:mysql : //127.0.0. 1:3306 /user », « root ») ;
stmt = conn.createStatement () ;
ResultSet rs = STMT.ExecuteQuery (requête) ;
si (rs.next ()) {
//Connexion réussie si une correspondance est trouvée
succès = vrai ;
}
} catch (Exception e) {
e. printStackTrace () ;
} enfin {
essayez {
stmt.fermer () ;
conn.close () ;
} catch (Exception e) {}
}
si (succès) {
response.sendRedirect (» home.html «) ;
} autre {
Response.sendRedirect (» login.html ? erreur = 1") ;
}
}
以下はユーザー接続のリクエストです:
sample_data テーブルで、ユーザー名 = ユーザー名 かつ パスワード = パスワード のレコードを選択
インジェクションSQL
入力が有効であれば、システムは正常に動作します。例えば、ユーザー名が再びAliciaで、パスワードがsecretであると仮定します。
システムはこれらの認証情報と共にユーザーデータを返します。しかし、攻撃者はPostmanやcURLを使用してユーザーのリクエストを操作し、SQLインジェクションを実行する可能性があります。
例えば、ハッカーは架空のユーザー名(Alicia)とパスワード「or '1'='1'」を送信できる。
この場合、ユーザー名とパスワードは一致しませんが、「1'='1」という条件は常に真となるため、ログイン操作は成功します。
Java : 予防
予防措置として、ログイン検証コードを変更し、クエリ実行にStatementの代わりにPreparedStatementを使用する必要があります。この変更により、ユーザー名とパスワードがクエリ内で連結されるのを防ぎ、SQLインジェクションを回避するために設定データとして扱われます。
以下にLoginValidation用の修正済みコードを示します:
String query = « select * from sample_data where username= ? et mot de passe = ? » ;
Connexion conn = null ;
PreparedStatement stmt = null ;
essayez {
conn = DriverManager.getConnection (« jdbc:mysql : //127.0.0. 1:3306 /user », « root ») ;
stmt = Conn.PrepareStatement (requête) ;
STMT.setString (1, nom d'utilisateur) ;
STMT.setString (2, mot de passe) ;
ResultSet rs = STMT.executeQuery () ;
si (rs.next ()) {
succès = vrai ;
}
rs.fermer () ;
} catch (Exception e) {
e. printStackTrace () ;
} enfin {
essayez {
stmt.fermer () ;
conn.close () ;
} catch (Exception e) {
}
}
この場合、PreparedStatement、セッターメソッド、および基盤となるJDBC APIがユーザー入力の検証を担当し、SQLインジェクションを防止します。

例
次に、実際の動作をよりよく理解するために、異なる言語における追加の例をいくつか検討します。
C# - 非セキュア
この例はFromRawSQLを使用しているため安全ではありません。このメソッドはパラメータをバインドせず、エスケープ処理も行いません。したがって、このメソッドは絶対に避けるべきです。
var blogs = context.POSTS
.fromRawSQL (« SÉLECTIONNEZ * PARMI LES ARTICLES OÙ state = {0} ET author = {1} », state, author)
.toList () ;
C# - セキュア
この例はFromSQLInterpolatedによって保護されており、補間された値を取得してパラメータ化します。
一般的に安全であるとはいえ、FromRawSQLと非常に似ている可能性があり、FromRawSQLは安全ではありません。
var blogs = context.POSTS
.fromSQLInterpolated ($"SÉLECTIONNEZ * PARMI LES ARTICLES OÙ state = {state} ET author = {author} »)
.toList () ;
Java - セキュア:Hibernate - 名前付きクエリ + ネイティブクエリ
Hibernateは、安全なクエリ構築のために「ネイティブクエリ」と「名前付きクエリ」の2つの方法を提供します。どちらもパラメータの配置を指定できます。
@NamedNativeQuery (
name = « find_post_by_state_and_author »,
requête =
« SÉLECTIONNEZ *" +
« DE LA POSTE » +
« WHERE state =:state » +
« ET auteur = : auteur »,
Classe de résultats = Post.class)
java
Liste des <Post>messages = Session.createNativeQuery (
« SÉLECTIONNEZ *" +
« DE LA POSTE » +
« WHERE state =:state » +
« ET auteur = : auteur »)
.Ajouter une entité (Post.class)
.setParameter (« état », état)
.setParameter (« auteur », auteur)
.liste () ;
Java - セキュア:jplq
jplqリポジトリインターフェースに`Query`属性をアノテーションすることで、それらは複数の形式を取り、パラメータ化される。
@Query("SELECT p FROM MESSAGE p WHERE u.state = ?1 AND u.author = ?2 IN")
Post findPostByStateAndAuthor(String state, int author);
@Query("SELECT p FROM Post 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(「状態 = 1$ かつ 投稿者 = 2$ の投稿から * を選択」, [状態, 投稿者])
JavaScript - セキュア:Sequelize
Sequelizeライブラリは、クエリをパラメータ設定する手段を提供します。これはクエリの2番目の引数を通じて行われ、クエリのパラメータを受け取ります。これには、名前またはインデックスによってクエリにパラメータとしてバインドされる値のリストが含まれます。