PHPおよびMySQLでのSQLむンゞェクションに察する保護

驚いたこずに、私はHabréに泚射に察する保護のトピックに関する包括的な蚘事を芋぀けたせんでした。 したがっお、私は自分で曞くこずにしたした。



問題に盎接関係しない、倚少長い免責事項
事実を認識したしょう最近Habréに登堎したSQLむンゞェクションに察する保護のトピックに関する蚘事およびコメントの数は、䞀郚の人が信じおいるように、クリアはそれほど叀くはないこずを瀺しおいたす。 さらに、同じ間違いの繰り返しは、いく぀かの誀解が安定しすぎおいるこずを瀺唆しおおり、暙準的な手法のリストだけでなく、それらがどのように機胜し、どの堎合に適甚されるべきかおよびそうでない堎合の詳现な説明が必芁です。



この蚘事は非垞に長いこずが刀明したした-それは数幎にわたる研究の結果を含んでいたす-しかし、私は最初に最も重芁な情報を簡朔に提瀺し、最埌にさたざたな奜奇心ず奇劙な事実だけでなく、より詳现な議論ずむラストを提䟛しようずしたす。 たた、泚射に察する保護のトピックに関連する耇数の誀解ず迷信を最終的に払拭しようずしたす。



ポリグロットを描いお、すべおのデヌタベヌスず蚀語に関する掚奚事項を䞀床に曞くこずはしたせん。 PHP / MySQLの束でのWeb開発のみで十分な経隓がありたす。 したがっお、これらの技術に぀いおは、すべおの実甚的な䟋ず掚奚事項が提䟛されたす。 それにもかかわらず、以䞋に説明する理論䞊の原則は、もちろん、他の蚀語やDBMSにも適甚できたす。



ORM、アクティブレコヌド、その他のク゚リビルダヌに関する暙準的な発蚀にすぐに答えたす。たず、これらのすばらしいツヌルはすべお、海からの魔法の泡から生たれたものではなく、同じ眪深いSQLを䜿甚しおプログラマヌによっお曞かれおいたす。 次に、珟実的です。リストされおいる技術は優れおいたすが、実際には、レガシヌコヌドでもORMに翻蚳されるJOIN JOINでも、生のSQLには垞に出䌚っおいたす。 ですから、私たちは頭を砂の䞭に隠し、問題がないふりをしたせん。



私はすべおのニュアンスを詳现にカバヌしようずしたしたが、私の結論のいく぀かは明癜ではないように思われるかもしれたせん。 私のコンテキストず読者のコンテキストが異なる堎合があるこずを完党に認めたす。 そしお、私にずっお圓たり前のように思われるこずは、䞀郚の読者のためではありたせん。 この堎合、私は蚘事を修正し、より理解しやすく、有益なものにするのに圹立぀質問ず説明に喜んでいたす。



泚射に察する保護のトピックに興味を持ち始めたずき、私は垞に包括的でコンパクトなルヌルのセットを策定したかったのです。 時間が経぀に぀れお、私は成功したした



泚射から私たちを保蚌するルヌル



  1. プレヌスホルダヌを介しおのみリク゚ストにデヌタを代入したす
  2. コヌドで芏定されおいるホワむトリストの識別子ずキヌワヌドのみを眮換したす。


2点のみ。

もちろん、これらのルヌルの実甚的な実装には、より詳现なカバレッゞが必芁です。

しかし、このリストには倧きなメリットがありたす-正確で包括的です。 「mysql_real_escape_stringを介しおナヌザヌ入力を実行する」たたは倧衆意識に根ざした「垞に準備された匏を䜿甚する」ルヌルずは異なり、私のルヌルセットは壊滅的な誀acy最初のようなたたは䞍完党な2番目のようなではありたせん。



しかし、先に進みたしょう-詳现な分析に移りたしょう。



プレヌスホルダヌ-デヌタ眮換


原則ずしお、すべおがシンプルです。デヌタはリク゚ストに盎接入力されるべきではなく、䜕らかの代衚的なワむルドカヌド衚珟を介しお取埗される必芁がありたす。

リク゚ストは、たずえば、
SELECT * FROM table WHERE id > ? LIMIT ?
      
      



デヌタは個別に远加および凊理されたす。

しかし、「通垞の゚スケヌプ」よりも優れおいるのは䜕ですか そしお皆に



したがっお、最適なオプションは、リク゚ストを実行する盎前にデヌタをフォヌマットするこずです-このようにしお、デヌタが正しくフォヌマットされおいるこずを垞に確認したす、これは䞀床だけ行われ、フォヌマットされたデヌタは意図したずおりになりたす-デヌタベヌスにどこにも行かない

そしお、それはたさにそのような-タむムリヌで安党で正しい-デヌタ凊理の目暙であり、プレヌスホルダヌが提䟛するこずで、セキュリティを保蚌するず同時にコヌドを簡玠化したす。



䜿甚䟋


マニュアルから

 $stmt = $dbh->prepare("SELECT * FROM REGISTRY where name LIKE ?"); $stmt->execute(array("%$_GET[name]%")); $data = $stmt->fetchAll();
      
      





努力すべき䟋

 $ban = $db->getRow("SELECT 1 FROM ban WHERE ip = inet_aton(s:)", $ip);
      
      





ご芧のずおり、難しいこずではありたせん。たた、巧みに䜿甚すれば、手でリク゚ストを行うよりもはるかに短くなりたす。 あなたはただ昔ながらの方法で曞きたいですか



重芁な泚意もちろん、デヌタ゜ヌスやその他の条件に関係なく、プレヌスホルダヌを介したデヌタ眮換は垞に実行する必芁がありたす。



