PHPでmysql_ *関数を使用すべきではないのはなぜですか?


2502

mysql_*関数を使用すべきではない理由の技術的な理由は何ですか?(例mysql_query()mysql_connect()またはmysql_real_escape_string())?

サイトで機能しているのに、なぜ他のものを使用する必要があるのですか?

サイトで機能しない場合、なぜ次のようなエラーが発生するのですか?

警告:mysql_connect():そのようなファイルまたはディレクトリはありません


次のようなエラー:致命的なエラー:
キャッチさ

21
非推奨にすることだけでそれらを回避するのに十分な理由
Sasa1234

回答:


2088

MySQL拡張:

  • 活発に開発されていない
  • されて正式に廃止予定(2013年6月発売)PHP 5.5のよう。
  • された削除、完全に(2015年12月発売)PHP 7.0のよう
    • つまり、2018年12月31日現在、サポートされているどのバージョンのPHPにも存在しません。サポートしているバージョンのPHPを使用している場合は、セキュリティの問題が修正されていないバージョンを使用しています。
  • OOインターフェースがない
  • 以下をサポートしません:
    • 非ブロッキング非同期クエリ
    • 準備されたステートメントまたはパラメーター化されたクエリ
    • ストアドプロシージャ
    • 複数のステートメント
    • 取引
    • 「新しい」パスワード認証方式(MySQL 5.6ではデフォルトでオン、5.7では必須)
    • MySQL 5.1以降の新機能のいずれか

これは非推奨であるため、使用するとコードの将来性が失われます。

準備されたステートメントのサポートの欠如は、外部データをエスケープして引用する方法が、個別の関数呼び出しで手動でエスケープするよりも明確でエラーが発生しにくいため、特に重要です。

SQL拡張機能の比較をご覧ください。


287
非推奨にすることだけでそれらを回避するのに十分な理由があります。彼らはいつかそこにはいませんし、あなたがそれらに依存しているなら、あなたは幸せではありません。残りは、古い拡張機能を使用して人々が学習することを妨げてきたものの単なるリストです。
Tim Post

111
非推奨は、誰もがそう思っているように見える魔法の弾ではありません。PHP自体はいつの日か存在しませんが、私たちは今日自由に使用できるツールに依存しています。ツールを変更する必要がある場合は、変更します。
オービットの

133
@LightnessRacesinOrbit —非推奨は特効薬ではありません。「これは問題があることを認識しているため、これ以上はサポートしません」というフラグです。将来のコードの検証を強化することは、非推奨の機能から離れる良い理由ですが、それだけではありません(または主要な機能でさえも)。ツールを変更するのは、より良いツールがあるからではなく、強制されているからではありません。(そして、強制される前にツールを変更するということは、コードが機能しなくなり、昨日修正する必要があるという理由だけで新しいツールを学習していないことを意味します。
クエンティン

18
準備されたステートメントの欠如について言及していないことの1つは、パフォーマンスの問題です。ステートメントを発行するたびに、MySQLデーモンがそれを理解できるように何かをコンパイルする必要があります。このAPIを使用すると、ループ内で同じクエリを200,000回発行すると、MySQLがそれを理解するためにクエリを200,000回コンパイルする必要があります。準備されたステートメントでは、一度コンパイルされ、その後、値はコンパイルされたSQLにパラメーター化されます。
Goldentoa11 2013

20
@symcbean、それは確かに準備されたステートメントをサポートしませ。それが非推奨の主な理由です。準備されたステートメントがない(使いやすい)場合、mysql拡張はSQLインジェクション攻撃の犠牲になります。
rustyx 2014

1287

PHPは、MySQLに接続するための3つの異なるAPIを提供します。これらはmysql(PHP 7で削除されました)、、mysqliおよびPDO拡張機能です。

mysql_*以前は非常に人気のあった機能ですが、その使用は推奨されなくなりました。ドキュメントチームはデータベースのセキュリティ状況について議論しており、一般的に使用されるext / mysql拡張から離れるようにユーザーを教育することはこれの一部です(php.internals:ext / mysqlの非推奨を確認してください)。

以降PHPの開発者チームは、生成することを決定とったE_DEPRECATEDているかどうか、ユーザーは、MySQLに接続する際のエラーをmysql_connect()mysql_pconnect()またはに組み込まれた暗黙的な接続機能ext/mysql

ext/mysql公式にPHP 5.5の時点で非推奨とされているPHP 7のように除去

赤い箱を見る?

あなたはどのに行くときmysql_*の機能のマニュアルページ、あなたはそれはもう使用すべきではない説明、赤いボックスを参照してください。

なぜ


離れることext/mysqlは、セキュリティだけでなく、MySQLデータベースのすべての機能にアクセスできることも重要です。

ext/mysqlMySQL 3.23用に構築されましたおり、追加されたものはごくわずかで、コードの保守が少し難しくなるこの古いバージョンとの互換性はほとんど維持されています。ext/mysqlincludeでサポートされていない不足している機能:(PHPマニュアルから)。

使用しない理由 mysql_*関数を

  • 活発に開発されていない
  • PHP 7で削除
  • OOインターフェースがない
  • 非ブロッキング非同期クエリをサポートしていません
  • 準備されたステートメントまたはパラメーター化されたクエリをサポートしていません
  • ストアドプロシージャをサポートしていません
  • 複数のステートメントをサポートしていません
  • トランザクションをサポートしていません
  • MySQL 5.1のすべての機能をサポートしていない

クエンティンの答えから引用された上記のポイント

準備されたステートメントのサポートの欠如は、外部データをエスケープして引用する方法が、個別の関数呼び出しで手動でエスケープするよりも明確でエラーが発生しにくいため、特に重要です。

SQL拡張機能比較をご覧ください。


非推奨の警告の抑制

コードがMySQLi/ に変換されている間、php.iniで除外するように設定することPDOE_DEPRECATEDエラーを抑制できますerror_reportingE_DEPRECATED:

error_reporting = E_ALL ^ E_DEPRECATED

これにより、他の非推奨の警告も非表示になることに注意してください。ただし、MySQL以外のものである可能性があります。(PHPマニュアルから

記事PDOとMySQLi:どちらを使用すればよいですか?Dejan Marjanovicによってあなたが選択するのに役立ちます。

そしてより良い方法はPDOであり、私は今簡単なPDOチュートリアルを書いています。


シンプルで短いPDOチュートリアル


Q.最初に頭に浮かんだ質問は、「PDO」とは何ですか。

A.「PDO – PHPデータオブジェクト –は、複数のデータベースへの統一的なアクセス方法を提供するデータベースアクセスレイヤーです。」

代替テキスト


MySQLへの接続

mysql_*関数を使用するか、古い方法と言えます(PHP 5.5以降では非推奨)

$link = mysql_connect('localhost', 'user', 'pass');
mysql_select_db('testdb', $link);
mysql_set_charset('UTF-8', $link);

ありPDO:新しいPDOオブジェクトを作成するだけです。コンストラクターは、データベースソースPDOのコンストラクターを指定するためのパラメーターを受け入れます。ほとんどのDSN場合username、(データソース名)とオプションでの4つのパラメーターを取りますpassword

ここで私はあなたが以外のすべてに精通していると思いますDSN。これはの新機能ですPDO。A DSNは基本的に、PDO使用するドライバーと接続の詳細を指示する一連のオプションです。詳細については、PDO MySQL DSNを確認してください。

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'username', 'password');

注:を使用することもできますがcharset=UTF-8、エラーが発生する場合があるため、を使用することをお勧めしますutf8

接続エラーがある場合は、PDOExceptionキャッチしてExceptionさらに処理できるオブジェクトをスローします。

お読みください接続と接続管理¶

いくつかのドライバーオプションを配列として4番目のパラメーターに渡すこともできます。PDO例外モードにするパラメーターを渡すことをお勧めします。一部のPDOドライバーはネイティブの準備済みステートメントをサポートしていないためPDO、準備のエミュレーションを実行します。また、このエミュレーションを手動で有効にすることもできます。ネイティブのサーバー側の準備済みステートメントを使用するには、明示的に設定する必要がありますfalse

もう1つは、MySQLドライバでデフォルトで有効になっている準備エミュレーションをオフにするPDOことですが、安全に使用するには準備エミュレーションをオフにする必要があります。

準備エミュレーションをオフにする必要がある理由については後で説明します。理由を見つけるには、この投稿をチェックしください。

古いバージョンのを使用している場合にのみ使用できます MySQL私がお勧めしない。

以下にその方法の例を示します。

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password',
              array(PDO::ATTR_EMULATE_PREPARES => false,
              PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION));

