php - エスケープ - sql インジェクション like 対策



PHPでSQLインジェクションを防止するにはどうすればよいですか? (19)

セキュリティ警告 :この回答はセキュリティのベストプラクティスに沿ったものではありません。 エスケープは、SQLインジェクションを防ぐためには不十分で 、代わりにプリペアドステートメントを使用します。 自己責任で以下に示す戦略を使用してください。 (また、 mysql_real_escape_string()はPHP 7で削除されました)

次のような基本的なことができます:

$safe_variable = mysql_real_escape_string($_POST["user-input"]);
mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

これはすべての問題を解決するわけではありませんが、それは非常に良い踏み台です。 変数の存在、形式(数字、文字など)をチェックするなどの明白な項目は除外しました。

ユーザーの入力をそのままSQLクエリに挿入すると、アプリケーションは次の例のようにSQLインジェクションに対して脆弱になります。

$unsafe_variable = $_POST['user_input']; 

mysql_query("INSERT INTO `table` (`column`) VALUES ('$unsafe_variable')");

これは、ユーザーがvalue'); DROP TABLE table;--ようなものを入力できるためvalue'); DROP TABLE table;-- value'); DROP TABLE table;-- 、クエリは次のようになります。

INSERT INTO `table` (`column`) VALUES('value'); DROP TABLE table;--')

これを防ぐために何ができるのでしょうか?


Answer #1

警告:この回答のサンプルコード(質問のサンプルコードのような)はPHPのmysql拡張を使用しています。これはPHP 5.5.0では廃止され、PHP 7.0.0では完全に削除されました。

最新バージョンのPHPを使用している場合、下記のmysql_real_escape_stringオプションは使用できなくなります(ただし、 mysqli::escape_stringは現代版と同等です)。 最近のmysql_real_escape_stringオプションは、PHPの古いバージョンのレガシーコードにのみ意味があります。

unsafe_variable特殊文字をエスケープするか、パラメータ化されたクエリを使用するかの2つのオプションがあります。 どちらもSQLインジェクションからあなたを守ります。 パラメータ化されたクエリはより良い方法であると考えられますが、使用する前にPHPで新しいmysql拡張に変更する必要があります。

最初に最初にエスケープする文字列を取り上げます。

//Connect

$unsafe_variable = $_POST["user-input"];
$safe_variable = mysql_real_escape_string($unsafe_variable);

mysql_query("INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

//Disconnect

mysql_real_escape_string関数の詳細も参照してください。

パラメータ化されたクエリを使用するには、 MySQL関数ではなくMySQLiを使用する必要があります。 あなたの例を書き直すには、次のようなものが必要です。

<?php
    $mysqli = new mysqli("server", "username", "password", "database_name");

    // TODO - Check that connection was successful.

    $unsafe_variable = $_POST["user-input"];

    $stmt = $mysqli->prepare("INSERT INTO table (column) VALUES (?)");

    // TODO check that $stmt creation succeeded

    // "s" means the database expects a string
    $stmt->bind_param("s", $unsafe_variable);

    $stmt->execute();

    $stmt->close();

    $mysqli->close();
?>

mysqli::prepareは、 mysqli::prepareが読んでみたいキー機能です。

また、他の人が示唆しているように、 PDOようなものを使って抽象化のレイヤーを立てることは便利で簡単です。

質問したケースはかなり単純なものであり、より複雑なケースではより複雑なアプローチが必要な場合があります。 特に:

  • ユーザの入力に基づいてSQLの構造を変更したい場合、パラメータ化されたクエリは助けにならず、必要なエスケープ処理はmysql_real_escape_string対象外mysql_real_escape_string 。 このような場合、「安全な」値だけが許可されるように、ユーザーの入力をホワイトリストに通す方がよいでしょう。
  • ある条件でユーザー入力からの整数を使用してmysql_real_escape_stringアプローチを使用すると、以下のコメントのPolynomialで説明されている問題が発生します。 整数は引用符で囲まれないので、この場合は扱いにくいので、ユーザ入力に数字だけが含まれていることを検証することで対処できます。
  • 私が気づいていない他のケースがありそうです。 thisは、あなたが遭遇する可能性があるより微妙な問題のいくつかに役立つリソースです。

Answer #2

PDOと準備されたクエリを使用します。

$connPDOオブジェクトです)