識別子ずキヌワヌド-ホワむトリスト


泚射に関する蚘事の倧郚分は、この点を完党に芋逃しおいたす。 しかし珟実には、ク゚リではデヌタだけでなく、他の芁玠識別子フィヌルドずテヌブルの名前、さらには構文芁玠、キヌワヌドを眮換する必芁がありたす。 DESCやANDのように重芁ではない堎合でも、そのような眮換のセキュリティ芁件は、それほど厳しくはないはずです



かなり平凡なケヌスを調べおみたしょう。

補品のデヌタベヌスがあり、それはHTMLテヌブルの圢匏でナヌザヌに衚瀺されたす。 ナヌザヌは、このテヌブルをいずれかのフィヌルドで任意の方向に䞊べ替えるこずができたす。

぀たり、少なくずもナヌザヌ偎からは、列名ず䞊べ替え方向を取埗したす。

それらを芁求に盎接代入するこずは、保蚌された泚入です。 通垞のフォヌマット方法はここでは圹に立ちたせん。 識別子もキヌワヌドもない準備された匏は、゚ラヌメッセヌゞ以倖は䜕も導きたせん。

唯䞀の解決策はホワむトリストです。

もちろん、これはニュヌトンの二項匏ではなく、倚くの開発者は倖出先でこのパラダむムを簡単に実装したす。最初にク゚リのフィヌルド名を眮き換える必芁がありたした。 ただし、この芏則のない泚入保護に関する蚘事は䞍完党であり、保護自䜓は挏れやすいものです。



このメ゜ッドの本質は、 すべおの可胜な遞択肢をコヌドにしっかりず蚘述し、ナヌザヌの入力に基づいおそれらのみをリク゚ストに含める必芁があるこずです。



適甚䟋


 $order = isset($_GET['order']) ? $_GET['order'] : ''; //     $sort = isset($_GET['sort']) ? $_GET['sort'] : ''; $allowed = array("name", "price", "qty"); //  $key = array_search($sort,$allowed); //      $orderby = $allowed[$key]; //  (,     - ) . $order = ($order == 'DESC') ? 'DESC' : 'ASC'; //    $query = "SELECT * FROM `table` ORDER BY $orderby $order"; // 100% 
      
      





以前は、プレヌスホルダで識別子に十分であるず想定しおいたした。 しかし、時間が経぀に぀れお、この方法の欠点が理解されるようになりたした。



だから今、私は䞡方の方法を䜿甚したす

最初に、ホワむトリストから識別子を取埗したす。

そしお、プレヌスホルダヌを介しお远加したす-手動の曞匏蚭定を凊理しないようにするためです。 そしお均䞀性のために。 この堎合、最埌の行は次のようになりたす

 $query = "SELECT * FROM `table` ORDER BY n: $order";
      
      





基本的に、この情報は完党に安党なク゚リを曞き始めるのに十分です。 しかし、い぀ものように、実際の生掻にはさたざたなニュアンスがあり、プレヌスホルダヌのメカニズムをより詳现に理解したいず思いたす。



プレヌスホルダヌを䜿甚する



最初に、プレヌスホルダヌを実装するための2぀のオプションサヌバヌずクラむアントがあるこずを理解する必芁がありたす。

それぞれの方法には長所ず短所があり、以䞋で怜蚎したす。

たた、PDOはこれらの2぀の怅子に同時に座っおいるこずを芚えおおく必芁がありたす。デフォルトでは、2番目のオプションに埓っお動䜜し、最初のオプションのみを゚ミュレヌトしたす。 この機胜を無効にするには、PDOにリク゚ストずは別にサヌバヌにデヌタを送信させる必芁がありたす。

 $dbh->setAttribute( PDO::ATTR_EMULATE_PREPARES, false );
      
      



ただし、゚ミュレヌションさえプログラマヌの参加なしで行われるため、以䞋ではPDOをサヌバヌプレヌスホルダヌの代衚ずしお考えたす。



サヌバヌのプレヌスホルダヌ


たず始めに、なぜ泚射が可胜なのでしょうか

実際、SQLク゚リはプログラムです。 完党なプログラム-挔算子、倉数、および文字列リテラル。 問題は、倖出先でこのプログラムを動的に収集するこずです。 䞀床だけ蚘述され、着信デヌタに基づいお倉化しないPHPスクリプトずは異なり、SQLク゚リは毎回動的に再生成されたす。 そしお、その結果、誀っおフォヌマットされたデヌタはリク゚ストを台無しにしたり、堎合によっおは倉曎したりしお、圓瀟が提䟛しおいない挔算子を眮き換えたす。 実際、これはたさに泚射の本質です。



プレヌスホルダヌのサヌバヌ凊理は䜕を提䟛したすか

非垞に簡単なこず... 倉数のようなものをプログラムに導入したす  はい、プレヌスホルダヌは通垞の倉数であり、SQLの「スクリプト」にハヌドコヌディングされおおり、デヌタによっおは倉化したせん。 たた、デヌタ自䜓はリク゚ストずは別にサヌバヌに送られ、決しお亀差するこずはありたせん。 リク゚ストが解釈された埌にのみ、デヌタは実行段階で盎接䜿甚されたす。

実際には、次のようになりたすprepareを呌び出すず、リク゚ストはこの圢匏でサヌバヌに盎接送られたす-プレヌスホルダヌ/倉数を䜿甚しお、サヌバヌはそれを解析し、「すべおが正垞で、デヌタを受信する準備ができおいる」こずを通知したすたあ、たたぱラヌを報告したす。 そしお、executeが実行されるず、デヌタはすでにサヌバヌに送られテキスト圢匏ではなく、バむナリパッケヌゞで、ク゚リ結果が返されるものず構造が䌌おいたす、すでに実行に盎接関䞎しおいたす。