PDOの構築後に属性を設定できますか?

はいsetAttributeメソッドを使用してPDOを構築した後にいくつかの属性を設定することもできます:

$db = new PDO('mysql:host=localhost;dbname=testdb;charset=UTF-8', 
              'username', 
              'password');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);

エラー処理


では、エラー処理がはるかに簡単PDOですmysql_*

使用時の一般的な方法mysql_*は次のとおりです。

//Connected to MySQL
$result = mysql_query("SELECT * FROM table", $link) or die(mysql_error($link));

OR die()で処理できないため、エラーを処理するのに適した方法ではありませんdie。スクリプトを突然終了し、通常はエンドユーザーに表示したくないエラーを画面にエコーし、血まみれのハッカーにスキーマを発見させます。または、mysql_*関数の戻り値をmysql_error()と組み合わせて使用​​してエラーを処理することもできます。

PDOより良いソリューションを提供します:例外。我々がやる何かPDOに包まれるべきtry- catchブロック。PDOエラーモード属性を設定することで、3つのエラーモードのいずれかに強制できます。3つのエラー処理モードを以下に示します。

  • PDO::ERRMODE_SILENT。エラーコードを設定するだけで、mysql_*各結果を確認して$db->errorInfo();からエラーの詳細を取得する必要がある場合とほぼ同じように動作します。
  • PDO::ERRMODE_WARNING上げるE_WARNING。(実行時警告(致命的でないエラー)。スクリプトの実行は停止されません。)
  • PDO::ERRMODE_EXCEPTION:例外をスローします。PDOによって発生したエラーを表します。PDOException独自のコードからをスローしないでください。PHPの例外の詳細については、例外を参照してください。or die(mysql_error());キャッチされない場合は、のように機能します。ただし、とは異なりor die()、そうするPDOExceptionことを選択した場合、をキャッチして適切に処理できます。

良い読み

お気に入り:

$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING );
$stmt->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION );

そして、以下のようにtry- catchでラップすることができます:

try {
    //Connect as appropriate as above
    $db->query('hi'); //Invalid query!
} 
catch (PDOException $ex) {
    echo "An Error occured!"; //User friendly message/message you want to show to user
    some_logging_function($ex->getMessage());
}

try-で処理する必要はありませんcatch。いつでも適切にキャッチできますが、try- を使用することを強くお勧めしますcatch。また、PDOものを呼び出す関数の外でそれをキャッチするほうが理にかなっているかもしれません:

function data_fun($db) {
    $stmt = $db->query("SELECT * FROM table");
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

//Then later
try {
    data_fun($db);
}
catch(PDOException $ex) {
    //Here you can handle error and show message/perform action you want.
}

また、で対応しor die()たりmysql_*、のように言ったりできますが、実際にはさまざまです。display_errors offエラーログを回して読むだけで、本番環境の危険なエラーメッセージを非表示にできます。

さて、上記のすべてのものを読んだ後、あなたはおそらく考えている:一体何であるかという、私は単純な傾い開始したい時にSELECTINSERTUPDATE、またはDELETEステートメントを?心配しないで、ここに行きます:


データの選択

PDO選択画像

だからあなたがやっていることmysql_*は:

<?php
$result = mysql_query('SELECT * from table') or die(mysql_error());

$num_rows = mysql_num_rows($result);

while($row = mysql_fetch_assoc($result)) {
    echo $row['field1'];
}

これでPDO、次のように実行できます。

<?php
$stmt = $db->query('SELECT * FROM table');

while($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    echo $row['field1'];
}

または

<?php
$stmt = $db->query('SELECT * FROM table');
$results = $stmt->fetchAll(PDO::FETCH_ASSOC);

//Use $results

:以下のようなメソッド(query())を使用している場合、このメソッドはPDOStatementオブジェクトを返します。したがって、結果をフェッチしたい場合は、上記のように使用します。

<?php
foreach($db->query('SELECT * FROM table') as $row) {
    echo $row['field1'];
}

PDOデータで->fetch()は、ステートメントハンドルのメソッドであるを介して取得されます。fetchを呼び出す前に、最善の方法は、データをどのようにフェッチするかをPDOに指示することです。以下のセクションでは、これについて説明します。

フェッチモード

上記PDO::FETCH_ASSOCfetch()およびfetchAll()コードでの使用に注意してください。これはPDO、フィールド名をキーとする連想配列として行を返すように指示します。他にも多くのフェッチモードがありますが、これらについては1つずつ説明します。

まず、フェッチモードの選択方法を説明します。

 $stmt->fetch(PDO::FETCH_ASSOC)

上記では、を使用していfetch()ます。次のものも使用できます。

今私はフェッチモードに入ります:

  • PDO::FETCH_ASSOC:結果セットで返された列名でインデックス付けされた配列を返します
  • PDO::FETCH_BOTH (デフォルト):結果セットで返されるように、列名と0でインデックス付けされた列番号の両方でインデックス付けされた配列を返します

さらに選択肢があります!PDOStatementFetchのドキュメントでそれらすべてについて読んでください

行数の取得

を使用mysql_num_rowsして、返された行数を取得する代わりに、次のようにa PDOStatementおよびdoを取得できますrowCount()

<?php
$stmt = $db->query('SELECT * FROM table');
$row_count = $stmt->rowCount();
echo $row_count.' rows selected';

最後に挿入されたIDの取得

<?php
$result = $db->exec("INSERT INTO table(firstname, lastname) VAULES('John', 'Doe')");
$insertId = $db->lastInsertId();

ステートメントの挿入と更新または削除

PDO画像を挿入して更新する

mysql_*関数で実行していることは次のとおりです。

<?php
$results = mysql_query("UPDATE table SET field='value'") or die(mysql_error());
echo mysql_affected_rows($result);

そしてpdoでは、これと同じことを次のようにして行うことができます:

<?php
$affected_rows = $db->exec("UPDATE table SET field='value'");
echo $affected_rows;

上記のクエリではPDO::exec、SQLステートメントを実行し、影響を受けた行の数を返します。

挿入と削除については後で説明します。

上記の方法は、クエリで変数を使用していない場合にのみ役立ちます。ただし、クエリで変数を使用する必要がある場合は、これまでに説明したようなことを行わないでください。準備されたステートメントまたはパラメーター化されたステートメントが存在し ます


準備されたステートメント

Q.準備済みステートメントとは何ですかなぜ必要なのですか。
A.準備されたステートメントは、データのみをサーバーに送信することで複数回実行できるプリコンパイルされたSQLステートメントです。

準備済みステートメントを使用する一般的なワークフローは次のとおりです(Wikipediaの3つの3ポイントから引用)。

  1. 準備:ステートメントテンプレートはアプリケーションによって作成され、データベース管理システム(DBMS)に送信されます。特定の値は、パラメーター、プレースホルダー、またはバインド変数(?以下にラベルが付けられています)と呼ばれる、未指定のままになります。

    INSERT INTO PRODUCT (name, price) VALUES (?, ?)

  2. DBMSは、ステートメントテンプレートでクエリの最適化を解析、コンパイル、および実行し、実行せずに結果を保存します。

  3. 実行:後で、アプリケーションがパラメーターの値を提供(またはバインド)し、DBMSがステートメントを実行します(結果を返す可能性があります)。アプリケーションは、ステートメントを異なる値で何回でも実行できます。この例では、最初のパラメーターと1.002番目のパラメーターに「パン」を提供する場合があります。

SQLにプレースホルダーを含めることにより、準備済みステートメントを使用できます。基本的に、プレースホルダーのないもの(変数の上にある変数を使用しないでください)、名前のないプレースホルダーがあるもの、名前のあるプレースホルダーがあるものの3つがあります。

Q.では、名前付きプレースホルダーとは何ですか、またどのように使用するのですか?
A.名前付きプレースホルダー。疑問符ではなく、コロンを前に付けた説明的な名前を使用します。名前プレースホルダー内の値の位置/順序は関係ありません。

 $stmt->bindParam(':bla', $bla);

bindParam(parameter,variable,data_type,length,driver_options)

実行配列を使用してバインドすることもできます。

<?php
$stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
$stmt->execute(array(':name' => $name, ':id' => $id));
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

OOP友人にとってもう1つの優れた機能は、名前付きプレースホルダーが、プロパティが名前付きフィールドと一致すると想定して、オブジェクトをデータベースに直接挿入できることです。例えば:

class person {
    public $name;
    public $add;
    function __construct($a,$b) {
        $this->name = $a;
        $this->add = $b;
    }

}
$demo = new person('john','29 bla district');
$stmt = $db->prepare("INSERT INTO table (name, add) value (:name, :add)");
$stmt->execute((array)$demo);

Q.では、名前のないプレースホルダーとは何ですか、またどのように使用しますか?
A.例を挙げましょう。

<?php
$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->bindValue(1, $name, PDO::PARAM_STR);
$stmt->bindValue(2, $add, PDO::PARAM_STR);
$stmt->execute();

そして

$stmt = $db->prepare("INSERT INTO folks (name, add) values (?, ?)");
$stmt->execute(array('john', '29 bla district'));

上記で?は、名前プレースホルダーのように名前の代わりにそれらを見ることができます。最初の例では、変数をさまざまなプレースホルダーに割り当てます($stmt->bindValue(1, $name, PDO::PARAM_STR);)。次に、それらのプレースホルダーに値を割り当て、ステートメントを実行します。2番目の例では、最初の配列要素が最初の配列要素に移動し?、2番目の配列要素が2番目の配列要素に移動し?ます。

名前のないプレースホルダーでは、PDOStatement::execute()メソッドに渡す配列内の要素の適切な順序に注意する必要があります。


SELECTINSERTUPDATEDELETEクエリを準備

  1. SELECT

    $stmt = $db->prepare("SELECT * FROM table WHERE id=:id AND name=:name");
    $stmt->execute(array(':name' => $name, ':id' => $id));
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
  2. INSERT

    $stmt = $db->prepare("INSERT INTO table(field1,field2) VALUES(:field1,:field2)");
    $stmt->execute(array(':field1' => $field1, ':field2' => $field2));
    $affected_rows = $stmt->rowCount();
  3. DELETE

    $stmt = $db->prepare("DELETE FROM table WHERE id=:id");
    $stmt->bindValue(':id', $id, PDO::PARAM_STR);
    $stmt->execute();
    $affected_rows = $stmt->rowCount();
  4. UPDATE

    $stmt = $db->prepare("UPDATE table SET name=? WHERE id=?");
    $stmt->execute(array($name, $id));
    $affected_rows = $stmt->rowCount();

注意:

ただしPDOMySQLi完全に安全ではありません。回答を確認してくださいPDOの準備済みステートメントはSQLインジェクションを防ぐのに十分ですか?によってircmaxell。また、私は彼の答えから一部を引用しています:

$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$pdo->query('SET NAMES GBK');
$stmt = $pdo->prepare("SELECT * FROM test WHERE name = ? LIMIT 1");
$stmt->execute(array(chr(0xbf) . chr(0x27) . " OR 1=1 /*"));

15
上記の良い読みが適切に言及すべきこと:準備されたステートメントはの意味のある使用を取り除きますIN (...) construct
Eugen Rieck 2013

24
問題は、「PHPでmysql_ *関数を使用すべきでない理由」でした。この回答は印象的で役立つ情報でいっぱいですが、範囲外であり、@ trejderが言うように-10人のうち8人は、4時間を費やして作業に費やすことができないために、その情報を見逃すことになります。それ。これははるかに価値があり、いくつかのより正確な質問への回答として使用されます。
Alex McMillan

私はmysqliとPDOを好みます。しかし、ダイ処理のために、私は例外の代替を試みました例外 function throwEx() { throw new Exception("You did selected not existng db"); } mysql_select_db("nonexistdb") or throwEx();をスローするために機能します。
kuldeep.kamboj 2016年

Doesn't support non-blocking, asynchronous queriesmysql_を使用しない理由としてリストする-PDOもサポートしていないため、PDOを使用しない理由としてもリストする必要があります。(ただし、MySQLiがサポート)
hanshenrik '25

これを使用しているデータベースがあるので、Charset utf8mb4_unicode_ciを使用することは可能ですか?
ライアンストーン

301

まず、全員に与える標準的なコメントから始めましょう。

mysql_*新しいコードでは関数を使用しないでください。それらはもはや保守されておらず、正式に非推奨になっています。赤い箱見えますか?学ぶプリペアドステートメントの代わりに、および使用 PDOまたは MySQLiをする -この記事は、あなたがどれを決めるのに役立ちます。PDOを選択した場合こちらが良いチュートリアルです。

文章ごとに説明し、説明しましょう。

  • それらはもはや保守されておらず、正式に非推奨になっています

    これは、PHPコミュニティがこれらの非常に古い関数のサポートを段階的に廃止していることを意味します。PHPの将来の(最近の)バージョンには存在しない可能性があります!これらの関数を継続して使用すると、(そうではない)遠い将来にコードが壊れる可能性があります。

    新着!-ext / mysqlがPHP 5.5で正式に非推奨になりました!

    新しい!ext / mysql はPHP 7で削除されました

  • 代わりに、準備されたステートメントについて学ぶ必要があります

    mysql_*拡張機能は、準備されたステートメントをサポートしていません。これは、(とりわけ)SQLインジェクションに対する非常に効果的な対策です。MySQLに依存するアプリケーションの非常に深刻な脆弱性を修正しました。これにより、攻撃者がスクリプトにアクセスし、データベースで可能なクエリを実行する可能性があります

    詳細については、「PHPでSQLインジェクションを防ぐにどうすればよいですか?」を参照してください

  • 赤い箱を見る?

    あなたがいずれかに行くときmysqlの機能のマニュアルページ、あなたはそれはもう使用すべきではない説明、赤いボックスを参照してください。

  • PDOまたはMySQLiを使用する

    より優れた、より堅牢でよく構築された代替手段、PDO-PHPデータベースオブジェクト(データベースの相互作用に対する完全なOOPアプローチを提供)、およびMySQLi(MySQL固有の改善)があります。


6
もう1つあります。機能がPHPに残っているのは1つの理由だけだと思います-古くて時代遅れですが、まだ実行中のCMS、eコマース、掲示板システムなどとの互換性があります。最後に削除され、書き換える必要がありますアプリケーション...
Kamil

4
@カミル:それは本当ですが、それがあなたがそれを使うべきでない理由ではありません など、それは古代のだからそれは使用しない理由は、安全でない、:)
マダラのゴースト

4
@Mario-PHP開発者にはプロセスがあり、5.5の時点でext / mysqlを正式に廃止することを支持して投票しました。それはもはや架空の問題ではありません。
SDC

2
PDOやMySQLiなどの実績のある手法を使用して2、3行を追加すると、PHPが常に提供していた使いやすさが向上します。開発者のために、チュートリアルでこれらのひどいmysql_ *関数を見ると実際にレッスンが損われることを知っており、この種のコードは10年前にすごすぎたことをOPに伝える必要があります。チュートリアルの関連性も!
FredTheWebGuy

1
答えが適切に言及すべきこと:準備されたステートメントは、の意味のある使用を取り除きますIN (...) construct
Eugen Rieck 2013

217

使いやすさ

分析的および合成的な理由はすでに述べられています。初心者にとっては、日付が付けられたmysql_関数の使用をやめるより大きなインセンティブがあります。

現代のデータベースAPIは使いやすいだけです

コードを簡略化できるのは、主にバインドされたパラメーターです。そして、で優れたチュートリアル(上で見たように)への移行PDOは過度に困難ではありません。

ただし、大きなコードベースを一度に書き換えるには時間がかかります。この中間的な代替案の存在理由:

等価PDO_ *の代わりに機能mysql_ *

