多くの Web 開発者は、SQL クエリが改ざんされる可能性があることを認識していないため、SQL クエリを信頼できるコマンドとして扱います。彼らは、SQL クエリがアクセス制御をバイパスし、それによって認証と権限のチェックをバイパスできることをほとんど知りませんでした。さらに、SQL クエリを介してホスト オペレーティング システム レベルのコマンドを実行することもできます。
ダイレクト SQL コマンド インジェクションは、攻撃者が既存の SQL ステートメントを作成または変更して、隠しデータを取得したり、キー値を上書きしたり、データベース ホスト オペレーティング システム コマンドを実行したりするためによく使用される手法です。これは、アプリケーションがユーザー入力を受け取り、それを静的パラメーターと組み合わせて SQL クエリにすることによって実現されます。いくつかの実例を以下に示します。
入力されたデータの検証が不足しており、接続する新しいユーザーを作成する権限を持つスーパーユーザーまたは他のデータベース アカウントを使用しているため、攻撃者はデータベースに新しいスーパーユーザーを作成する可能性があります。
例 #1 データのページング表示を実装するコードは、スーパーユーザー (PostgreSQL システム) の作成にも使用できます。
<?php $offset = $argv[0]; // 注意,没有输入验证! $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; $result = pg_query($conn, $query); ?>
一般ユーザーは、$offset がビニングされている「前のページ」と「次のページ」のリンクをクリックします。元のコードでは、$offset が数値であるとのみ考えられます。ただし、誰かが次のステートメントを urlencode() して URL に追加しようとすると:
0; insert into pg_shadow(usename,usesysid,usesuper,usecatupd,passwd) select 'crack', usesysid, 't','t','crack' from pg_shadow where usename='postgres'; --
、スーパー ユーザーを作成できます。 0; は、エラーが発生しないように、元のクエリを完成させるための正しいオフセットを提供するだけであることに注意してください。
注:
-- は SQL のコメント マークであり、通常、SQL インタープリターに次のステートメントを無視するように指示するために使用されます。
パスワードを取得する考えられる方法は、検索結果を表示するページをターゲットにすることです。攻撃者が行う必要があるのは、どの変数が SQL ステートメントに送信され、それらの変数が誤って処理されたかを特定することだけです。このような変数は通常、WHERE、ORDER BY、LIMIT、OFFSET などの SELECT クエリの条件文で使用されます。データベースが UNION 構造をサポートしている場合、攻撃者は完全な SQL クエリを元のステートメントに追加して、任意のデータ テーブルからパスワードを取得する可能性もあります。したがって、パスワードフィールドを暗号化することが重要です。
例 #2 記事といくつかのパスワードを表示する (任意のデータベース システム)
<?php $query = "SELECT id, name, inserted, size FROM products WHERE size = '$size' ORDER BY $order LIMIT $limit, $offset;"; $result = odbc_exec($conn, $query); ?>
元のクエリに別の SELECT クエリを追加してパスワードを取得できます:
' union select '1', concat(uname||'-'||passwd) as name, '1971-01-01', '0' from usertable; --
上記のステートメントの場合 (' と -- を使用) $query内の任意の変数に追加されると面倒になります。 SQL の
UPDATE にも脆弱性があります。このクエリは、上の例のように、別の完全なリクエストに挿入または追加することもできます。しかし、攻撃者は、テーブル内の一部のデータを変更できるように、SET 句をターゲットにすることを好みます。この場合、クエリを正常に変更するには、データベースの構造を知っている必要があります。フォーム上の変数名に基づいてフィールドを推測したり、総当たりでクラックしたりすることができます。ユーザー名とパスワードを保存するフィールドに名前を付ける方法は多くありません。
例 #3 パスワードのリセットから追加の権限の取得まで (任意のデータベース システム)
<?php $query = "UPDATE usertable SET pwd='$pwd' WHERE uid='$uid';"; ?>
しかし、悪意のあるユーザーは ' または '%admin%' のような uid を $uid への変数の値として送信します。管理者パスワードを入力するか、$pwd の値を "hehehe', admin='yes', trusted=100" (その後にスペースがあります) に送信して、さらに多くの権限を取得します。これを行うと、クエリは実際には次のようになります:
<?php // $uid == ' or uid like'%admin%'; -- $query = "UPDATE usertable SET pwd='...' WHERE uid='' or uid like '%admin%'; --"; // $pwd == "hehehe', admin='yes', trusted=100 " $query = "UPDATE usertable SET pwd='hehehe', admin='yes', trusted=100 WHERE ...;"; ?>
次の恐ろしい例は、いくつかのデータベースでシステム コマンドを実行する方法を示します。
例 #4 データベースが配置されているホストのオペレーティング システム (MSSQL Server) を攻撃します
<?php $query = "SELECT * FROM products WHERE id LIKE '%$prod%'"; $result = mssql_query($query); ?>
攻撃が送信した場合、%' exec master..xp_cmdshell 'net user test testpass /ADD' -- の値として変数 $prod の場合、$query は
<?php $query = "SELECT * FROM products WHERE id LIKE '%a%' exec master..xp_cmdshell 'net user test testpass /ADD'--"; $result = mssql_query($query); ?>
になります。MSSQL サーバーは、システムにユーザーを追加するためのコマンドをその後に含むこの SQL ステートメントを実行します。このプログラムが sa として実行されており、MSSQLSERVER サービスに十分な権限がある場合、攻撃者はホストにアクセスするためのシステム アカウントを取得できます。
注:
上記の例は特定のデータベース システムに関するものですが、他のデータベース システムに対して同様の攻撃を実行できないという意味ではありません。さまざまな方法を使用すると、さまざまなデータベースが影響を受ける可能性があります。
予防措置
上記の攻撃を実行するには、攻撃者はデータベース構造に関する情報を知る必要があると言って自分を慰める人もいるかもしれません。はい、そうです。しかし、攻撃者がこの情報を取得しないとは誰も保証できません。取得したデータベースは漏洩の危険にさらされます。フォーラム プログラムなどのオープン ソース ソフトウェア パッケージを使用してデータベースにアクセスしている場合、攻撃者が関連コードを入手するのは簡単です。コードの設計が不適切な場合、リスクはさらに大きくなります。
这些攻击总是建立在发掘安全意识不强的代码上的。所以,永远不要信任外界输入的数据,特别是来自于客户端的,包括选择框、表单隐藏域和 cookie。就如上面的第一个例子那样,就算是正常的查询也有可能造成灾难。
永远不要使用超级用户或所有者帐号去连接数据库。要用权限被严格限制的帐号。
检查输入的数据是否具有所期望的数据格式。PHP 有很多可以用于检查输入的函数,从简单的变量函数和字符类型函数(比如 is_numeric(), ctype_digit())到复杂的Perl 兼容正则表达式函数都可以完成这个工作。
如果程序等待输入一个数字,可以考虑使用 is_numeric() 来检查,或者直接使用 settype() 来转换它的类型,也可以用 sprintf() 把它格式化为数字。
Example #5 一个实现分页更安全的方法
<?php settype($offset, 'integer'); $query = "SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET $offset;"; // 请注意格式字符串中的 %d,如果用 %s 就毫无意义了 $query = sprintf("SELECT id, name FROM products ORDER BY name LIMIT 20 OFFSET %d;", $offset); ?>
使用数据库特定的敏感字符转义函数(比如 mysql_escape_string() 和 sql_escape_string())把用户提交上来的非数字数据进行转义。如果数据库没有专门的敏感字符转义功能的话 addslashes() 和 str_replace() 可以代替完成这个工作。看看第一个例子,此例显示仅在查询的静态部分加上引号是不够的,查询很容易被攻破。
要不择手段避免显示出任何有关数据库的信心,尤其是数据库结构。
也可以选择使用数据库的存储过程和预定义指针等特性来抽象数库访问,使用户不能直接访问数据表和视图。但这个办法又有别的影响。
除此之外,在允许的情况下,使用代码或数据库系统保存查询日志也是一个好办法。显然,日志并不能防止任何攻击,但利用它可以跟踪到哪个程序曾经被尝试攻击过。日志本身没用,要查阅其中包含的信息才行。毕竟,更多的信息总比没有要好。