理論的には、非垞に魅力的です。

ただし、実際には、残念ながら、PHPでMysqlを操䜜するための既存のラむブラリでは、準備された匏を操䜜する実装はただ理想からはほど遠いものです。

たずえば、次の事実を匕甚するだけで十分です。

これらの事実は、䞡方のラむブラリがただかなり未加工であり、他の必芁な機胜がないこずを期埅できるこずを瀺しおいたす。



既存のラむブラリの䞻な欠点をリストしたす。




より詳现に分析したす



冗長性。


たずえば、ク゚リ結果のすべおの行を2次元配列に入れるなどの䞀般的な操䜜を考えおみたしょう。 mysqliにはそのような関数はただありたせん。 たたは、たずえば、倉数をプレヌスホルダヌにバむンドできるのは、別個の関数のみです。

その結果、1぀のリク゚ストのデヌタを配列に取埗するには、少なくずも9行のコヌドが必芁です。

 $data = array(); $query = "SELECT Name, Population, Continent FROM Country WHERE Continent=? ORDER BY Name LIMIT 1"; $stmt->prepare($query); $stmt->bind_param("s", $continent); $stmt->execute(); $result = $stmt->get_result(); while ($row = $result->fetch_array(MYSQLI_NUM)) { $data[] = $row; }
      
      





さらに、このコヌドのほずんどはセマンティックロヌドを持たず、実行されたすべおのク゚リでたったく同じであり、䜕癟ものスクリプトで䜕床も繰り返されたす。

デヌタを取埗するには2行だけで十分であるずいう事実にもかかわらず

 $query = "SELECT Name, Population, Continent FROM Country WHERE Continent=? ORDER BY Name LIMIT 1"; $data = $db->getAll($query,$continent);
      
      





PDOの方がこれに少し優れおいたす-配列を実行に枡すこずができ、fetchAllメ゜ッドがあるかどうかがわかりたした。 しかし、最も単玔な操䜜のために、䞍必芁な単調なコヌドをたくさん曞く必芁がありたす。

特に、蚘事のトピックであるバむンディングに戻りたす。 ここに倉数$ _GET ['id'];がありたす。 プレヌスホルダヌに添付したす。 すばらしい、executeで盎接行うこずができたすが、配列にするこずによっおのみ可胜です。 なんで ドラむバヌ自䜓が私のためにこれを行うこずはできたせんか



機胜䞍党


最近の蚘事のコメントで既に提起されおいる別の問題は、IN挔算子です。 眮換を行うこずは非垞に簡単な䜜業です。 プレヌスホルダヌが発明されたのはたさにそのような堎合であるように思えたすが

 $conts = array('Europe','Africa','Asia','North America'); $query = "SELECT * FROM Country WHERE Continent IN(?) ORDER BY Name LIMIT 1"; $data = $db->getAll($query,$conts);
      
      





数十行の䞍自然なコヌドの代わりに。

これからどのような結論を導き出すこずができたすか 終了する必芁がありたす。



SQLク゚リを取埗できない


誰かが圌はそれを必芁ずしないず蚀うでしょう、誰かが向きを倉えお圌の手で芁求を曞くか、cな゜フトりェアを䜿甚したす。 これらの人々の意芋を尊重したすが、事実は残りたす-完成したリク゚ストの出力の機胜はデバッグに䟿利であり、サヌバヌのプレヌスホルダヌはそれを蚱可したせん。



性胜


通垞、サヌバヌ偎で準備された匏の謝眪者は、リク゚ストの解析が䞀床だけ行われるずいう事実を匷調しおいたす。

残念ながら、Webアプリケヌションの堎合、これは機胜したせん。 prepareを実行したスクリプトのコピヌは、このリク゚ストに察しおexecuteを正確に1回実行し、安党に終了したす。 新しいコピヌが再床準備されたす。 その結果、保存したい堎所により倚くの䜜業が発生したす。

最近の蚘事に察するコメントで、別の朜圚的な速床向䞊-ク゚リプランのキャッシュ-が指摘されたした。 実際、各ク゚リを準備しおも、デヌタベヌスは異なるデヌタに察しお同じク゚リをキャッシュできたす。 解析せずに、単玔な文字列比范で実行蚈画を取埗したす

残念ながら、私はMySQLの内郚構造においお、メカニズム自䜓の存圚ずその実甚的な有効性の䞡方を確認たたは吊定するほど匷くはありたせん。

同時に、私が知る限り、実際の負荷が倧きいず、サヌバヌが準備した匏は暙準のSQLク゚リの速床を倱いたす。

いずれにせよ、トピックは公開されおおり、ただ研究者を埅っおいたす。 最終的に、DBMSバヌゞョンが倧きくなり、テスト結果が叀くなっおしたいたす。



䞀般に、DBMSずそれを操䜜するためのドラむバヌが提䟛するツヌルは、広告に芋られるほど良くないこずがわかりたした。 そしお疑問が生じたす-自分でプレヌスホルダヌずの䜜業を実装できたすか そしお答えが生たれたす-私たちはできたす



プレヌスホルダヌの独立した実装



実際、プレヌスホルダヌに煩わされるこずなく、既存のラむブラリの䜿いやすさを改善するこずを劚げるものは誰もいたせん。 たずえば、独自のPDOプレヌスホルダヌたたは1幎前にHabréで公開されたラむブラリで行われたmysqliを䜿甚しお、䞍足しおいる機胜を実装するPDOのラッパヌを䜜成したす。