< pdo_mysql.php >を使用すると、最小限の労力で古いmysql_関数から切り替えることができます。対応するpdo_関数ラッパーを追加しますmysql_

  1. 単に、データベースと対話する必要のある各呼び出しスクリプトで。 include_once("pdo_mysql.php");

  2. あらゆる場所mysql_関数の接頭辞を削除し、それをに置き換えます。pdo_

    • mysql_connect() なる pdo_connect()
    • mysql_query() なる pdo_query()
    • mysql_num_rows() なる pdo_num_rows()
    • mysql_insert_id() なる pdo_insert_id()
    • mysql_fetch_array() なる pdo_fetch_array()
    • mysql_fetch_assoc() なる pdo_fetch_assoc()
    • mysql_real_escape_string() なる pdo_real_escape_string()
    • 等々...

  3. コードは同じように動作しますが、ほとんど同じように見えます。

    include_once("pdo_mysql.php"); 
    
    pdo_connect("localhost", "usrABC", "pw1234567");
    pdo_select_db("test");
    
    $result = pdo_query("SELECT title, html FROM pages");  
    
    while ($row = pdo_fetch_assoc($result)) {
        print "$row[title] - $row[html]";
    }

Etvoilà。
コードでPDO を使用しています。
今こそ実際に活用する時です。

バインドされたパラメーターは使いやすい

扱いにくいAPIが必要なだけです。

pdo_query()バインドされたパラメータの非常に簡単なサポートを追加します。古いコードの変換は簡単です:

変数をSQL文字列の外に移動します。

  • それらをカンマ区切りの関数パラメーターとしてに追加しますpdo_query()
  • ?変数が以前にあった場所のプレースホルダーとして疑問符を配置します。
  • '以前に文字列値/変数を囲んでいた一重引用符を取り除きます。

コードが長いほど、利点は明白になります。

多くの場合、文字列変数はSQLに補間されるだけでなく、その間にエスケープ呼び出しが連結されます。

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title='" . pdo_real_escape_string($title) . "' OR id='".
   pdo_real_escape_string($title) . "' AND user <> '" .
   pdo_real_escape_string($root) . "' ORDER BY date")

?プレースホルダあなたはそれを気にする必要はありません適用されます。

pdo_query("SELECT id, links, html, title, user, date FROM articles
   WHERE title=? OR id=? AND user<>? ORDER BY date", $title, $id, $root)

pdo_ *でも、またはのいずれかを使用できることに注意してください。
変数エスケープせず、同じクエリにバインドしないでください。

  • プレースホルダー機能は、その背後にある実際のPDOによって提供されます。
  • したがって、:named後でプレースホルダリストも使用できます。

さらに重要なこととして、$ _ REQUEST []変数をクエリの背後で安全に渡すことができます。送信された<form>フィールドがデータベース構造と完全に一致する場合、さらに短くなります。

pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);

とてもシンプルです。しかし、なぜあなたがmysql_脱出して脱出したいのかについて、いくつかの書き直しのアドバイスと技術的な理由に戻りましょう。

オールドスクールsanitize()機能を修正または削除する

バインドされたパラメータmysql_を使用pdo_queryしてすべての呼び出しをに変換したら、すべての冗長なpdo_real_escape_string呼び出しを削除します。

特に、日付付きのチュートリアルで宣伝されているsanitizeor cleanまたはfilterThisor clean_data機能をいずれかの形式で修正する必要があります。

function sanitize($str) {
   return trim(strip_tags(htmlentities(pdo_real_escape_string($str))));
}

ここで最も目立つバグは、ドキュメントの欠如です。さらに重要なことに、フィルタリングの順序は正確に間違った順序でした。

  • 正しい順序は次のとおりです。非推奨stripslashesに、最も内側の呼び出しとしてtrim、その後strip_tagshtmlentities出力コンテキストに対して、そして最後に、_escape_stringアプリケーションがSQLインターパースの直前にあるべきです。

  • しかし、最初のステップとして_real_escape_string呼び出しを取り除くだけです

  • sanitize()データベースとアプリケーションフローがHTMLコンテキストセーフな文字列を想定している場合は、今のところ残りの関数を保持する必要があるかもしれません。今後はHTMLエスケープのみが適用されるというコメントを追加します。

  • 文字列/値の処理は、PDOとそのパラメーター化されたステートメントに委任されます。

  • stripslashes()サニタイズ機能で言及があった場合は、より高いレベルの監視を示している可能性があります。

    • これは一般的に、廃止されmagic_quotesたからのダメージ(ダブルエスケープ)を元に戻すためにありました。ただし、文字列ごとではなく、中央修正するの最適です。

    • ユーザーランドの反転アプローチのいずれかを使用します。次にstripslashes()sanitize関数内のを削除します。

    magic_quotesに関する歴史的なメモ。その機能は当然非推奨です。ただし、セキュリティ機能が失敗したと誤って表現されることがよくあります。しかし、magic_quotesは、栄養源として失敗したテニスボールと同じくらい、失敗したセキュリティ機能です。それは単に彼らの目的ではありませんでした。

    PHP2 / FIの元の実装では、「引用符が自動的にエスケープされ、フォームデータをmsqlクエリに直接渡すのが簡単になる」というだけで明示的に導入されました。特に、mSQLはASCIIのみをサポートしていたため、誤って使用しても安全でした。
    次に、PHP3 / ZendはMySQLのmagic_quotesを再導入し、それを誤って文書化しました。しかし、もともとは便利な機能であり、セキュリティを目的としたものではありませんでした。

準備されたステートメントの違い

文字列変数をSQLクエリにスクランブルすると、従うのが複雑になるだけではありません。また、MySQLがコードとデータを再度分離することは、余分な労力です。

SQLインジェクションは、単にデータがコードコンテキストに流れ込んだときです。データベースサーバーは、PHPが最初にクエリ句の間に変数を接着した場所を後で見つけることはできません。

バインドされたパラメーターを使用して、SQLコードとSQ​​Lコンテキスト値をPHPコードで分離します。しかし、それは舞台裏で再びシャッフルされません(PDO :: EMULATE_PREPARESを除く)。データベースは、変化しないSQLコマンドと1:1の変数値を受け取ります。

この回答は、ドロップの読みやすさの利点に注意する必要があることを強調していますmysql_。この可視的で技術的なデータ/コードの分離により、パフォーマンス上の利点(値が異なるだけのINSERTの繰り返し)も時々あります。

パラメータのバインドは、すべての SQLインジェクションに対する魔法のワンストップソリューションではないことに注意してください。データ/値の最も一般的な用途を処理します。ただし、列名/テーブル識別子をホワイトリストに登録したり、動的な句の作成に役立てたり、単純な配列値リストを作成したりすることはできません。

ハイブリッドPDOの使用

これらのpdo_*ラッパー関数は、コーディングに適したストップギャップAPIを作成します。(それMYSQLIが特異な関数のシグネチャシフトに対応していなかったとしたら、それはほぼ可能でした)。また、ほとんどの場合、実際のP​​DOを公開します。
書き換えは、新しいpdo_関数名の使用で停止する必要はありません。各pdo_query()を1つずつ単純な$ pdo-> prepare()-> execute()呼び出しに移行できます。

ただし、再度簡略化することから始めるのが最善です。たとえば、一般的な結果のフェッチ:

$result = pdo_query("SELECT * FROM tbl");
while ($row = pdo_fetch_assoc($result)) {

foreachイテレーションだけで置き換えることができます。

foreach ($result as $row) {

または、よりよく、直接かつ完全な配列検索:

$result->fetchAll();

ほとんどの場合、PDOまたはmysql_がクエリの失敗後に通常提供するよりも役立つ警告が表示されます。

その他のオプション

ですから、これはいくつかの実際的な理由と価値のあるドロップの経路をうまく視覚化したものmysql_です。

に切り替えるだけ 完全にそれをカットしていません。pdo_query()それは単なるフロントエンドでもあります。

パラメータバインディングを導入したり、より優れたAPIから何かを利用したりできる場合を除いて、それは無意味なスイッチです。初心者を落胆させないように、シンプルに描かれているといいのですが。(教育は通常禁止よりもうまく機能します。)

それは、最も単純なものである可能性のある作業カテゴリの対象ですが、それでも非常に実験的なコードです。週末に書いただけです。ただし、選択肢はたくさんあります。PHPデータベースの抽象化のためにグーグルし、少し閲覧します。そのようなタスクのための優れたライブラリーはたくさんあります。

データベースの相互作用をさらに単純化したい場合は、Paris / Idiormのようなマッパーが試してみる価値があります。JavaScriptで平凡なDOMを使用する人がいないのと同じように、現在、生のデータベースインターフェースをベビーシットする必要はありません。


8
pdo_query("INSERT INTO pages VALUES (?,?,?,?,?)", $_POST);機能に注意してくださいpdo_query("INSERT INTO users VALUES (?, ?, ?), $_POST); $_POST = array( 'username' => 'lawl', 'password' => '123', 'is_admin' => 'true');

@Tom確かに、あまりメンテナンスされていませんが(0.9.2が最後でした)、化石のアカウントを作成したり、Wikiに追加したり、バグ報告を提出したりできます(登録IIRCなし)。
マリオ

pdo_real_escape_string() <-これは実際の機能でもありますが、ドキュメントは見つかりませんか?このソースを投稿してください。
ライアンストーン

144

mysql_機能:

  1. 古くなっています-それらはもう維持されていません
  2. 別のデータベースバックエンドに簡単に移動できないようにする
  3. 準備されたステートメントをサポートしないため、
  4. プログラマーにクエリを作成するために連結を使用するように勧め、SQLインジェクションの脆弱性を引き起こします

18
#2も同様に当てはまるmysqli_
12:13

16
公平を期すために、SQL方言のバリエーションを考えると、PDOでもある程度の確実性はありません。そのためには適切なORMラッパーが必要です。
SDC 2012年

このmysql_*関数は、新しいPHPバージョンのmysqlnd関数へのシェルです。したがって、古いクライアントライブラリが維持されなくなっても、mysqlndは維持されます:)
2013年

問題は、多くのWebホスティングプロバイダーがそのようなオブジェクト指向のデザインスタイルをサポートできないことです。これは、古いphpバージョンのためです
Raju yourPepe

@RajuGujaratiなので、できるウェブホストを見つけてください。あなたのウェブホストがそうしなければ、彼らのサーバーへの攻撃に対して脆弱である可能性が非常に高いです。
Alnitak 2013

106

技術的な理由といえば、ごく少数であり、非常に具体的でほとんど使用されていません。ほとんどの場合、あなたは自分の人生でそれらを決して使用しないでしょう。
たぶん私は無知かもしれませんが、私はそれらのようなものを使用する機会がありませんでした

  • 非ブロッキング非同期クエリ
  • 複数の結果セットを返すストアドプロシージャ
  • 暗号化(SSL)
  • 圧縮

あなたがそれらを必要とする場合-これらは間違いなくmysql拡張からよりスタイリッシュでモダンな外観に移行する技術的な理由です。

それにもかかわらず、いくつかの非技術的な問題もあり、経験を少し難しくする可能性があります

  • これらの関数を最新のPHPバージョンでさらに使用すると、非推奨レベルの通知が表示されます。単にオフにすることができます。
  • 将来的には、デフォルトのPHPビルドから削除される可能性があります。mydsql extがPECLに移動され、すべてのホスティング業者がサイトを何十年も働いていたクライアントを失いたくないので、喜んでPHPをコンパイルできるので、それほど大したことではありません。
  • Stackoverflowコミュニティからの強い抵抗。theseこれらの正直な機能に言及するたびに、厳密なタブーにさらされていると言われます。
  • 平均的なPHPユーザーである場合、これらの関数を使用するという考えはエラーが発生しやすく、間違っている可能性があります。間違った方法を教えるこれらの多数のチュートリアルとマニュアルがあるからといって。関数自体ではなく、強調する必要がありますが、それらの使用方法です。

この後者の問題は問題です。
しかし、私の意見では、提案されたソリューションも優れています。これらのすべてのPHPユーザーがSQLクエリを一度に適切に処理する方法を学ぶ
ことは、あまりにも理想的な夢のようです。ほとんどの場合、彼らはmysql_ *をmysqli_ *に機械的に変更するだけで、アプローチは同じままです。特に、mysqliは準備されたステートメントの使用を信じられないほど苦痛で面倒にするためです。
言うまでもなく、ネイティブの準備されたステートメント SQLインジェクションから保護するのに十分ではなく、mysqliもPDOもソリューションを提供しません。

したがって、この正直な拡張と戦うのではなく、間違った慣習と戦い、人々を正しい方法で教育したいと思います。

また、次のようないくつかの誤ったまたは重要ではない理由があります

  • ストアドプロシージャをサポートしていません(私たちはmysql_query("CALL my_proc");古くから使用していました)
  • トランザクションをサポートしていません(上記と同じ)
  • 複数のステートメントをサポートしていません(誰が必要ですか?)
  • 活発な開発が行われていない(つまり、何が実際的な影響与えいるのか?)
  • OOインターフェースがない(作成するのに数時間かかる)
  • 準備済みステートメントまたはパラメーター化されたクエリをサポートしていません

最後は面白い点です。mysql extはネイティブの準備済みステートメントをサポートしていませんが、安全のために必須ではありません。(PDOのように)手動で処理されたプレースホルダーを使用して、準備されたステートメントを簡単に偽造できます。

function paraQuery()
{
    $args  = func_get_args();
    $query = array_shift($args);
    $query = str_replace("%s","'%s'",$query); 

    foreach ($args as $key => $val)
    {
        $args[$key] = mysql_real_escape_string($val);
    }

    $query  = vsprintf($query, $args);
    $result = mysql_query($query);
    if (!$result)
    {
        throw new Exception(mysql_error()." [$query]");
    }
    return $result;
}

$query  = "SELECT * FROM table where a=%s AND b LIKE %s LIMIT %d";
$result = paraQuery($query, $a, "%$b%", $limit);

出来上がり、すべてがパラメータ化され、安全です。

しかし、わかりました。マニュアルの赤いボックスが気に入らない場合は、mysqliまたはPDOの選択の問題が発生します。

さて、答えは次のようになります:

  • データベースアブストラクションレイヤーを使用する必要性を理解し、それを作成するためのAPIを探す場合、mysqliは非常に良い選択です。これは、実際に多くのmysql固有の機能をサポートしているためです。
  • ほとんどのPHP関係者のように、アプリケーションコードで直接API呼び出しを使用している場合(これは基本的に間違った方法です)- この拡張機能はAPIだけでなくセミDALのふりをしているため、PDOが唯一の選択肢です。まだ不完全ですが、多くの重要な機能を提供します。そのうちの2つは、PDOをmysqliと明確に区​​別します。

    • mysqliとは異なり、PDOはプレースホルダーをvalueでバインドできます。これにより、動的に構築されたクエリを、かなり厄介なコードのいくつかの画面なしで実行可能にします。
    • mysqliとは異なり、PDOは常に単純な通常の配列でクエリ結果を返すことができますが、mysqliはmysqlndインストールでのみそれを行うことができます。

したがって、平均的なPHPユーザーであり、ネイティブの準備済みステートメントを使用するときに頭痛の種を節約したい場合は、PDOが唯一の選択肢です。
ただし、PDOも特効薬ではなく、困難を伴います。
だから、私はすべての一般的な落とし穴と複雑なケースの解決策をPDOタグwikiに書いた

それにもかかわらず、拡張機能について話している誰もが常にMysqliとPDOに関する2つの重要な事実を見逃しています。

  1. 準備された声明は特効薬ではありません。準備されたステートメントを使用してバインドできない動的識別子があります。不明な数のパラメーターを持つ動的クエリがあり、クエリの作成が困難なタスクになります。

  2. mysqli_ *もPDO関数もアプリケーションコードに表示されるべきではありませんでした。それらとアプリケーションコードの間に抽象化レイヤー
    が存在する必要があります。これにより、バインディング、ループ、エラー処理などのすべてのダーティジョブが内部で実行され、アプリケーションコードがDRYでクリーンになります。特に、動的クエリの作成などの複雑なケースでは。

したがって、PDOまたはmysqliに切り替えるだけでは不十分です。コードで生のAPI関数を呼び出す代わりに、ORM、クエリビルダー、またはその他のデータベース抽象化クラスを使用する必要があります。
逆に、アプリケーションコードとmysql APIの間に抽象化レイヤーがある場合、実際にどのエンジンを使用するかは問題ではありません。mysql extは非推奨になるまで使用でき、その後、抽象化クラスを別のエンジンに簡単に書き換えて、すべてのアプリケーションコードをそのまま使用できます。

そのような抽象クラスがどうあるべきかを示すために、私のsafemysqlクラスに基づいたいくつかの例を以下に示します。

$city_ids = array(1,2,3);
$cities   = $db->getCol("SELECT name FROM cities WHERE is IN(?a)", $city_ids);

この1行とPDOで必要なコードの量を比較してください。
次に、Mysqliで準備された生のステートメントを使用して必要な膨大な量のコードと比較します。エラー処理、プロファイリング、クエリロギングがすでに組み込まれて実行されていることに注意してください。

$insert = array('name' => 'John', 'surname' => "O'Hara");
$db->query("INSERT INTO users SET ?u", $insert);

これを通常のPDO挿入と比較します。すべての単一のフィールド名が6〜10回繰り返される場合-これらの多数の名前付きプレースホルダー、バインディング、およびクエリ定義すべてで。

もう一つの例:

$data = $db->getAll("SELECT * FROM goods ORDER BY ?n", $_GET['order']);

このような実用的なケースを処理するPDOの例はほとんど見つかりません。
そして、それは言い過ぎで、おそらく安全ではありません。

だから、もう一度-それは単なる生のドライバである必要があるだけでなく、抽象クラスでもあり、初心者用マニュアルの愚かな例だけでなく、実際の問題を解決するのにも役立ちます。


20
mysql_*脆弱性が発生しやすくなります。PHPは多くの初心者ユーザーによって使用されるためmysql_*、理論上は問題なく使用できるとしても、実際には有害です。
マダラのゴースト

4
everything is parameterized and safe-パラメータ化されている可能性がありますが、関数は実際の準備済みステートメントを使用していません。
uınbɐɥs

6
Not under active developmentその構成された「0.01%」だけはどうですか?この静止関数を使用して何かを構築し、1年以内にmysql-versionを更新し、稼働していないシステムを使用すると、その「0.01%」に突然多くの人がいるはずです。それと密接に関連しているdeprecatedと思いますnot under active development。それには「[価値のある]理由がない」と言えますが、オプションの選択肢を提示するとno active developmentdeprecated私が言うのとほとんど同じくらい悪いのです。
ナンヌ2013

1
@MadaraUchiha:脆弱性が非常に簡単に発生することを説明できますか?特に、これらの同じ脆弱性がPDOやMySQLiに影響を及ぼさない場合...私があなたの言う1つに気づいていないので。
ircmaxell 2013

4
@ShaquinTrifonoff:確かに、それは準備されたステートメントを使用しません。しかしほとんどの人がMySQLiよりも推奨するPDOも同様です。そのため、ここで大きな影響があるかどうかはわかりません。上記のコード(もう少し構文解析あり)は、デフォルトでステートメントを準備するときにPDOが行うことです
ircmaxell

97

理由はたくさんありますが、おそらく最も重要な理由は、これらの関数は準備されたステートメントをサポートしていないため、安全でないプログラミング手法を奨励することです。準備済みステートメントは、SQLインジェクション攻撃の防止に役立ちます。

mysql_*関数を使用するときは、ユーザーが指定したパラメーターをで実行することを忘れないでくださいmysql_real_escape_string()。1か所だけで忘れたり、入力の一部だけをエスケープしたりすると、データベースが攻撃を受ける可能性があります。

PDOまたはで準備されたステートメントを使用mysqliすると、この種のプログラミングエラーが発生しにくくなります。


3
残念ながら、可変数のパラメーターを渡すためのMySQLi_ *の不十分なサポート(IN句でチェックする値のリストを渡す場合など)は、パラメーターの使用を奨励せず、まったく同じ連結クエリの使用を奨励します。 MySQL_ *呼び出しを脆弱にしておきます。
キックスタート2013年

5
ただし、ここでも、セキュリティの問題はmysql_ *関数の固有の問題ではなく、不正な使用の問題です。
アガメムス

2
@Agamemnus問題は、mysql_ *により、特に経験の浅いプログラマーにとって、その「誤った使用法」を簡単に実装できるようになることです。準備されたステートメントを実装するライブラリは、そのタイプのエラーを発生させることを困難にします。
Trott、

75

(他の理由の中で)入力データが確実にサニタイズされるようにするのははるかに難しいためです。PDOやmysqliのようにパラメーター化されたクエリを使用すると、リスクを完全に回避できます。

例として、誰かが"enhzflep); drop table users"ユーザー名として使用できます。古い関数では、クエリごとに複数のステートメントを実行できるため、その厄介なバグのようなものがテーブル全体を削除する可能性があります。

mysqliのPDOを使用する場合、ユーザー名はになり"enhzflep); drop table users"ます。

bobby-tables.comを参照してください。


10
The old functions will allow executing of multiple statements per query-いいえ、彼らはしません。そのようなインジェクションはext / mysqlでは不可能です。この種のインジェクションがPHPとMySQLで可能になる唯一の方法は、MySQLiとmysqli_multi_query()関数を使用する場合です。ext / mysqlおよびエスケープされていない文字列で可能な種類の注入は' OR '1' = '1、データベースからアクセスできるように意図されていないデータを抽出するようなものです。特定の状況では、サブクエリを挿入することが可能ですが、この方法でデータベースを変更することはまだ不可能です。
DaveRandom

64

この回答は、不十分に記述されたPHPユーザー検証コードをバイパスすることがいかに簡単か、これらの攻撃がどのように(そして何を使用して)機能するか、古いMySQL関数を安全な準備済みステートメントに置き換える方法、そして基本的にStackOverflowユーザーがなぜそうであるかを示すために書かれています(おそらく多くの担当者と一緒に)彼らのコードを改善するために質問をしている新しいユーザーに吠えています。

まず、このテストmysqlデータベースを自由に作成してください(私はmine prepと呼んでいます)。

mysql> create table users(
    -> id int(2) primary key auto_increment,
    -> userid tinytext,
    -> pass tinytext);
Query OK, 0 rows affected (0.05 sec)

mysql> insert into users values(null, 'Fluffeh', 'mypass');
Query OK, 1 row affected (0.04 sec)

mysql> create user 'prepared'@'localhost' identified by 'example';
Query OK, 0 rows affected (0.01 sec)

mysql> grant all privileges on prep.* to 'prepared'@'localhost' with grant option;
Query OK, 0 rows affected (0.00 sec)

これで、PHPコードに移ることができます。

次のスクリプトがWebサイトの管理者の確認プロセスであるとしましょう(簡略化していますが、コピーしてテストに使用すると機能します)。

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }

    $database='prep';
    $link=mysql_connect('localhost', 'prepared', 'example');
    mysql_select_db($database) or die( "Unable to select database");

    $sql="select id, userid, pass from users where userid='$user' and pass='$pass'";
    //echo $sql."<br><br>";
    $result=mysql_query($sql);
    $isAdmin=false;
    while ($row = mysql_fetch_assoc($result)) {
        echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
        $isAdmin=true;
        // We have correctly matched the Username and Password
        // Lets give this person full access
    }
    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }
    mysql_close($link);

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

