ゆるキャラのペパクラ情報サイトを改善する(2)SQLインジェクション対策をする

0
    JUGEMテーマ:ホームページ作成

    1.SQLインジェクションとは
    さて唐突に始まりました第2話、SQLインジェクションなる言葉がいきなり出てきましたが、これは一体なんでしょう?
    まずSQLとは、Sequential Query Lauguageの略で、言ってしまえばデータベースを操作するコマンドの事です。そこにインジェクションが付きます。インジェクションとは辞書で調べると、注入とか注射とか。併せるとSQLのコマンドに何かが注入されてしまうということになります。何かとはデータベースを操作する別のコマンドです。実際の例を見てみましょう。
    $gid  = $_GET['gid'];
    $sort = $_GET['sort'];
    $sql = "SELECT * FROM users WHERE gid=$gid ORDER BY $sort";
    $result = mysql_query($sql);
    
    これはA Day in Serenity(Reloaded)さんが公開しているサンプルを少し修正したものです。
    意図は恐らく、同じグループIDの人を抽出して、指定した順序で並べ替えるというものだと思います。
    でも、このgidとsort、ユーザーが自由に入力できるようになっていると、何が入ってくるかわかりません。
    例えば、悪意あるユーザがこんな入れ方をしたとします。
    gid=1
    sort=name; delete from users
    
    すると組み合わさってできるクエリはこうなります。
    SELECT * FROM users WHERE gid=1 ORDER BY name; delete from users
    
    これでグループID=1の人のデータは全てデータベースから消去されてしまいます。続けてgidを変えてゆけばデータベース全削除です。これがSQLインジェクションの恐ろしさです。
    私のゆるキャラペパクラインデックスなどは、いたずらで消されてしまったら私がしょんぼりするだけですが、人とやり方よってはデータベースの中身が逆に全部抜き取られ、個人情報の漏洩といったことにもなりかねません。これは穏やかではありません。

    この機会に、SQLインジェクションを受け付けないような対策を施してやろう、と考えたと言う訳なのです。


    2.プリペアドステートメントとプレースホルダとは
    SQLインジェクション対策として最も基本的なのが、「プリペアドステートメント」と「プレースホルダ」を使う方法です。
    プリペアドステートメントとは、SQLを文字列の結合で作ったりせずに、あらかじめひな型として準備しておく機能の事、プレースホルダとはそのひな型の中で、ユーザに指定してほしい部分に仮置きしておく枠のようなものです。

    先ほどの例をプリペアドステートメントとプレースホルダを使って書き換えてみましょう。
    $query = "SELECT * FROM users WHERE gid=:gid ORDER BY :sort";
    $stmt = $pdo->prepare($query);
    $stmt->bindValue(':gid', (int)$_GET["gid"], PDO::PARAM_INT);
    $stmt->bindValue(':sort', $_GET["sort"]);
    
    1行目がクエリのひな型で、中にある「:gid」と「:sort」がプレースホルダです。
    2行目でこれをプリペア=準備しています。
    そして3行目と4行目で、プレースホルダに値を埋め込んでいます。こうすることで、gidやsortはクエリの中の各所に相当するパラメータとして認識されますので、変なコマンドを入れてもコマンドとして認識されなくなるので、安全にクエリを発行できるようになるのです。


    3.PDOとは
    ところで、先ほどの例、見慣れないコマンドが出てきたと思います。これはPDOという仕組みを利用した書き方になっているのですがこのPDOとはなんでしょう?

    PDOは「PHP Data Object」の略で、PHPでデータベースに接続するためのクラスです。クラスとは、おおざっぱに言うと、データを取り込む変数たちとそれを操作するコマンドがセットになったようなものです。併せると、PDOはデータベースに接続するために必要な情報を入れる箱と、データベース接続にかかわる種々の手続き・関数・コマンド等がセットになった便利な道具と思ってもらえればいいでしょう。
    クラスの概念が腑に落ちないという人は、こちらのサイトに一から解説してありますので、こちらをご覧いただくといいと思います。
    【PHP超入門】クラス〜例外処理〜PDOの基礎

    まあつまり、PDOにはデータベースと安全に接続するための機能・手続きがまとまっていますので、mysql関数は止めてPDOでデータベースとの接続周りを書き換えてやろうということなわけです。先ほどのプリペアドステートメントもこのPDOで実現できる機能の一つです。


    4.PDOの使い方
    それではPDOを使ってデータベースにアクセスする手順を追ってみましょう。
    まずは、データベースとの接続です。こうなります。
    $pdo = new PDO(
      'mysql:dbname=LA********-mysql5;host=********.phy.lolipop.lan;charset=utf8',
      'LA********',
      'password',
      array(
        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_EMULATE_PREPARES => false,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
      )
    );
    
    1行目:PDOというクラスのひな型から新しい接続「$pdo」を作りなさい、という宣言になります。
    2行目:接続先のデータベース名、ホスト名、文字コードを指定します。
    3行目:ユーザIDです。
    4行目:パスワードです。もちろんダミーです。
    5〜10行目:オプションを色々設定しています。
    ・PDO::ATTR_ERRMODE:SQL実行時にエラーが起きた際の処置。上記は例外をスローしてくれる設定です。
    ・PDO::ATTR_EMULATE_PREPARES:エミュレーション有無の設定。上記はエミュレーションoff。
    ・PDO::ATTR_DEFAULT_FETCH_MODE:カラム名をキーにした連想記憶配列でデータを取得する設定。
    ・PDO::MYSQL_ATTR_USE_BUFFERED_QUERY:バッファクエリを使用する設定。

    詳しくはQiitaさんに記事がありましたので、そちらもご参照ください。
    PHPでデータベースに接続するときのまとめ

    データベースと接続したら、次はクエリの発行です。
     
    $stmt1 = $pdo->prepare('SELECT * FROM table名 WHERE `index` = :index');
    $stmt1->bindValue(':index', (int)$_POST["id"], PDO::PARAM_INT);
    $stmt1->execute();
    $row1 = $stmt1->fetch();
    if($_POST["password"]<>$row1["password"]){$errorflg=1; $errorflg4=1;}
    

    これは、パスワードチェックを行っている部分のサンプルです。
    1行目:先ほど出てきたprepareの指示です。「->」はクラスがもっている機能「メソッド」を呼び出すときの記号です。
    2行目:プレースホルダに値をバインドしています。
    3行目:クエリを実行するコマンドです。
    4行目:クエリの結果を受け取っています。先ほどの接続オプションで連想記憶配列を指定しましたので、次の5行目ではカラム名をキーにしてパスワードの値を読み出しています。

    同じデータベースに別のクエリを発行したいときは、$pdoはそのまま使い、$stmtの方を色々書き換えてやればOKです。

    最後にすべて終わって接続を切るときは、次のように書きます。
    $pdo = null;
    


    4.ypindex.phpの改修
    それではいよいよ実際のコードを修正していきます。今回は、前に作った、データベースを検索して結果を表にして表示するスクリプト「ypindex.php」を修正していきます。
    まずmysql関数を使った旧コードがこちら。
    $link = mysql_connect('********.phy.lolipop.lan', 'LA********', 'password');
    $db_selected = mysql_select_db('LA********-mysql5', $link);
    $charaset = mysql_query('SET NAMES utf8', $link );
    $query1 = 'SELECT * FROM テーブル名';
    $query1 = $query1.' WHERE name LIKE ¥'%'.$searchword.'%¥'';
    $query1 = $query1.' or area LIKE ¥'%'.$searchword.'%¥'';
    $query1 = $query1.' or affiliation LIKE ¥'%'.$searchword.'%¥'';
    $query1 = $query1.' or note LIKE ¥'%'.$searchword.'%¥'';
    $result1 = mysql_query($query1);
    $numRow1 = mysql_num_rows($result1);
    $itemPerPage = 20;
    $numPage = ceil($numRow1/$itemPerPage);
    
    今見ると恐ろしい書き方をしていますね。ではPDOを使った新コードがこちら。
    $pdo = new PDO(
    'mysql:dbname=LA********-mysql5;host=********.phy.lolipop.lan;charset=utf8',
    'LA********',
    'password',
    array(
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_EMULATE_PREPARES => false,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => true
    )
    );
    $stmt1 = $pdo->prepare('SELECT * FROM テーブル名 WHERE name LIKE :name or area LIKE :area or affiliation LIKE :affiliation or note LIKE :note');
    $stmt1->bindValue(':name', '%'.$searchword.'%');
    $stmt1->bindValue(':area', '%'.$searchword.'%');
    $stmt1->bindValue(':affiliation', '%'.$searchword.'%');
    $stmt1->bindValue(':note', '%'.$searchword.'%');
    $stmt1->execute();
    $numRow1 = $stmt1->rowCount();
    $itemPerPage = 20;
    $numPage = ceil($numRow1/$itemPerPage);
    
    新しく出てきたのはrowCount()というメソッドです。単に行数を数えているだけです。一旦全体の行数を取得してから、これでページナビゲーションを作ったうえで、指定した部分の20行を呼び出すようにしています。その部分の新コードがこちら。
    $startLine = ($page-1)*20;
    $stmt = $pdo->prepare('SELECT * FROM テーブル名 WHERE name LIKE :name or area LIKE :area or affiliation LIKE :affiliation or note LIKE :note LIMIT :startLine ,20');
    $stmt->bindValue(':name', '%'.$searchword.'%');
    $stmt->bindValue(':area', '%'.$searchword.'%');
    $stmt->bindValue(':affiliation', '%'.$searchword.'%');
    $stmt->bindValue(':note', '%'.$searchword.'%');
    $stmt->bindValue(':startLine', (int)$startLine, PDO::PARAM_INT);
    $stmt->execute();
    (中略)
    while ($row = $stmt->fetch()){
    
    最後のwhile文で1行ずつ取り出して表示させています。そして最後に、
    $pdo = null;
    
    で終了です。


    今回はここまで。次回は本命のデータベース修正スクリプトの作成です。

    (3)DB修正スクリプトの作成
    ・ypmod.phpの内部構成
    ・ypmod.phpの作成
    (4)画像登録処理
    ・画像の受け取り方
    ・画像の縮小方法
    ・画像の形式変換方法
    (5)削除スクリプトの作成
    ・ypdelete.phpの作成

     


    コメント
    コメントする








       

    calendar

    S M T W T F S
         12
    3456789
    10111213141516
    17181920212223
    24252627282930
    << November 2019 >>

    アクセスカウンタ

    合計:
    今日:
    昨日:

    selected entries

    categories

    archives

    recent comment

    • ホームページをリニューアルするの巻(19)−Googleウェブサイト翻訳ツールを組み込む
      てちくん
    • ホームページをリニューアルするの巻(19)−Googleウェブサイト翻訳ツールを組み込む
      てちくん
    • ホームページをリニューアルするの巻(19)−Googleウェブサイト翻訳ツールを組み込む
      小田きく江
    • ロリポブログでGoogleにサイトマップを登録する際の注意事項
      てちくん
    • ロリポブログでGoogleにサイトマップを登録する際の注意事項
      suraugi
    • いそべぇのペーパークラフトを作る(初級編)(12)
      てちくん
    • いそべぇのペーパークラフトを作る(初級編)(12)
      だべえ
    • noomでマイナス12kgのダイエットに成功!
      Yoko

    recommend

    recommend

    recommend

    ドール デザートメーカー ヨナナス901
    ドール デザートメーカー ヨナナス901 (JUGEMレビュー »)

    結構高いんです、でも欲しいんです!

    links

    profile

    書いた記事数:173
    最近の更新日:2017/01/30

    search this site.

    others

    mobile

    qrcode

    powered

    無料ブログ作成サービス JUGEM

    Google Adsense

    楽天ブックス

    楽天