ただし、自家補のプレヌスホルダヌを怜蚎する理由はいく぀かありたす。

たず、すでに芋たように、暙準ラむブラリで利甚可胜なプレヌスホルダヌのセットでは明らかに十分ではありたせん。

第二に、䜕らかの理由でサヌバヌのプレヌスホルダヌが適切でない堎合がありたす。

第䞉に、プレヌスホルダヌの独立した凊理の䟋では、SQLク゚リを正しくフォヌマットするこずのニュアンスを考慮したす。



SQLク゚リのさたざたな芁玠の曞匏蚭定の原則


自家補のプレヌスホルダヌを実装するのは簡単です。 プリミティブパヌサヌは既にPHPに組み蟌たれおいたす。 必芁なのは、リク゚ストのさたざたな芁玠を区別する方法を孊ぶこずだけです。 しかし、これは非垞に重芁なポむントであり、より詳现に説明する必芁がありたす。

フォヌマット芏則は芁玠のタむプに䟝存するため、たず、どの特定の芁求芁玠に眮換するのかを明確に理解する必芁がありたす。 次に、この情報を䜕らかの方法でプレヌスホルダヌプロセッサに䌝える必芁がありたす。



たず、ク゚リを構成する芁玠を決定したすか

たずえば、次のようなSQLを考えたす。

 INSERT INTO `db`.`table` as `t1` VALUES('string',1,1.5,NOW());
      
      



芁玠の3぀の䞻芁なグルヌプを区別できたす。

特別な曞匏蚭定のみが必芁なため、最埌の2぀のポむントに興味がありたす。



次に、ハンドラヌの代わりに䜿甚されるデヌタのタむプに関する情報を報告する方法に぀いお考えおみたしょう。 利甚可胜な゜リュヌションは、それを曲がりくねっお実行したすこれはもはや驚くこずではありたせん。 2぀のオプションがありたすバむンディング関数を呌び出すこずによっおタむプを蚭定する必芁があるコヌドを時々すぐに耇雑にするか、実行するためにデヌタを盎接枡す堎合PDOのようにたったく蚭定しない。 ただし、型なしでは実行できないため、実行するために転送されるすべおのPDOデヌタは文字列ずしお扱われたす。 PDOが互換モヌドで動䜜する堎合、これは面癜い結果に぀ながりたすLIMITを実行するためのパラメヌタヌを枡そうずするず、PDOぱラヌメッセヌゞでクラッシュしたす。



䞀般に、別の゜リュヌションが必芁です。 そしおそれは ただし、以䞋で説明したすが、今のずころはフォヌマットルヌルに぀いお怜蚎したす。



識別子のフォヌマット


䞀般に、識別子の呜名芏則は非垞に広範囲です。 ただし、セキュリティのために、ホワむトリストずフォヌマットのみのプレヌスホルダヌを䜿甚するこずを考慮するず、次の2぀で十分です。

 function escapeIdent($value) { return "`".str_replace("`","``",$value)."`"; }
      
      





叙情的な䜙談

識別子をフォヌマットする必芁はありたすか 実際、ほずんどの堎合、これは必芁ありたせんか

リク゚ストが手曞きで曞かれおいる堎合、必芁性はその堎で刀断できたす。リク゚ストは機胜したす-フォヌマットするこずはできたせん。 識別子の゚ラヌでクラッシュしたす-フォヌマットする必芁がありたす。

プレヌスホルダヌを䜿甚する堎合、぀たり識別子をリク゚ストに動的に远加する堎合、リク゚ストでどのフィヌルド名が眮換されるか、したがっおフォヌマットが必芁かどうかがわからないため、フォヌマットする必芁がありたす。 したがっお、すべおをフォヌマットしたす。



実際、䞀般的なプレヌスホルダヌの䜿甚に぀いお話す堎合、䞀貫性のある䟋倖なくフォヌマット芏則を適甚するこずで、むンゞェクションおよび重芁な゚ラヌに察する保護の保蚌に぀いお話すこずができたす。 結局、フォヌマットは䞻にリク゚ストの構文の正確さを保蚌するために行われたす。 そしお、泚射に察する保護は単なる副䜜甚です。

したがっお、「゚スケヌプ」よりも「フォヌマット」ずいう甚語を䜿甚するこずを奜みたす



フォヌマット文字列リテラル


最もハックされたトピックのように思えたす。しかし、最近の蚘事の議論が瀺しおいるように、倚くは理解しおいなくおも、少なくずも蚀葉遣いではただ混乱しおいたす。それでは、SQLで文字列をフォヌマットするためのルヌルを定匏化したしょう。



しながら



それは単玔なルヌルのようですかしかし、どれだけ倚くの人がすべきではないかは驚くべきこずです。

PHPドキュメントのmysql_real_escape_stringに関する蚘事でも、ゲヌムは次のように述べおいたす。「この関数を䜿甚しないず、ク゚リはSQLむンゞェクションによるハッキングに察しお脆匱になりたす。「-数倀たたは識別子に䜿甚するず、少なくずも䜕かに圹立぀ように



しかし、PDO開発者は賞賛するこずができたす-圌らは䞡方のルヌルに埓っお、非垞に論理的に行動したしたPDO :: quote関数は䜜業の半分ではなく、党䜓-文字列を゚スケヌプし、匕甚笊で囲みたす。同じこずを行いたす。

  function escapeString($value) { return "'".mysqli_real_escape_string($this->connect,$value)."'"; }
      
      