一見すると十分に合法のようです。

ユーザーはログイン名とパスワードを入力する必要がありますよね?

素晴らしい、以下に入力しない:

user: bob
pass: somePass

そしてそれを提出してください。

出力は次のとおりです。

You could not be verified. Please try again...

素晴らしい!期待どおりに動作しているので、実際のユーザー名とパスワードを試してみましょう。

user: Fluffeh
pass: mypass

すごい!すべてのラウンドでハイファイブ、コードは管理者を正しく検証しました。それは完璧だ!

まあ、そうでもない。ユーザーが賢い小さな人だとしましょう。その人が私だとしましょう。

次のように入力します。

user: bob
pass: n' or 1=1 or 'm=m

そして出力は:

The check passed. We have a verified admin!

おめでとうございます、あなたは私に偽のユーザー名と偽のパスワードを入力して、超保護された管理者のみのセクションに入るのを許可しました。真剣に、あなたが私を信じていないなら、私が提供したコードでデータベースを作成し、このPHPコードを実行してください。

だから、答えは、それがあなたが怒られている理由です。

では、何が問題だったか、なぜ私があなたのsuper-admin-only-bat-caveに入ったのかを見てみましょう。私は推測して、あなたはあなたの入力に注意していないと思い、単純にそれらを直接データベースに渡しました。実際に実行していたクエリを変更する方法で入力を作成しました。それで、それは何であるはずでしたか、そしてそれは結局何でしたか?

