web-dev-qa-db-ja.com

PHPで安全なmysqlの準備済みステートメントを作成する方法は?

Phpでmysqlの準備済みステートメントを使用するのは初めてです。列を取得する準備済みステートメントを作成するのに助けが必要です。

別の列から情報を取得する必要があります。現在テストファイルでは、完全に安全ではない SQLステートメントを使用しています。

$qry = "SELECT * FROM mytable where userid='{$_GET['userid']}' AND category='{$_GET['category']}'ORDER BY id DESC"
$result = mysql_query($qry) or die(mysql_error()); 

誰かが私が準備したsecure mysqlステートメントを作成するのを手伝ってもらえますか?

ボーナス:準備されたステートメントは、速度を上げることも想定しています。 1つのページで3〜4回だけプリペアドステートメントを使用すると、全体的な速度が向上しますか?

26
chris

Mysqliを使用した例を次に示します(オブジェクト構文-必要に応じて、関数構文に変換するのはかなり簡単です)。

$db = new mysqli("Host","user","pw","database");
$stmt = $db->prepare("SELECT * FROM mytable where userid=? AND category=? ORDER BY id DESC");
$stmt->bind_param('ii', intval($_GET['userid']), intval($_GET['category']));
$stmt->execute();

$stmt->store_result();
$stmt->bind_result($column1, $column2, $column3);

while($stmt->fetch())
{
    echo "col1=$column1, col2=$column2, col3=$column3 \n";
}

$stmt->close();

また、バインドする変数を正確に指定する代わりに、(SELECT *で使用する)連想配列を取得する簡単な方法が必要な場合は、便利な関数を次に示します。

function stmt_bind_assoc (&$stmt, &$out) {
    $data = mysqli_stmt_result_metadata($stmt);
    $fields = array();
    $out = array();

    $fields[0] = $stmt;
    $count = 1;

    while($field = mysqli_fetch_field($data)) {
        $fields[$count] = &$out[$field->name];
        $count++;
    }
    call_user_func_array(mysqli_stmt_bind_result, $fields);
}

使用するには、bind_resultを呼び出す代わりに、それを呼び出すだけです。

$stmt->store_result();

$resultrow = array();
stmt_bind_assoc($stmt, $resultrow);

while($stmt->fetch())
{
    print_r($resultrow);
}
46
Amber

代わりにこれを書くことができます:

$qry = "SELECT * FROM mytable where userid='";
$qry.= mysql_real_escape_string($_GET['userid'])."' AND category='";
$qry.= mysql_real_escape_string($_GET['category'])."' ORDER BY id DESC";