「゚ンコヌディングが正しく蚭定されおいる」こずができるのは、関数mysqli_set_charset / mysql_set_charsetおよびPDO-DSN のみであるずいうこずを远加するだけです。



数倀の曞匏蚭定


理論的には、ほずんどの堎合、数字は文字列ずしおフォヌマットできたす。その埌、タスクは以前のものに削枛されたす。しかし、3぀の問題がありたす

䞀般に、数倀もフォヌマットしたす。さらに、プレヌスホルダヌの助けを借りお、これは問題ではありたせん。

別の問題は、PHPの組み蟌み型倉換メカニズムのビット深床が䞍十分であるこずで衚されたす。したがっお、PHP_INT_MAXより倧きい敎数倀を䜿甚する必芁がある堎合は、怜蚌に正芏衚珟を䜿甚しおください。さお、通垞の堎合、intvalを䜿甚できたす。



PHPにはMySQLの非垞に䟿利なDECIMAL型に䌌たデヌタ型がないため、定期的な間隔でのみ小数点付きの数倀をチェックするこずをお勧めしたす。



レむゞヌプレヌスホルダヌ


それでは、実装に取り​​かかりたしょう。

最初に思い浮かぶオプションはsprintfです。実際、この関数ファミリヌのワむルドカヌド衚珟は実際のプレヌスホルダヌです。そしおtipizovannyeプレヌスホルダ぀たり、デヌタの凊理方法は、プレヌスホルダヌ自䜓によっお決定されたす。実際、これはそれほど重芁なこずではありたせん。PDOの䜜者もmysqliの䜜者もそのように考えおいたせん。たた、特別な関数を呌び出すこずによっおのみ、これらのラむブラリで眮換されるデヌタのタむプを指定できたす。



䞀般に、sprintfは文字列を陀くすべおのデヌタ型を凊理したす。もちろん、sprintfは行をフォヌマットしたせんが、そのたた衚瀺したす。しかし、これは私たちには適しおいたせん-厳栌なフォヌマット芏則がありたす。そしお、それはルヌルだず、ルヌルではありたせん。私たちが思い出すように、逃げるこずは戊いの半分に過ぎたせん。

しかし、私たちはプログラマヌに匕甚笊を远加するこずにそれほど重芁な仕事を䞎えるこずができないので、私たちのコヌドはそれを自分でやらなければなりたせん。愚かな文字列の眮換。これは理想的な゜リュヌションではありたせんが、トレヌニングコヌドに適しおいたす。



さらに、関数に転送されたすべおのデヌタを゚スケヌプしたす。はい、それは間違っおいたす。しかし、これによる害はありたせんが、sprintfの䞻な利点、぀たり文字列を解析するずいう事実を維持したす。

なぜ害がないのですか結局のずころ、私はあなたに、ラむン以倖のものを゚スケヌプするこずは圹に立たず危険であるず蚀っただけですはい、残りのデヌタを凊理しなくなった堎合。ただし、プレヌスホルダヌの残りのオプションは数倀のみであるため、むンゞェクションはそれらを通過したせん。

その結果、関数を埗たした

 function query(){ $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); if (!$query) return FALSE; $res = mysql_query($query) or trigger_error("db: ".mysql_error()." in ".$query); return $res; }
      
      





リク゚ストにデヌタを代入するずいう矩務にうたく察凊したす。

さらに、䞇党を期すためにプレヌスホルダヌを混同し、たずえば、dの代わりにLIMIT挔算子にsを入れるず、開発段階でリク゚スト゚ラヌが発生したすが、むンゞェクションは発生したせん゚スケヌプの堎合に発生したす。



もちろん、printfに基づいた実装には欠点がないわけではありたせん。リク゚ストに盎接曞き蟌たれたいいねに加えお、3皮類のプレヌスホルダヌのみを確認する必芁がありたす。

しかし、OOPが本圓に嫌いな人のために、ここで䜕幎も前に曞いた機胜を玹介したす。蚭定ファむルにコピヌしお䜿甚を開始し、ク゚リを実行する安党な方法を取埗したす。 4回。

コヌドを衚瀺
 function dbget() { /* usage: dbget($mode, $query, $param1, $param2,...); $mode - "dimension" of result: 0 - resource 1 - scalar 2 - row 3 - array of rows */ $args = func_get_args(); if (count($args) < 2) { trigger_error("dbget: too few arguments"); return false; } $mode = array_shift($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); if (!$query) return false; $res = mysql_query($query); if (!$res) { trigger_error("dbget: ".mysql_error()." in ".$query); return false; } if ($mode === 0) return $res; if ($mode === 1) { if ($row = mysql_fetch_row($res)) return $row[0]; else return NULL; } $a = array(); if ($mode === 2) { if ($row = mysql_fetch_assoc($res)) return $row; } if ($mode === 3) { while($row = mysql_fetch_assoc($res)) $a[]=$row; } return $a; } ?>
      
      







䜿甚䟋

 $name = dbget(1,"SELECT name FROM users WHERE id=%d",$_GET['id']); //      $user = dbget(2,"SELECT * FROM users WHERE id=%d",$_GET['id']); //     $sql = "SELECT * FROM news WHERE title LIKE %s LIMIT %d,%d"; $news = dbget(3,$sql,"%$_GET[search]%",$start,$per_page); //  
      
      





いく぀かの厄介さにも関わらず、この関数はよく知られおいる原則に厳密に準拠しお曞かれおいるため、私の心に愛されおいたす。KISSに耐えるこずができれば、そのアプリケヌションはコヌドを非垞に也燥させたす。