select id, userid, pass from users where userid='$user' and pass='$pass'

それがクエリですが、変数を実際に使用した入力に置き換えると、次のようになります。

select id, userid, pass from users where userid='bob' and pass='n' or 1=1 or 'm=m'

最初にパスワードの前後の単一引用符を閉じ、次に完全に新しい比較を導入するように「パスワード」を作成した方法を参照してください。次に、念のため、別の「文字列」を追加して、最初のコードで期待どおりに単一引用符が閉じられるようにしました。

しかし、これは今あなたに向かって叫んでいる人々についてではなく、これはあなたのコードをより安全にする方法をあなたに示すことについてです。

では、何が問題で、どうすれば修正できますか?

これは古典的なSQLインジェクション攻撃です。その点で最も単純なものの1つ。攻撃ベクトルの規模では、これは幼児が戦車を攻撃して勝利します。

では、どのようにしてあなたの神聖な管理セクションを保護し、それを美しく安全なものにするのでしょうか?最初に行うことは、これらの本当に古くて非推奨のmysql_*関数の使用をやめることです。あなたはオンラインで見つけたチュートリアルに従ってきましたが、それは機能しますが、古くて時代遅れであり、数分の間に、私は汗をかくほどではなく、それを乗り越えたところです。

これで、mysqli_またはPDOを使用するためのより良いオプションがあります。私は個人的にPDOの大ファンなので、この回答の残りの部分ではPDOを使用します。賛否両論がありますが、個人的には、プロの方がコンの方をはるかに上回っています。MySQLやOracleを使用している場合でも、流血なものを使用している場合でも、接続文字列を変更するだけで、複数のデータベースエンジン間で移植可能であり、使用したいすべての優れた機能を備えており、見た目もすっきりしています。きれいが好きです。

さて、そのコードをもう一度見てみましょう。今回はPDOオブジェクトを使用して記述されています。

<?php 

    if(!empty($_POST['user']))
    {
        $user=$_POST['user'];
    }   
    else
    {
        $user='bob';
    }
    if(!empty($_POST['pass']))
    {
        $pass=$_POST['pass'];
    }
    else
    {
        $pass='bob';
    }
    $isAdmin=false;

    $database='prep';
    $pdo=new PDO ('mysql:host=localhost;dbname=prep', 'prepared', 'example');
    $sql="select id, userid, pass from users where userid=:user and pass=:password";
    $myPDO = $pdo->prepare($sql, array(PDO::ATTR_CURSOR => PDO::CURSOR_FWDONLY));
    if($myPDO->execute(array(':user' => $user, ':password' => $pass)))
    {
        while($row=$myPDO->fetch(PDO::FETCH_ASSOC))
        {
            echo "My id is ".$row['id']." and my username is ".$row['userid']." and lastly, my password is ".$row['pass']."<br>";
            $isAdmin=true;
            // We have correctly matched the Username and Password
            // Lets give this person full access
        }
    }

    if($isAdmin)
    {
        echo "The check passed. We have a verified admin!<br>";
    }
    else
    {
        echo "You could not be verified. Please try again...<br>";
    }

?>

<form name="exploited" method='post'>
    User: <input type='text' name='user'><br>
    Pass: <input type='text' name='pass'><br>
    <input type='submit'>
</form>

主な違いは、mysql_*機能がなくなることです。すべてPDOオブジェクトを介して行われます。次に、準備されたステートメントを使用します。さて、あなたが尋ねる準備された声明は何ですか?これは、クエリを実行する前に、実行するクエリをデータベースに通知する方法です。この場合、データベースに「こんにちは、ID、ユーザーID、およびユーザーIDが変数でパスが変数でもあるテーブルユーザーからのパスを要求するSELECTステートメントを実行します。」と伝えます。

次に、executeステートメントで、データベースが期待するすべての変数を含む配列をデータベースに渡します。

結果は素晴らしいです。ユーザー名とパスワードの組み合わせを以前からもう一度試してみましょう。

user: bob
pass: somePass

ユーザーは確認されませんでした。驚くばかり。

どうですか:

user: Fluffeh
pass: mypass

ああ、私は少し興奮しました、うまくいきました。確認済みの管理者がいます!

さて、賢い章が私たちの小さな検証システムを通過するために入力するデータを試してみましょう:

user: bob
pass: n' or 1=1 or 'm=m

今回は、以下を取得します。

You could not be verified. Please try again...

これが、あなたが質問を投稿するときに怒鳴られる理由です。それは、あなたのコードが試みられてもバイパスされる可能性があることが人々にわかるためです。この質問と回答を使用してコードを改善し、コードをより安全にし、最新の機能を使用してください。

最後に、これはこれが完璧なコードであると言っているのではありません。それを改善するためにできることはもっとたくさんあります。たとえば、ハッシュ化されたパスワードを使用します。データベースに機密情報を保存するときは、プレーンテキストでは保存せず、複数のレベルの検証を行ってください。古いインジェクションが発生しやすいコードをこれに変更するだけで、優れたコードを作成する過程で順調に進みます。これまでに達成し、現在も読んでいるという事実は、このタイプを実装するだけではないという希望を感じさせます。あなたのウェブサイトやアプリケーションを書くときのコードの数、しかしあなたが外に出て先ほど述べた他のことを研究するかもしれません-そしてもっと。かろうじて機能する最も基本的なコードではなく、可能な限り最高のコードを記述してください。


2
回答ありがとうございます!+1してください!それmysql_*自体は安全ではないことは注目に値しますが、不適切なチュートリアルや適切なステートメント準備APIの欠如により、安全でないコードを促進します。
マダラのゴースト

2
ハッシュ化されていないパスワード、ああ恐怖!= oP詳細な説明については+1。
不可解なツセ

33

MySQL拡張機能は、3つのうち最も古いものであり、開発者がMySQLと通信するために使用した最初の方法でした。PHPとMySQLの両方の新しいリリースで行われた改善のため、この拡張機能は現在、他の2つの代替案のために廃止されています。

  • MySQLiは、MySQLデータベースを操作するための「改善された」拡張機能です。これは、MySQLサーバーの新しいバージョンで利用可能な機能を利用し、関数指向とオブジェクト指向の両方のインターフェイスを開発者に公開し、その他いくつかの気の利いたことを行います。

  • PDOは、以前は主要なデータベースアクセス拡張機能(MySQL、PostgreSQL、SQLite、MSSQLなど)に分散していた機能のほとんどを統合するAPIを提供します。インターフェイスは、プログラマーがデータベース接続、クエリ、および結果セット、および低レベルのドライバーは、データベースサーバーとの通信およびリソース処理を実行します。PDOについては多くの議論と作業が行われており、最新のプロフェッショナルなコードでデータベースを操作する適切な方法と見なされています。


21

上記の回答は非常に長いため、要約すると次のようになります。

mysqli拡張には多くの利点があり、mysql拡張に対する主な機能強化は次のとおりです。

  • オブジェクト指向のインターフェース
  • 準備済みステートメントのサポート
  • 複数のステートメントのサポート
  • トランザクションのサポート
  • 強化されたデバッグ機能
  • 組み込みサーバーのサポート

出典:MySQLiの概要


上記の回答で説明したように、mysqlの代替はmysqliおよびPDO(PHP Data Objects)です。

  • APIがサーバー側の準備済みステートメントをサポート:MYSQLiとPDOでサポート
  • APIはクライアント側の準備済みステートメントをサポートします:PDOでのみサポートされます
  • APIがストアドプロシージャをサポート:MySQLiとPDOの両方
  • APIは複数のステートメントとすべてのMySQL 4.1+機能をサポートします-MySQLiとほとんどの場合PDOによってサポートされます

MySQLiとPDOはどちらもPHP 5.0で導入されましたが、MySQLはPHP 3.0より前に導入されました。注意すべき点は、MySQLはPHP5.xに含まれていますが、それ以降のバージョンでは非推奨です。