$stmt = $conn->prepare("INSERT INTO tbl VALUES(:id, :name)");
$stmt->bindValue(':id', $id);
$stmt->bindValue(':name', $name);
$stmt->execute();

Answer #3

準備された文とパラメータ化されたクエリを使用します。 これらは、パラメータとは別にデータベースサーバに送信され、解析されるSQL文です。 この方法では、攻撃者が悪質なSQLを挿入することは不可能です。

基本的にこれを達成するには2つの選択肢があります:

  1. PDO使用(サポートされているデータベースドライバの場合):

    $stmt = $pdo->prepare('SELECT * FROM employees WHERE name = :name');
    
    $stmt->execute(array('name' => $name));
    
    foreach ($stmt as $row) {
        // do something with $row
    }
    
  2. MySQLi使用(MySQL用):

    $stmt = $dbConnection->prepare('SELECT * FROM employees WHERE name = ?');
    $stmt->bind_param('s', $name); // 's' specifies the variable type => 'string'
    
    $stmt->execute();
    
    $result = $stmt->get_result();
    while ($row = $result->fetch_assoc()) {
        // do something with $row
    }
    

MySQL以外のデータベースに接続している場合、参照できるドライバ固有の2番目のオプションがありますpg_execute() PostgreSQLの場合はpg_execute()pg_execute()など)。 PDOは普遍的な選択肢です。

接続を正しく設定する

PDOを使用してMySQLデータベースにアクセスすると、 実際に準備されたステートメントはデフォルトでは使用されません 。 これを修正するには、プリペアドステートメントのエミュレーションを無効にする必要があります。 PDOを使用して接続を作成する例は次のとおりです。

$dbConnection = new PDO('mysql:dbname=dbtest;host=127.0.0.1;charset=utf8', 'user', 'pass');

$dbConnection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$dbConnection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

上記の例では、エラーモードは厳密には必要ではありませんが、追加することをお勧めします。 このようにして、何か問題が生じたときにスクリプトがFatal Error停止することはありません。 そして、それは開発者にPDOExceptionとしてthrowエラーをcatchする機会を与えます。

ただし、PDOにエミュレートされたプリペアドステートメントを無効にし、 実際にプリペアドステートメントを使用するように指示する最初のsetAttribute()行は必須です。 これにより、MySQLサーバに送信する前にPHPが文と値を解析しないようにします(攻撃者に悪意のあるSQLを挿入する機会を与えません)。

コンストラクタのオプションでcharsetを設定することはできますが、PHPの古いバージョン(<5.3.6)では、DSNのcharsetパラメータ暗黙のうちに無視されることに注意することが重要です。

説明

何が起こるかは、 prepare渡したSQL文が解析され、データベースサーバによってコンパイルされるということです。 パラメータ(上記の例では、 :nameような?または名前付きパラメータ)を指定することにより、データベースエンジンにフィルタリングする場所を指定します。 executeを呼び出すと、プリペアドステートメントは指定したパラメータ値と組み合わされます。

ここで重要なのは、パラメータ値がSQL文字列ではなく、コンパイルされた文と結合されていることです。 SQLインジェクションは、データベースに送信するSQLを作成するときに、悪質な文字列を含むようにスクリプトをトリックすることによって機能します。 したがって、実際のSQLをパラメータとは別に送信することで、意図しない何かで終わるリスクが制限されます。 プリペアドステートメントを使用するときに送信するパラメータはすべて文字列として扱われます(ただしデータベースエンジンではいくつかの最適化が行われるため、パラメータも数字になります)。 上記の例では、 $name変数に'Sarah'; DELETE FROM employeesが含まれていれば、 'Sarah'; DELETE FROM employeesからの'Sarah'; DELETE FROM employees結果は単に"'Sarah'; DELETE FROM employees"という文字列の検索になり、空のテーブルにはなりません。