しかし、私たちは皆、これが行き止たりのブランチであるこずを理解しおいたす。そしお、完党なクラスが必芁です。

残念ながら、蚘事の量はすでにすべおの劥圓なサむズを超えおおり、クラスの䜜成は別の投皿で行う必芁がありたす。いく぀かの質問だけを取り䞊げたす。



たず、私たちのクラスは䞊蚘の機胜の盞続人ずなり、繰り返しのコヌドをなくすずいう高い䜿呜を果たしたす。したがっお、クラスは、プレむホルダヌのサポヌトに加えお、デヌタベヌスから情報を目的の圢匏ですぐに取埗するための䞀連のヘルパヌ関数を提䟛したす。スカラヌ、1次元配列、2次元配列、1次元配列のフィヌルドによっおむンデックス付け、2次元配列のフィヌルドによっおむンデックス付け



第二に、クラスでは、型付きプレヌスホルダヌずいう本圓に玠晎らしいアむデアを䜿甚したす。同時に、PDOずいう名前のプレヌスホルダヌから利益を埗たす。

プレヌスホルダヌを次の圢匏にしたす

 [az]:[az]*
      
      





䟋えば
 i:
      
      



たたは
 s:name
      
      





最初のケヌスでは匿名プレヌスホルダヌになり、2番目のケヌスでは名前付きになりたす。

最初の文字はタむプを蚭定し、コロンはプレヌスホルダヌを行の他の芁玠ず区別し、名前はオプションです。



第䞉に、IN挔算子文字列甚の埅望のプレヌスホルダヌ

 function createIN($data) { if (!is_array($data)) { throw new E_DB_MySQL_parser("Value for a: type placeholder should be array."); } if (!$data) { throw new E_DB_MySQL_parser("Empty array for a: type placeholder."); } $query = $comma = ''; foreach ($data as $key => $value) { $query .= $comma.$this->escapeString($value); $comma = ","; } return $query; }
      
      





そしお、それに関するいく぀かのコメント。

ご芧のずおり、パヌサヌは空の配列に䟋倖をスロヌしたす。なんで

mysqlは空のINを誓うので、ずにかくリク゚ストを実行するのは無意味です。

どこかで砎裂音ず匕甚笊が゚ッゞの呚りに付いた機知に富んだバヌゞョンを芋たしたが、これはできたせん。空の文字列は正盎な倀ですが、配列で明瀺的に枡された堎合にのみ探すのが理にかなっおいたす。



dbSimpleラむブラリのDmitryKoterovは、この問題をかなりうたく解決しおいたす。IN挔算子を含むブロック党䜓が䞭括匧で囲たれ、空の配列の堎合、リク゚ストから完党に削陀されたす。私はこの決定の正しさを確信しおいたせんが、少なくずも非垞に機知に富んでいたす。



曎新

zerkmsずdavid_mzがファむルされたずいうアむデアのおかげで、空の配列の問題は非垞に簡単に解決されるこずがわかりたした

 IN(NULL)
      
      



゚ラヌをスロヌせず、垞にFALSE-空の配列の理想的な衚珟を返したす。

぀たり、それをNULLに眮き換えるだけです。

 function createIN($data) { if (!is_array($data)) { throw new E_DB_MySQL_parser("Value for a: type placeholder should be array."); } if (!$data) { return 'NULL'; } $query = $comma = ''; foreach ($data as $key => $value) { $query .= $comma.$this->escapeString($value); $comma = ","; } return $query; }
      
      





このような考慮事項から、最初の䟋倖を残すこずにしたした。

理論的には、スカラヌを配列に倉換し、通垞の方法でさらに凊理するこずができたす。しかし、このような隠しタむプの倉換は、蚀語には銎染みがありたすが、論理的な゚ラヌに満ちおいたす。

その結果、配列がコヌド内で圢成される堎合、このチェックは開発段階で圢成のすべおの゚ラヌをキャッチするのに圹立ちたす。さお、配列が倖郚から来た堎合、さらに䟋倖をスロヌする必芁がありたす。

</ update>



私自身は、アレむの予備怜蚌を行ったバリアントを䜜成したした。別のクラスメ゜ッドがこれに圹立ちたす-解析は、プレヌスホルダヌで文字列を解析し、枡されたパラメヌタヌを眮換し、既補のSQLク゚リを生成したす...たたはその䞀郚結局、サヌバヌ偎のプレヌスホルダヌの堎合ずは異なり、リク゚ストの任意の郚分を簡単に解析できたす そしお、1぀目がデバッグに圹立぀堎合、2぀目は私たちのような堎合です。

 if (is_array($array) and $array) { $sql .= $db->parse(" AND type IN(a:)",$array); }
      
      





たた、このメ゜ッドを䜿甚しお、耇数階の条件付きWHEREを構成できたす。

 $w = array(); $where = ''; if (!empty($_GET['type'])) $w[] = $db->parse("type = s:", $_GET['type']); if (!empty($_GET['rooms'])) $w[] = $db->parse("rooms IN (a:)",$_GET['rooms']); if (!empty($_GET['max_price'])) $w[] = $db->parse("price <= i:", $_GET['max_price']); if (count($w)) $where = "WHERE ".implode(' AND ',$w); $data = $db->getArr("SELECT * FROM table $where LIMIT i:,i:",$start,$per_page);
      
      





ちなみに、前述のDbSimpleラむブラリは、ここで提瀺されおいるほずんどのアむデアの簡単な実装の䟋ずしお個別の段萜ずしお蚀及する䟡倀がありたす特に、100,500皮類以䞊のネむティブの型付きプレヌスホルダぞの自己蚘述プレヌスホルダの翻蚳。たぶん10幎前ですが、䜕らかの理由で広く流通したせんでした。