2
答えが長すぎますが、実際の要約は「mysql extはもうありません」です。それだけです
常識

1
@YourCommonSense私の答えは、mysqliがmysqlに置き換わった理由です。Mysqliが今日存在すると言っているわけではありませんので、それを使用してください。誰もがそれを知っています!
Ani Menon

1
まあ、mysqliがmysqlに取って代わった理由を誰も尋ねなかったという事実は別として、この質問にも答えていません。mysqliが導入された理由を答えています。しかし、mysqlとmysqliが並行して実行できなかった理由は説明されていません
常識

@YourCommonSenseまた、OPの質問は、「自分のサイトで機能しているのに、なぜ他のものを使用する必要があるのか​​」というものです。それが私が変更と改善を指摘した理由です。あなたはそれらが長い他のすべての答えを見るかもしれないので、私はそれを要約するべきだと思いました。
Ani Menon

6

mysql_*mysqliまたはPDOを使用して、ほぼすべての関数を定義できます。古いPHPアプリケーションの上にそれらを含めるだけで、PHP7で動作します。私の解決策はこちら

<?php

define('MYSQL_LINK', 'dbl');
$GLOBALS[MYSQL_LINK] = null;

function mysql_link($link=null) {
    return ($link === null) ? $GLOBALS[MYSQL_LINK] : $link;
}

function mysql_connect($host, $user, $pass) {
    $GLOBALS[MYSQL_LINK] = mysqli_connect($host, $user, $pass);
    return $GLOBALS[MYSQL_LINK];
}

function mysql_pconnect($host, $user, $pass) {
    return mysql_connect($host, $user, $pass);
}

function mysql_select_db($db, $link=null) {
    $link = mysql_link($link);
    return mysqli_select_db($link, $db);
}

function mysql_close($link=null) {
    $link = mysql_link($link);
    return mysqli_close($link);
}

function mysql_error($link=null) {
    $link = mysql_link($link);
    return mysqli_error($link);
}

function mysql_errno($link=null) {
    $link = mysql_link($link);
    return mysqli_errno($link);
}

function mysql_ping($link=null) {
    $link = mysql_link($link);
    return mysqli_ping($link);
}

function mysql_stat($link=null) {
    $link = mysql_link($link);
    return mysqli_stat($link);
}

function mysql_affected_rows($link=null) {
    $link = mysql_link($link);
    return mysqli_affected_rows($link);
}

function mysql_client_encoding($link=null) {
    $link = mysql_link($link);
    return mysqli_character_set_name($link);
}

function mysql_thread_id($link=null) {
    $link = mysql_link($link);
    return mysqli_thread_id($link);
}

function mysql_escape_string($string) {
    return mysql_real_escape_string($string);
}

function mysql_real_escape_string($string, $link=null) {
    $link = mysql_link($link);
    return mysqli_real_escape_string($link, $string);
}

function mysql_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql);
}

function mysql_unbuffered_query($sql, $link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, $sql, MYSQLI_USE_RESULT);
}

function mysql_set_charset($charset, $link=null){
    $link = mysql_link($link);
    return mysqli_set_charset($link, $charset);
}

function mysql_get_host_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_host_info($link);
}

function mysql_get_proto_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_proto_info($link);
}
function mysql_get_server_info($link=null) {
    $link = mysql_link($link);
    return mysqli_get_server_info($link);
}

function mysql_info($link=null) {
    $link = mysql_link($link);
    return mysqli_info($link);
}

function mysql_get_client_info() {
    $link = mysql_link();
    return mysqli_get_client_info($link);
}

function mysql_create_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "CREATE DATABASE `$db`");
}

function mysql_drop_db($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "DROP DATABASE `$db`");
}

function mysql_list_dbs($link=null) {
    $link = mysql_link($link);
    return mysqli_query($link, "SHOW DATABASES");
}

function mysql_list_fields($db, $table, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    $table = str_replace('`', '', mysqli_real_escape_string($link, $table));
    return mysqli_query($link, "SHOW COLUMNS FROM `$db`.`$table`");
}

function mysql_list_tables($db, $link=null) {
    $link = mysql_link($link);
    $db = str_replace('`', '', mysqli_real_escape_string($link, $db));
    return mysqli_query($link, "SHOW TABLES FROM `$db`");
}

function mysql_db_query($db, $sql, $link=null) {
    $link = mysql_link($link);
    mysqli_select_db($link, $db);
    return mysqli_query($link, $sql);
}

function mysql_fetch_row($qlink) {
    return mysqli_fetch_row($qlink);
}

function mysql_fetch_assoc($qlink) {
    return mysqli_fetch_assoc($qlink);
}

function mysql_fetch_array($qlink, $result=MYSQLI_BOTH) {
    return mysqli_fetch_array($qlink, $result);
}

function mysql_fetch_lengths($qlink) {
    return mysqli_fetch_lengths($qlink);
}

function mysql_insert_id($qlink) {
    return mysqli_insert_id($qlink);
}

function mysql_num_rows($qlink) {
    return mysqli_num_rows($qlink);
}

function mysql_num_fields($qlink) {
    return mysqli_num_fields($qlink);
}

function mysql_data_seek($qlink, $row) {
    return mysqli_data_seek($qlink, $row);
}

function mysql_field_seek($qlink, $offset) {
    return mysqli_field_seek($qlink, $offset);
}

function mysql_fetch_object($qlink, $class="stdClass", array $params=null) {
    return ($params === null)
        ? mysqli_fetch_object($qlink, $class)
        : mysqli_fetch_object($qlink, $class, $params);
}

function mysql_db_name($qlink, $row, $field='Database') {
    mysqli_data_seek($qlink, $row);
    $db = mysqli_fetch_assoc($qlink);
    return $db[$field];
}

function mysql_fetch_field($qlink, $offset=null) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    return mysqli_fetch_field($qlink);
}

function mysql_result($qlink, $offset, $field=0) {
    if ($offset !== null)
        mysqli_field_seek($qlink, $offset);
    $row = mysqli_fetch_array($qlink);
    return (!is_array($row) || !isset($row[$field]))
        ? false
        : $row[$field];
}

function mysql_field_len($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->length : false;
}

function mysql_field_name($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgname) ? $field->name : $field->orgname;
}

function mysql_field_table($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    if (!is_object($field))
        return false;
    return empty($field->orgtable) ? $field->table : $field->orgtable;
}

function mysql_field_type($qlink, $offset) {
    $field = mysqli_fetch_field_direct($qlink, $offset);
    return is_object($field) ? $field->type : false;
}

function mysql_free_result($qlink) {
    try {
        mysqli_free_result($qlink);
    } catch (Exception $e) {
        return false;
    }
    return true;
}

ソリューションのリンクを表示する代わりに、ここに回答として追加してください。
amarnath 2017年

1

これと同様の関数mysql_connect()mysql_query()タイプは、以前のバージョンのPHP ie(PHP 4)関数であり、現在は使用されていません。

これらは、最新のPHP5 mysqli_connect()ではmysqli_query()同様にに置き換えられています。

これがエラーの原因です。


2
PHP 5は2年間以上最新ではありません。
マダラのゴースト

1

MySQLはPHP 5.5.0で非推奨になり、PHP 7.0.0で削除されました。大規模で古いアプリケーションの場合、これを検索して各機能を置き換えることは困難です。

MySQL関数を使用するには、コードを実行している以下のそれぞれにラッパー関数を作成します。ここをクリック


-9

より優れた関数とコード構造が開発されたという事実を踏まえて、mysql_ *関数は廃止されました(PHP 5.5以降)。この機能が廃止されたという事実は、パフォーマンスとセキュリティの観点から機能の改善にこれ以上の努力が払われなくなることを意味します。つまり将来の保証は少なくなります

さらに理由が必要な場合:

  • mysql_ *関数は準備されたステートメントをサポートしません。
  • mysql_ *関数はパラメーターのバインディングをサポートしていません。
  • mysql_ *関数には、オブジェクト指向プログラミングの機能がありません。
  • リストは続きます...

18
この回答は古くなっています。その上、それはすでに存在する答えに有用なものを追加しません。
常識
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.