2013年6月24日月曜日

SQLインジェクションゴルフ - 認証回避の攻撃文字列はどこまで短くできるか?

コードゴルフという競技があります。与えられた問題(例えばFizzBuzz)を解くコードを、いかに短いプログラムで実現できるかというものです。
脆弱性の世界でもXSS Golfというものは既にあるようで、我らが はせがわようすけ氏にも、「短いXSSの話」というプレゼン資料が公開されています。第2回のOWASP Japanローカルチャプターミーティングでの講演ですね。これ、面白いので、まだ見ていない方はぜひご覧になって下さい。

XSSがあるならSQLインジェクションはどうかということで、ちょっと考えてみました。この手の遊びは、問題のルールが命というというところはありますが、最初なのであまり厳密に考えずにだらだらとやってみます。

攻撃対象プログラム

やはり、SQLインジェクション攻撃でみなさまおなじみの認証回避がよいのではないかと思いました。拙著「体系的に学ぶ 安全なWebアプリケーションの作り方」P123にSQLインジェクション攻撃で認証回避されてしまうスクリプトを紹介しましたが、以下に、PDOで書き換えたものを示します。
<?php
  header('Content-Type: text/html; charset=UTF-8');
  $id = $_GET['id'];   // ユーザID
  $pwd = $_GET['pwd']; // パスワード
  // データベースに接続
  $dbh = new PDO('pgsql:dbname=wasbook host=localhost', 'wasbook', 'wasbook'); // Postgres用
  //$dbh = new PDO('mysql:dbname=test', USERNAME, PASSWORD');   // MySQL用
  // SQLの組み立て
  $sql = "SELECT * FROM users WHERE id ='$id' AND pwd = '$pwd'";
  $stmt = $dbh->query($sql);  // クエリー実行
?>
<html>
<body><?php
  echo 'sql= ' . htmlspecialchars($sql, ENT_NOQUOTES, 'UTF-8') . '<br>';
  if ($stmt->rowCount() > 0) { // SELECTした行が存在する場合ログイン成功
    echo 'ログイン成功です';
  } else {
    echo 'ログイン失敗です';
  }
  $dbh = 0;
?></body>
</html>
テーブル users は以下のような感じで。
CREATE TABLE users (id varchar(10) NOT NULL PRIMARY KEY,  pwd varchar(10) NOT NULL);
INSERT INTO users VALUES ('yamada','sn6s3n');
INSERT INTO users VALUES ('tanaka','a2f9hy');

認証回避の基本形

拙著で紹介している認証回避のための攻撃文字列は以下の通りです。
' or 'a'='a
空白まで入れて11文字ですが、空白は不要なので除去すると9文字ですね。以下のように確認できます。「=」はパーセントエンコードして「%3d」と入力しています。


SQLコメントを使う

短い攻撃コードという目的には、SQLコメントを使う方法があります。なりすまし対象のログインIDが分かっているとすると、MySQLの場合、以下のように、idを「yamada'#」としてログインできます。MySQLは「#」から行末までがコメントとして認識しますから、パスワードの照合が無視されます。


次にPostgreSQLの場合。「#」 の代わりに、「--」をコメント開始記号として使用します。MySQLも「--」をコメントとして使えますが、MySQLの場合は「--」の後に一文字以上の空白が必要です。


コメントを使う場合の文字数は、MySQLの場合は2文字「'#」、PostgreSQLの場合は3文字「'--」となります。ただし、ログインIDまで文字数に含めるとすると、それぞれ8文字と9文字になります(なりすまし対象のログインIDが6文字の場合)。

あくまで論理和にこだわる

次に、あくまで論理和にこだわる場合を考えます。実は「OR 'a'='a'」という式は結構冗長でして、ORの右側(右オペランド)はとにかく真(true)の値をとればいいわけです。例えば、1=1が考えられますが、この場合、シングルクォートが余ってしまうのでコメントを使う必要があります。
SELECT * FROM users WHERE id ='' AND pwd = ''OR 1=1#'
上記の場合、攻撃文字列は「'OR 1=1#」で8文字となります。PostgreSQLの場合は「'OR 1=1--」で9文字です。元の9文字と比べて短くなっているとは言い難いですね。

できるだけ短い文字数の真値を探す

1=1よりもさらに短くすることはできないでしょうか。ここから先はデータベースの仕様によって変わります。
MySQLの場合、ゼロでない数値(1など)や、ゼロでない数値に変換される文字列('1'など)が暗黙の型変換で真値になります。これを利用して、以下の攻撃文字列が可能です。
  • 'OR 1#   (6文字)
  • 'OR'1     (5文字)
PostgreSQLの場合、数値から論理値への暗黙の型変換は認められないようですが、文字列から論理値への暗黙の型変換はできるようです。以下は、論理値を必要とする文脈で、暗黙に TRUE に変換されます。
  • '1'
  • 'TRUE'
  • 'TRU'
  • 'TR'
  • 'T'
  • 't'
以下は、暗黙に FALSE に変換される例です。
  • '0'
  • 'FALSE'
  • 'FALS'
  • 'FAL'
  • 'FA'
  • 'F'
  • 'f'
以下は、論理値が必要な文脈ではエラーになります。
  • 1
  • 'a'
  • 'truex'
この仕様を利用(悪用)すると、以下の攻撃文字列ができます。
  • 'OR'1   (5文字)
  • 'OR't    (5文字)
「'OR't」を指定した場合のSQL文は下記となります。
SELECT * FROM users WHERE id ='' AND pwd = ''OR't'

さらに短くできないか

以上の説明のように、認証回避の攻撃文字列を5文字まで短縮することができましたが、さらに短くできないでしょうか。例えば、MySQLにはビット論理和を求める演算子 | (1文字!)がありますが、これを使えないでしょうか。
mysql> SELECT * FROM users WHERE 0 | '1';
+--------+--------+
| id     | pwd    |
+--------+--------+
| tanaka | a2f9hy |
| yamada | sn6s3n |
+--------+--------+
2 rows in set (0.00 sec)
論理値が必要な文脈でビット演算を用いること自体は問題ないですね。
でも、ダメです。
mysql> SELECT * FROM users WHERE id='' AND pwd=''|'1';
Empty set, 2 warnings (0.00 sec)
なぜかというと、演算子の優先順位が理由です。以下は、MySQLの演算子の優先順位のまとめです(参考)。


上手のように、ビット論理和演算 | は、比較演算子 = よりも優先順位が高くなっています。このため、先のSQL文は下記のように解釈されます。

SELECT * FROM users WHERE id='' AND pwd=(''|'1');
(''|'1')の結果は 1 となるため、上記のWHERE句は、id=''AND pwd=1 ということで、認証回避にはなりません。

まとめ

SQLインジェクションゴルフということで、認証回避のSQLインジェクション攻撃文字列をどこまで短くできるかについて検討しました。もっと短くできる方法を見つけた方はぜひ教えて下さい。
また、認証回避だと問題が簡単すぎるきらいがありますので、もっと面白い想定があったらよいなと思います。こちらも、良いアイデアがあれば発表して、皆で楽しみましょう。
SQLインジェクションゴルフの実用的な価値はあまりないと思いますが、強いて言えば下記があげられます。

  • 文字数制限があって脆弱性の影響がないと言い張る人の論破
  • IPSやWAFの回避技術や、回避技術の対抗策の検討



0 件のコメント:

コメントを投稿

フォロワー