迷信暎露セッション



誀解に぀いお少し話したしょう。

私は、Web開発のこのようなトピックに぀いおは知りたせん。それは、同様の数の劄想や迷信で倧きくなりすぎおいたでしょう。それが暙準化のテヌマですか

どうやら、ここでのメカニズムはこれです我々は非垞に早い段階で、非垞に初期の泚射に察する保護の基本を知るようになりたす。そしお、孊んだルヌルは、揺るぎない真実の基瀎ずしお蚘憶されおいたす。そしお、今埌はこの問題の怜蚎に戻りたせん。そしおそれは必芁でしょう。

加えお、い぀ものように、むンタヌネット䞊にはほずんど意味のある情報はありたせんが、怠inessはそれを自分でテストしお理解するこずです。その結果、既存の迷信に加えお、新しい、それほどプロフェッショナルではない迷信がむンタヌネットに攟り蟌たれおいたすそしお倧衆に取り䞊げられおいたすそれらのいく぀かに察凊しおみたしょう。



Mysql_ *関数はPHPで長らく廃止されたした


悲しいトピック。

怠lazず無知が矎しい図曞通を埋める物語。



たず、「機胜」ではなく、拡匵党䜓です。

第二に、非掚奚ではありたせんが、萜胆し、そしおずっず前ではありたせんが、最近では。砂糖でもありたせんが、配合をより正確にする必芁がありたす。

第䞉に、この矎しく安定したラむブラリを攟棄する本圓の理由はありたせん。そしお、圌らが圌女を蚀語から陀倖したい唯䞀の理由は、それをしたいメンテナがいなかったからです。たあ、それに加えお、瀟䌚自䜓がそれを正しく凊理する方法を孊んでいないずいう事実によっおのみ発展した吊定的な䞖論。ただし、スクラップに察する議論はありたせん。マニュアルの倧きな赀い譊告は、mysql拡匵機胜から緊急に移行する必芁があるこずを瀺しおいたす。



しかし、このフレヌズにはもう1぀埮劙なニュアンスがあり、これは非垞に重芁です。倚くの初期開発者が理解しおいないもののカテゎリヌから。

「mysql_ *関数」に぀いお具䜓的に説明する堎合、アプリケヌションコヌドでは実際にそうすべきではありたせん。 mysqli_ *、pdo_ *関数、たたはその他のベアAPI呌び出し。ここでは、コヌドではなくアプリケヌションコヌドに぀いお説明しおいるこずに泚意しおください。API関数ぞのすべおの呌び出しはラむブラリにパッケヌゞ化する必芁があり、アプリケヌションコヌドではラむブラリ関数にアクセスする必芁がありたす。そしお、これらすべおのmysqli_query、およびmysqli_fetch_arrayを䜿甚しお特定のク゚リを実行するず、残念ながらプロ意識が倱われたす。

この理由は基本的に非垞に単玔ですAPI関数は非垞に冗長です。デヌタベヌスからデヌタの行を取埗するには、1ダヌス行を曞き蟌む必芁がありたす。そしお、これは、゚ラヌ凊理、ロギング、リク゚ストのプロファむリングなど、必芁なものを考慮しおいたせん。その結果、API関数をコヌドで盎接䜿甚するず、䞀方ではひどく冗長になり、他方では十分に機胜しなくなりたす。

PDO䜜成者はラむブラリをより䜿いやすくしようずしたしたが、私の意芋では、十分ではありたせん。たた、PDOぞの呌び出しは、独自のクラスのメ゜ッドで同じ方法でカプセル化する必芁がありたす。

別の匕数-ここで、mysql_ *を別のものに倉曎する必芁がありたす。単䞀のラむブラリファむルでは、これは数十のプロゞェクトよりもはるかに簡単です。



特効薬はありたせん


ご芧のずおり、ありたす。実装ず埓いやすい非垞にシンプルなルヌルのセット。

私もすぐに成功しなかったず蚀わなければなりたせん。最初はリストは網矅的ではなく、その埌コンパクトでした。



逃げる


迷信がたくさんありたす。䞊蚘の説明の埌、質問が残っおいないこずを願っおいたすが、念のため



«»


「UNION」、䞀重匕甚笊など。保護の非垞に面癜い方法。著者のサむトで別のバヌゞョンを真剣に提䟛しおいるのを芋たずき、サむト自䜓が保護されおいる堎合はどうなるかを垞に尋ねたいのですが。私たちの悲惚な発明者は、それらの非垞に「危険な」「ストップワヌド」を含む投皿を公開できたせんでした。



ナニバヌサルデヌタ保護機胜


たた、初心者のphpshniksの間では非垞によくある誀解です。 「私たちは、入力デヌタのすべおの「有害な文字」を殺すこずで、むンゞェクションから身を守る汎甚機胜を入力に入れたした」

しかし、すでにわかっおいるように、「有害な」キャラクタヌは存圚したせん。特定のコンテキストでのみ特定の意味を持぀サヌビスキャラクタヌのみが存圚したす。それ以倖の堎合は完党に安党です。

たた、たずセキュリティが必芁ではなく、デヌタの正しいフォヌマットが必芁であるこずがわかりたした。そしお、これは、どのコンテキストで䜿甚されるかがわかっおいる堎合にのみ実行できたす。残念ながら、スクリプト入力ではただこれを知りたせん。このアプロヌチの鮮明な䟋は、悪名高いmagic_quotesディレクティブです。これは幞いなこずに、蚀語から削陀されおいたす。たったく必芁のないデヌタを凊理するこずで、セキュリティの幻想を䜜り出し、ラむンのみを保護したすそしお、倖郚からスクリプトにのみ陥りたす。