プリペアドステートメントを使用するもう1つの利点は、同じセッションで同じステートメントを何度も実行すると、一度解析されてコンパイルされるだけで、速度が向上します。

ああ、あなたが挿入のためにそれを行う方法について尋ねたので、ここでは(PDOを使って)例を挙げます:

$preparedStatement = $db->prepare('INSERT INTO table (column) VALUES (:column)');

$preparedStatement->execute(array('column' => $unsafeValue));

動的なクエリにプリペアドステートメントを使用できますか?

クエリパラメータにはまだプリペアドステートメントを使用できますが、動的クエリ自体の構造はパラメータ化できず、特定のクエリ機能をパラメータ化できません。

これらの特定のシナリオでは、可能な値を制限するホワイトリストフィルタを使用することをお勧めします。

// Value whitelist
// $dir can only be 'DESC' otherwise it will be 'ASC'
if (empty($dir) || $dir !== 'DESC') {
   $dir = 'ASC';
}

Answer #4

あなたが何をしても、あなたの入力がまだmagic_quotesや他の何かのゴミにstripslashesれていないことを確認してください。必要ならば、それをstripslashesそれをstripslashesて消毒してください。


Answer #5

ご覧のとおり、人々はあなたが準備したステートメントを最大限に使うことを提案します。 間違いではありませんが、クエリがプロセスごとに1 回だけ実行されると、わずかなパフォーマンスの低下があります。

私はこの問題に直面していましたが、私は非常に洗練された方法でそれを解決したと思います - ハッカーが引用符を使わないようにする方法。 私はこれを、エミュレートされた準備文とともに使用しました。 私はあらゆる種類のSQLインジェクション攻撃を防ぐためにこれを使用しています。

私のアプローチ:

  • 入力が整数であることを期待するなら、 本当に整数であることを確認してください。 PHPのような可変型言語では、これは非常に重要です。 sprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);これは非常に単純で強力なソリューションsprintf("SELECT 1,2,3 FROM table WHERE 4 = %u", $input);

  • あなたが整数16進数から何かを期待するならば。 16進数の場合、すべての入力を完全にエスケープします。 C / C ++にはmysql_hex_string()という関数があり、PHPではbin2hex()使うことができます。

    mysql_real_escape_stringを使用してもPHPは同じ容量((2*input_length)+1)を割り当てなければならないため、エスケープされた文字列は元の長さの2倍のサイズになることを心配しないでください。

  • この16進法は、バイナリデータを転送するときによく使用されますが、SQLインジェクション攻撃を防ぐためにすべてのデータに使用しない理由はありません。 データには0xを前置するか、代わりにMySQL関数UNHEX使用する必要があります。

したがって、たとえば、次のクエリは次のようになります。

SELECT password FROM users WHERE name = 'root'

となります:

SELECT password FROM users WHERE name = 0x726f6f74

または

SELECT password FROM users WHERE name = UNHEX('726f6f74')

六角は完璧なエスケープです。 注射する方法はありません。

UNHEX関数と0x接頭辞の違い

コメントにいくつかの議論があったので、私はついにそれを明確にしたい。 これらの2つのアプローチは非常に似ていますが、いくつかの点で少し異なります。

** 0x **接頭辞は、 char、varchar、text、block、binaryなどのデータ列に対してのみ使用できます。
また、空の文字列を挿入しようとすると少し複雑になります。 あなたは完全に''で置き換える必要があります。そうしないと、エラーが発生します。

UNHEX()どのカラムで動作します。 空の文字列について心配する必要はありません。

16進法は攻撃として使用されることが多い

この16進法は、SQLインジェクション攻撃としてよく使われることに注意してください。整数は文字列のようなもので、 mysql_real_escape_stringだけでmysql_real_escape_stringます。 次に、引用符の使用を避けることができます。

たとえば、次のようなことをすればよい:

"SELECT title FROM article WHERE id = " . mysql_real_escape_string($_GET["id"])

攻撃はあなたに非常に簡単に注入することができます。 スクリプトから返された次の注入コードを考えてみましょう:

SELECT ... where id = -1 unionすべてのselect table_name from information_schema.tables

今度はテーブル構造を抽出するだけです:

SELECT ... WHERE id = -1共用体すべてselect information_name from column_name table_name = 0x61727469636c65

そして、必要なデータを選択してください。 それはクールではないですか?

しかし、注入可能なサイトのコーダが16 SELECT ... WHERE id = UNHEX('2d312075...3635')であれば、問合せは次のようになりますSELECT ... WHERE id = UNHEX('2d312075...3635')


Answer #6

パラメータ化されたSQLクエリを実行するには、 PDO (PHP Data Objects)を使用することをお勧めします。

これはSQLインジェクションから保護するだけでなく、クエリの処理速度も向上させます。

そして、 mysql_mysqli_ 、およびpgsql_関数ではなくPDOを使用することで、まれにデータベースプロバイダを切り替える必要があるため、アプリケーションをデータベースから少し抽象化します。


Answer #7

パラメータ化されたクエリと入力の検証が行なわれています。 mysql_real_escape_string()が使用されていても、SQLインジェクションが発生するシナリオはたくさんあります。

これらの例はSQLインジェクションに対して脆弱です。

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
RunQuery("SELECT userid, username FROM sql_injection_test LIMIT $offset, 10");

または

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
RunQuery("SELECT userid, username FROM sql_injection_test ORDER BY `$order`");

どちらの場合も、カプセル化を保護するために'を使用することはできません。

Source予期しないSQLインジェクション(エスケープ処理が不十分な場合)


Answer #8

私は、WebアプリケーションがSQLインジェクションに対して脆弱にならないように、3つの異なる方法を使用します。

  1. 使用mysql_real_escape_string()で事前定義された関数で、PHP、このコードは、次の文字にバックスラッシュを追加:\x00\n\r\'"\x1a。SQLインジェクションの可能性を最小限に抑えるために、入力値をパラメータとして渡します。
  2. 最も高度な方法は、PDOを使用することです。

これがあなたに役立つことを願っています。

以下のクエリを考えてみましょう。

$iId = mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

mysql_real_escape_string()はここで保護しません。クエリ内で変数の前後に一重引用符( '')を使用すると、これを防ぐことができます。これは以下の解決策です:

$iId = (int) mysql_real_escape_string("1 OR 1=1"); $sSql = "SELECT * FROM table WHERE id = $iId";

このquestionは、これquestionいくつかの良い答えがあります。

私は、PDOを使用することが最良の選択肢であることを示唆している。

編集:

mysql_real_escape_string()PHP 5.5.0以降では推奨されていません。mysqliまたはPDOを使用します。

mysql_real_escape_string()の代わりに

string mysqli_real_escape_string ( mysqli $link , string $escapestr )

例:

$iId = $mysqli->real_escape_string("1 OR 1=1");
$mysqli->query("SELECT * FROM table WHERE id = $iId");

Answer #9

私は数年前にこの小さな関数を書いた:

function sqlvprintf($query, $args)
{
    global $DB_LINK;
    $ctr = 0;
    ensureConnection(); // Connect to database if not connected already.
    $values = array();
    foreach ($args as $value)
    {
        if (is_string($value))
        {
            $value = "'" . mysqli_real_escape_string($DB_LINK, $value) . "'";
        }
        else if (is_null($value))
        {
            $value = 'NULL';
        }
        else if (!is_int($value) && !is_float($value))
        {
            die('Only numeric, string, array and NULL arguments allowed in a query. Argument '.($ctr+1).' is not a basic type, it\'s type is '. gettype($value). '.');
        }
        $values[] = $value;
        $ctr++;
    }
    $query = preg_replace_callback(
        '/{(\\d+)}/', 
        function($match) use ($values)
        {
            if (isset($values[$match[1]]))
            {
                return $values[$match[1]];
            }
            else
            {
                return $match[0];
            }
        },
        $query
    );
    return $query;
}

function runEscapedQuery($preparedQuery /*, ...*/)
{
    $params = array_slice(func_get_args(), 1);
    $results = runQuery(sqlvprintf($preparedQuery, $params)); // Run query and fetch results.   
    return $results;
}

これにより、1行のC#のString.Format形式で文を実行できます。