しかし、準備済みステートメントを使用するには、 [〜#〜] pdo [〜#〜] のような汎用ライブラリを使用する方が良いでしょう。

<?php
/* Execute a prepared statement by passing an array of values */
$sth = $dbh->prepare('SELECT * FROM mytable where userid=? and category=? 
                      order by id DESC');
$sth->execute(array($_GET['userid'],$_GET['category']));
//Consider a while and $sth->fetch() to fetch rows one by one
$allRows = $sth->fetchAll(); 
?>

または、 mysqli を使用します

<?php
$link = mysqli_connect("localhost", "my_user", "my_password", "world");

/* check connection */
if (mysqli_connect_errno()) {
    printf("Connect failed: %s\n", mysqli_connect_error());
    exit();
}

$category = $_GET['category'];
$userid = $_GET['userid'];

/* create a prepared statement */
if ($stmt = mysqli_prepare($link, 'SELECT col1, col2 FROM mytable where 
                      userid=? and category=? order by id DESC')) {
    /* bind parameters for markers */
    /* Assumes userid is integer and category is string */
    mysqli_stmt_bind_param($stmt, "is", $userid, $category);  
    /* execute query */
    mysqli_stmt_execute($stmt);
    /* bind result variables */
    mysqli_stmt_bind_result($stmt, $col1, $col2);
    /* fetch value */
    mysqli_stmt_fetch($stmt);
    /* Alternative, use a while:
    while (mysqli_stmt_fetch($stmt)) {
        // use $col1 and $col2 
    }
    */
    /* use $col1 and $col2 */
    echo "COL1: $col1 COL2: $col2\n";
    /* close statement */
    mysqli_stmt_close($stmt);
}

/* close connection */
mysqli_close($link);
?>
11
Vinko Vrsalovic

私は他のいくつかの答えに同意します:

  • PHPの_ext/mysql_は、パラメーター化されたSQLステートメントをサポートしていません。
  • クエリパラメータは、SQLインジェクションの問題からの保護においてより信頼性が高いと見なされています。
  • mysql_real_escape_string()は、正しく使用すれば効果的ですが、コードを書く方が冗長です。
  • 一部のバージョンでは、国際文字セットに適切にエスケープされない文字のケースがあり、微妙な脆弱性が残っています。クエリパラメータを使用すると、これらのケースを回避できます。

パラメータはSQLクエリ内のリテラル値の代わりにしか使用されないため、クエリパラメータを使用する場合でも、SQLインジェクションには注意する必要があることにも注意してください。 SQLクエリを動的に構築し、テーブル名、列名、またはSQL構文の他の部分にPHP変数を使用する場合、この場合、クエリパラメータもmysql_real_escape_string()ヘルプも使用しません。 。 例えば:

_$query = "SELECT * FROM $the_table ORDER BY $some_column"; 
_

パフォーマンスについて:

  • 準備されたクエリを異なるパラメーター値で複数回実行すると、パフォーマンスが向上します。クエリの解析と準備のオーバーヘッドを回避できます。しかし、同じPHPリクエストで同じSQLクエリを何度も実行する必要がある頻度はどれくらいですか?
  • このパフォーマンス上の利点を利用できる場合でも、通常、オペコードキャッシングやデータキャッシングを効果的に使用するなど、パフォーマンスに対処するために実行できる他の多くのことと比較して、わずかな改善です。
  • 準備されたクエリharmsのパフォーマンスでさえいくつかのケースがあります。たとえば次の場合、オプティマイザはパラメータ値mightがワイルドカードで始まると想定する必要があるため、検索にインデックスを使用できると想定できません。

    _SELECT * FROM mytable WHERE textfield LIKE ?
    _
8
Bill Karwin

PHP(またはそのことについては他の言語)でのMySQLのセキュリティ)は、主に議論されている問題です。いくつかの優れたヒントを入手できる場所がいくつかあります。

私の意見では、最も重要な2つの項目は次のとおりです。

  • SQLインジェクション:PHPのmysql_real_escape_string()関数(または類似の関数)を使用して、すべてのクエリ変数をエスケープしてください。
  • 入力の検証:ユーザーの入力を信頼しないでください。入力を適切にサニタイズおよび検証する方法のチュートリアルについては、 this を参照してください。
2
James Skidmore

Mysqliを使用する場合-これは私にとって最良の解決策のようです- codesense_mysqli クラスのコピーをダウンロードすることを強くお勧めします。

これは、準備されたステートメントを使用すると古いmysql/phpインターフェースを1行または2行追加するだけで済むように、生のmysqliを使用するときに蓄積する残骸の大部分をまとめて非表示にする、きちんとした小さなクラスです。

1
Cruachan

準備されたステートメントは、プレーンな古いMysql/PHPインターフェースではサポートされていません。 [〜#〜] pdo [〜#〜] または mysqli が必要です。ただし、プレースホルダーを置き換えるだけの場合は、phpのmysql_queryマニュアルページで このコメントを確認 を使用します。

1
inerte

かなり遅いですが、これは誰かを助けるかもしれません:

/**
* Execute query method. Executes a user defined query
*
* @param string $query the query statement
* @param array(Indexed) $col_vars the column variables to replace parameters. The count value should equal the number of supplied parameters
*
* Note: Use parameters in the query then supply the respective replacement variables in the second method parameter. e.g. 'SELECT COUNT(*) as count FROM foo WHERE bar = ?'
*
* @return array
*/
function read_sql($query, $col_vars=null) {
    $conn = new mysqli('hostname', 'username', 'user_pass', 'dbname');
    $types = $variables = array();
    if (isset($col_vars)) {
        for ($x=0; $x<count($col_vars); $x++) {
            switch (gettype($col_vars[$x])) {
                case 'integer':
                    array_Push($types, 'i');
                        break;
                case 'double':
                    array_Push($types, 'd');
                    break;
                default:
                    array_Push($types, 's');
            }
            array_Push($variables, $col_vars[$x]);
        }
        $types = implode('', $types);
        $sql = $conn->prepare($query);
        $sql -> bind_param($types, ...$variables);
        $sql -> execute();
        $results = $sql -> get_result();
        $sql -> close();
    }else {
        $results = $conn->query($query) or die('Error: '.$conn->error);
    }
    if ($results -> num_rows > 0) {
        while ($row = $results->fetch_assoc()) {
            $result[] = $row;
        }
        return $result;
    }else {
        return null;
    }
}

その後、次のように関数を呼び出すことができます。

read_sql('SELECT * FROM mytable where userid = ? AND category = ? ORDER BY id DESC', array($_GET['userid'], $_GET['category']));
0
Adera