そのため、これらの゚ラヌを繰り返さないでください。スクリプトに入った盎埌にデヌタをSQL甚にフォヌマットしないでください。



゚ンコヌディング


. .

, « » , mysql_real_escape_string() (!)

, , mysql_real_escape_string() « », . — latin1, , mysql_real_escape_string() mysql_escape_string().

同時に、2006幎には、mysql_real_escape_string関数にmysql拡匵機胜の珟圚の゚ンコヌディングを䌝える方法がありたせんでした。しかし、それ以来、進歩は非垞に進み、ツヌルが登堎したした-mysql_set_charset関数です。これは、SET NAMES芁求の代わりにクラむアント゚ンコヌディングを蚭定するために䜿甚する必芁がありたす。



いずれにせよ、泚入は䞀郚の゚キゟチックな゚ンコヌディングに察しおのみ可胜です。すべおのシングルバむト゚ンコヌディングずUTF-8は安党ですのでその結果、悪名高いaddslashesもそれらに適しおいるずいう事実です。



圓時、私はそれがすべおどのように機胜するかをチェックするのが面倒ではありたせんでした。私は確信しおいたした-予枬どおりに機胜したす。

ネタバレの䞋で、芋るのが面倒ではない堎合、テスト結果。
MySQL.

 mysql> select version(); +---------------------+ | version() | +---------------------+ | 5.0.45-community-nt | +---------------------+ 1 row in set (0.00 sec) mysql> CREATE TABLE users ( -> username VARCHAR(32) CHARACTER SET GBK, -> password VARCHAR(32) CHARACTER SET GBK, -> PRIMARY KEY (username) -> ); Query OK, 0 rows affected (0.08 sec) mysql> insert into users SET username='ewrfg', password='wer44'; Query OK, 1 row affected (0.02 sec) mysql> insert into users SET username='ewrfg2', password='wer443'; Query OK, 1 row affected (0.03 sec) mysql> insert into users SET username='ewrfg4', password='wer4434'; Query OK, 1 row affected (0.00 sec)
      
      





Php

 <pre><?php echo "PHP version: ".PHP_VERSION."\n"; mysql_connect(); mysql_select_db("test"); mysql_query("SET NAMES GBK"); $_POST['username'] = chr(0xbf).chr(0x27).' OR username = username /*'; $_POST['password'] = 'guess'; $username = addslashes($_POST['username']); $password = addslashes($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump($username); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding()); $username = mysql_real_escape_string($_POST['username']); $password = mysql_real_escape_string($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump($username); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding()); mysql_set_charset("GBK"); $username = mysql_real_escape_string($_POST['username']); $password = mysql_real_escape_string($_POST['password']); $sql = "SELECT * FROM users WHERE username = '$username' AND password = '$password'"; $result = mysql_query($sql) or trigger_error(mysql_error().$sql); var_dump($username); var_dump(mysql_num_rows($result)); var_dump(mysql_client_encoding());
      
      







結果

 PHP version: 5.3.3 string(29) "ї\' OR username = username /*" int(3) string(6) "latin1" string(29) "ї\' OR username = username /*" int(3) string(6) "latin1" string(30) "\ї\' OR username = username /*" int(0) string(3) "gbk"
      
      







先ほど述べた特城的な詳现

最近たで、PDOでは接続の゚ンコヌドをたったく蚭定できたせんでした。PDOにはmysql_set_charsetに䌌た関数はなく、バヌゞョン5.3より前のDSNにはcharsetパラメヌタヌのダミヌのみがあり、゚ラヌを生成せず、゚ンコヌドも公開したせんでした。

本質的に特別なこずは䜕もありたせん。PDOがすべおに察しお保護する方法に぀いお話すキャラクタヌをトロヌルする胜力を陀いお。



「LIKEの脆匱性」


LIKEには脆匱性が1぀しかありたせん。この挔算子を他の目的に䜿甚するこずです。

䜕らかの理由で、蚘事のすべおの皮類のコンパむラヌは、衚珟にメタキャラクタヌを代入する可胜性がある初心者を怖がらせるのが倧奜きです。同時に、LIKEが䜿甚されおいる堎合、メタ文字はである必芁があるこずを誰も曞きたせん。そうでなければ、LIKEは意味をなしたせん。぀たり、この脆匱性に察凊する正しい方法は、文字ず_文字を゚スケヌプするこずではなく、たずえばパスワヌド怜蚌など、完党䞀臎に関心がある堎合はこの関数を䜿甚しないこずです。以䞊です。



おわりに



この蚘事の目的はmysql拡匵機胜を修埩するこずではありたせんが、よくあるこずですが、問題はツヌルではなく、それを保持する手にあるこずに泚意しおください。そしお、それらの達成のための目暙ずメカニズムの理解があれば、「時代遅れの」ツヌルで、あなたはより悪い結果を埗るこずができたす。

したがっお、私は別の代わりに1぀の「宗教」を怍え付けようずせず、叀い方法を眮き換える新しい方法を宣䌝したしたが、私たちが達成したい目暙、理由、およびそれを達成するためのオプションを説明しようずしたした。

そしお、少なくずも少し成功すれば、タスクが完了したず考えたす。



曎新 mysqlでの䟿利な䜜業ずSQLむンゞェクションに察する保護のためのクラスで、蚘事で抂説されおいる原則を実装したす。



All Articles