概要
EasyBuggyとEasyBuggy Bootを利用して、SQLインジェクションの脆弱性をシミュレートし、その修正を試みます。
環境構築はこちらの記事から。
注意事項
本記事では脆弱性をついた攻撃手法について解説しています。
しかし、実際に第三者が運用しているWebサービスなどに対して、勝手に脆弱性の検査をおこなうのは違法行為となる可能性が非常に高いです。無害な検査用文字列を送信しているだけのつもりであっても、意図しない破壊を招いたり、監視システムによって攻撃と勘違いされる可能性があります。
絶対にやめましょう。
参考事例: 脆弱性検査について
SQLインジェクションの怖さ
SQLインジェクションの怖さを理解するには、下記の動画がおすすめです。
これは、実際に経済産業省からも注意喚起がされている脆弱性ですね。
脆弱性を確認する
EasyBuggyを起動し、脆弱性 > SQLインジェクション
を選択します。
画面の指示に従い、パスワードに ' OR '1'='1
を入力します。
すると、ほかのユーザーすべての情報が表示できてしまいます。
脆弱性を修正する
XSS脆弱性の根本的解決のためには、いくつかの方法があります。
そのうち、今回は「SQL文の組み立ては全てプレースホルダで実装する」という方法での修正を試みます。
ただ、上記のページの文章を読むだけでは、ちょっと意味がつかみにくいかと思います。
実際のコードで見てみましょう。
EasyBuggyの場合
SQLInjectionServletクラスに問題となる実装があります。
import java.sql.Statement;
// (中略)
Statement stmt = null;
// (中略)
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name
+ "' AND password='" + password + "'");
SQLインジェクションの原因
さきほど確認したSQLインジェクションの原因ですが、このSQL文にパスワードを渡すときの実装に問題があります。
プログラムが実行されると、下記のようなSQL文が実行されます。
SELECT name, secret FROM users WHERE ispublic = 'true' AND name='[入力された名前]' AND password='[入力されたパスワード]'
このSQL文の[入力した名前]
をMark
、[入力されたパスワード]
を' OR '1'='1
で置き換えてみます。
SELECT name, secret FROM users WHERE ispublic = 'true' AND name='Mark' AND password='' OR '1'='1'
SQLの演算子はANDが先に処理されます。
このため、WHERE句のispublic = 'true' AND name='Mark' AND password=''
の部分が先に処理されます。
name='Mark' AND password=''
を満たすレコードはないため、結果はFALSE
となります。
SELECT name, secret FROM users WHERE FALSE OR '1'='1'
'1'='1'
の結果は TRUE
です。
SELECT name, secret FROM users WHERE FALSE OR TRUE
FALSE OR TRUE
の結果は TRUE
です。
SELECT name, secret FROM users WHERE TRUE
つまり、すべてのレコード行を返すSQL文になってしまいました。
順を追って修正していきましょう。
1. Statement から PreparedStatementへの変更
SQL文を組み立てるときは、java.sql.Statementではなくjava.sql.PreparedStatementを使いましょう。
import java.sql.PreparedStatement; // import java.sql.Statement; から変更
// (中略)
PreparedStatement stmt = null; // Statement stmt = null; から変更
// (中略)
stmt = conn.createStatement();
rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name
+ "' AND password='" + password + "'");
2. Connection#createStatement から Connection#prepareStatement への変更
先ほどの変更により、コンパイルエラーとなる箇所を修正します。
Connection#prepareStatementの引数にはとりあえず空文字を入れ、コンパイルエラーが解消されることを確認します。
import java.sql.PreparedStatement;
// (中略)
PreparedStatement stmt = null;
// (中略)
stmt = conn.prepareStatement(""); // stmt = conn.createStatement(); から変更
rs = stmt.executeQuery("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name
+ "' AND password='" + password + "'");
3. Connection#prepareStatement の引数にSQLを渡す
とりあえずコンパイルエラーは解消しました。
しかし、JavaDocに書いてある通り、 Connection#prepareStatement
の引数にはSQLを渡す必要があります。このため、Statement#executeQuery の引数に渡しているSQLを移動させます。
import java.sql.PreparedStatement;
// (中略)
PreparedStatement stmt = null;
// (中略)
stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name='" + name
+ "' AND password='" + password + "'");
rs = stmt.executeQuery();
ここまでは、脆弱性を修正するまでの準備段階です。
4. パラメータ・プレースホルダーを設定する
ここからが、実際に脆弱性を修正する部分になります。
パラメータ・プレースホルダーを設定することで、機械的な処理でSQL文が組み立てられます。これにより、SQLインジェクションの脆弱性を解消できます。
まず、パラメータ部分を?
に置換します。
import java.sql.PreparedStatement;
// (中略)
PreparedStatement stmt = null;
// (中略)
stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name=? AND password=?");
rs = stmt.executeQuery();
5. 設定したプレースホルダーにパラメータを割り当てる
PreparedStatement#setStringを呼び出して、さきほど設定したプレースホルダーにパラメータを割り当てます。
パラメータの数値は1から始まります。
1番目のプレースホルダーに変数name
の値を、2番目のプレースホルダーに変数password
の値を割り当てます。
import java.sql.PreparedStatement;
// (中略)
PreparedStatement stmt = null;
// (中略)
stmt = conn.prepareStatement("SELECT name, secret FROM users WHERE ispublic = 'true' AND name=? AND password=?");
stmt.setString(1, name);
stmt.setString(2, password);
rs = stmt.executeQuery();
ここまできたら、再度、動作を確認してみましょう。
修正方法が正しければ、ほかのユーザーの情報が表示されることはなくなったはずです。
EasyBuggy Bootの場合
SQLInjectionServletクラスの実装は、SQLInjectionControllerクラスに移植されています。
Spring Frameworkを利用しているため、PreparedStatementではなくJdbcTemplateを利用してSQLを発行しています。こちらのほうが、簡単に修正することができます。
1. パラメータ・プレースホルダーを設定する
EasyBuggyと同じように、パラメータ・プレースホルダーを設定したSQL文字列に置き換えましょう。
2. 設定したプレースホルダーにパラメータを割り当てる
JdbcTemplate#queryでは、第3引数以降にパラメータを指定できます。
メソッドを呼び出す際の引数として、パラメータを順番に渡しましょう。
おつかれさまでした。