runEscapedQuery("INSERT INTO Whatever (id, foo, bar) VALUES ({0}, {1}, {2})", $numericVar, $stringVar1, $stringVar2);

変数型を考慮してエスケープします。テーブル、カラム名をパラメータ化しようとすると、すべての文字列が引用符で囲まれているため、無効な構文になります。

セキュリティ更新:以前のstr_replaceバージョンでは、ユーザーデータに{#}トークンを追加して注入を許可しました。このpreg_replace_callbackバージョンでは、置換にこれらのトークンが含まれていても問題は発生しません。


Answer #10

**警告:この回答に記載されているアプローチは、非常に特定のシナリオにのみ適用され、SQLインジェクション攻撃はインジェクションできることに依存しているわけではないため、安全ではありませんX=Y

攻撃者がPHPの$_GET変数またはURLのクエリ文字列を使ってフォームにハックしようとしている場合、セキュリティで保護されていない場合はそれらを捕まえることができます。

RewriteCond %{QUERY_STRING} ([0-9]+)=([0-9]+)
RewriteRule ^(.*) ^/track.php

ので1=12=21=22=11+1=2など、...攻撃のSQLデータベースへの一般的な質問です。また、多くのハッキングアプリケーションで使用されている可能性があります。

しかし、あなたはあなたのサイトから安全なクエリを書き換えてはならないことに注意する必要があります。上記のコードは、ハッキング固有の動的クエリ文字列を攻撃者のIPアドレスを格納するページに書き直すか、リダイレクトするか(ヒントに依存します)、またはCookie、履歴、ブラウザ、その他の機密情報情報を提供しているため、後でアカウントを禁止したり、当局に連絡したりすることで対処できます。


Answer #11

RedisMemcachedようなキャッシュエンジンを利用したい場合は、おそらくDALMPを選択することができます。それは純粋なMySQLi使用します。これを確認してください:PHPを使ったMySQLのDALMPデータベース抽象化レイヤー。

また、クエリを準備する前に引数を準備して、動的クエリを構築し、最終的には完全に準備されたクエリクエリを作成することができます。PHPを使用したMySQL用DALMPデータベース抽象化レイヤ。


Answer #12

PDOの使い方がわからない人mysql_には、非常にシンプルなPDOラッパーを1つ作成しました。これは、アプリケーションが行う必要があるすべての一般的なことをいかに簡単に行うことができるかを示すために存在します。PostgreSQL、MySQL、およびSQLiteで動作します。

基本的には、それを読んでPDO、それは簡単な形式で値を格納および取得するために作るために実際の生活の中で使用するためにPDOの機能を配置する方法を確認するためにあなたがしたいです。

私は単一の列が欲しい

$count = DB::column('SELECT COUNT(*) FROM `user`);

私は配列(キー=>値)の結果が必要です(つまり選択ボックスを作るため)

$pairs = DB::pairs('SELECT `id`, `username` FROM `user`);

私は単一の行の結果が欲しい

$user = DB::row('SELECT * FROM `user` WHERE `id` = ?', array($user_id));

私は結果の配列が欲しい

$banned_users = DB::fetch('SELECT * FROM `user` WHERE `banned` = ?', array(TRUE));

Answer #13

SQLインジェクションや他のSQLハックを防ぐ方法はたくさんあります。インターネット上で簡単に見つけることができます(Google検索)。もちろん、PDOも優れたソリューションの1つです。しかし、私はあなたにSQLインジェクションからの良いリンク防止を提案したいと思います。

SQLインジェクションとは何ですか?

SQLインジェクションのPHPマニュアル

PHPのSQLインジェクションと予防に関するMicrosoftの説明

MySQLとPHPでのSQLインジェクション防止のようなもの

さて、なぜあなたはSQLインジェクションからあなたのクエリを防ぐために必要があるのですか?

私は以下のような短い例でSQLインジェクションを防止しようとしているのはなぜですか?

ログイン認証の照合:

$query="select * from users where email='".$_POST['email']."' and password='".$_POST['password']."' ";

今、もし誰か(ハッカー)が

$_POST['email']= [email protected]' OR '1=1

パスワード何か....

クエリはシステム内でのみ解析されます。

$query="select * from users where email='[email protected]' OR '1=1';

他の部分は破棄されます。だから、どうなるの?承認されていないユーザー(ハッカー)は、パスワードを使わずに管理者としてログインできます。今、彼は管理者/電子メールの人ができることを何でもすることができます。SQLインジェクションが防止されないと、非常に危険です。


Answer #14

このPHP関数mysql_escape_string()を使用すると、すぐに良い予防を得ることができます。

例えば:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."'

mysql_escape_string - mysql_queryで使用する文字列をエスケープする

より多くの予防のために、最後に追加することができます...

wHERE 1=1   or  LIMIT 1

最後にあなたは:

SELECT * FROM users WHERE name = '".mysql_escape_string($name_from_html_form)."' LIMIT 1

Answer #15

この問題の簡単な代替方法は、データベース自体に適切な権限を与えることで解決できます。たとえば、mysqlデータベースを使用している場合は、端末または提供されたUIを使用してデータベースに入力し、次のコマンドを実行します。

 GRANT SELECT, INSERT, DELETE ON database TO [email protected]'localhost' IDENTIFIED BY 'password';

これにより、ユーザーは指定したクエリのみに限定されるように制限されます。削除権限を削除して、データがPHPページから起動されたクエリから削除されることはありません。もう1つは、mysqlが権限と更新をリフレッシュするように権限をフラッシュすることです。

FLUSH PRIVILEGES; 

詳細についてはflush

ユーザーの現在の権限を確認するには、次のクエリを実行します。

select * from mysql.user where User='username';

GRANT詳細をご覧ください。


Answer #16

可能であれば、パラメータの型をキャストします。しかし、int、bool、floatなどの単純な型でしか動作しません。

$unsafe_variable = $_POST['user_id'];

$safe_variable = (int)$unsafe_variable ;

mysqli_query($conn, "INSERT INTO table (column) VALUES ('" . $safe_variable . "')");

Answer #17

私はストアドプロシージャMySQLは5.0以降のプロシージャサポートをストアしていました)をセキュリティの観点から推奨します。利点は、

  1. ほとんどのデータベース(MySQLを含む)は、ユーザーアクセスがストアドプロシージャの実行に制限されるようにします。きめ細かなセキュリティアクセス制御は、特権攻撃のエスカレーションを防ぐのに役立ちます。これにより、侵害されたアプリケーションがデータベースに対してSQLを直接実行できなくなります。
  2. それらは、アプリケーションから未処理のSQLクエリを抽象化するので、データベース構造の情報はアプリケーションで利用できません。これにより、人々はデータベースの基礎構造を理解し、適切な攻撃を設計することが難しくなります。
  3. パラメータだけを受け入れるので、パラメータ化されたクエリの利点があります。もちろん、IMOの場合は、依然として入力をサニタイズする必要があります。特に、ストアドプロシージャ内で動的SQLを使用している場合は特にそうです。

欠点は -

  1. それら(ストアドプロシージャ)は維持するのが難しく、非常に迅速に乗算する傾向があります。これにより、これらの問題を管理できます。
  2. 動的クエリにはあまり適していません。動的コードをパラメータとして受け入れるように構築されていれば、多くの利点が否定されます。

Answer #18

良いアイデアはIdiorm ような「オブジェクト・リレーショナル・マッパー」Idiormです:

$user = ORM::for_table('user')
->where_equal('username', 'j4mie')
->find_one();

$user->first_name = 'Jamie';
$user->save();

$tweets = ORM::for_table('tweet')
    ->select('tweet.*')
    ->join('user', array(
        'user.id', '=', 'tweet.user_id'
    ))
    ->where_equal('user.username', 'j4mie')
    ->find_many();

foreach ($tweets as $tweet) {
    echo $tweet->text;
}

SQLインジェクションからだけでなく、構文エラーからもあなたを救うことができます!また、一度に複数の結果にアクションをフィルタリングしたり、複数の接続に適用するメソッド連鎖を持つモデルのコレクションをサポートします。





sql-injection