PHP セキュリティ プログラミング

ログインしている人にだけCSVファイルや動画などのメディアを返すPHPページを作った話。

コトの発端

ログインしている人だけが見られるページとかはよくあるけど、画像とかをドキュメントルートに置いているとログインしてなくても見られちゃうよね。

概要

ログイン認証が必要なPHPファイルを作成して、メディアタイプを指定してからファイルを出力してあげればOK。

サーバ内の実ファイルのパスはパラメータに格納すると、攻撃(ディレクトリトラバーサル)の的になるからだめだよ。

実装サンプル

セッションにて保持しているユーザ情報とGETパラメータに応じて、データベースに問い合わせ、メディアを返すプログラム。

KINDが1だと動画、2だとCSVを返すようにしている。

<?php
    //DBアクセス情報
    require("../../dbinfo.php");

    //ログイン認証処理→ログインしてなければ所定のページへ飛ぶ実装を想定。
    require_logined_session();
    $LOGINED_ID = $_SESSION['LOGINED_USERID'];

    //GETパラメータの取得
    //レコードIDと、メディア種別→1.動画 2,CSV
    $RECORDID = filter_input(INPUT_GET, "RECORDID");
    $KIND = filter_input(INPUT_GET, "KIND");

    //DBへの問い合わせ。所有権の確認もここで行っている。
    //DBにローカルのファイルパスが格納されている。
    //間違ってもURLにファイルのパスを格納しちゃだめだぞ。ディレクトリトラバーサル食らうぞ。
    $res = false;
    try{
        $dbh = new PDO($dsn, $user,$password,array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false));

        $chkSQL = "
            select
                RECORDID,
                MOVIE_FILEPATH,
                CSV_FILEPATH
            from
                T_RECORD
            where
                RECORDID = :RECORDID
                and
                OWNER_USERID = :OWNER_USERID
            ;
        ";
        $chkStmt = $dbh->prepare($chkSQL);
        $chkStmt->bindParam(":RECORDID", $RECORDID);
        $chkStmt->bindParam(":OWNER_USERID", $LOGINED_ID);
        $chkStmt->execute();
        if($chkResult = $chkStmt->fetch()){
            $res = true;

            $path_movie = $chkResult["MOVIE_FILEPATH"];
            $path_csv = $chkResult["CSV_FILEPATH"];
            
        }
        else{
            $res = false;
        }
    }
    catch(PDOExeption $e){
        $res = false;
    }
    $dbh = null;

    //DBからのパス取得に失敗してれば403返そうか。
    if($res === false)
    {
        http_response_code(403);
        exit;
    }

    //GETのKINDによってメディアの分岐
    if($KIND=="1"){
        //1.動画の場合
        $path=$path_movie;
        if (file_exists($path)) {
            header("Accept-Ranges: bytes");
            header('Content-Type: video/mp4');
            header('Content-Length: '. filesize($path));
            header('Content-Disposition: inline; filename="'. rawurlencode(basename($path)) .'"');
            ob_end_clean();
            readfile($path);
        }
        else{
            http_response_code(404);
        }
    }
    else if($KIND=="2"){
        //2.CSVの場合
        $path=$path_csv;
        if (file_exists($path)) {
            header('Content-Type: text/csv');
            header('Content-Length: '. filesize($path));
            header('Content-Disposition: inline; filename="'. rawurlencode(basename($path)) .'"');
            ob_end_clean();
            readfile($path);
        }else{
            http_response_code(404);
        }
    }
    else{
        http_response_code(403);
    }
    exit;
    

使い方例

Content-Typeを指定して出力しているので、csvや.mp4などに直接アクセスするような感覚で使用できる。

例えば、パラメータ付きURLをメディアとして、HTMLタグに組み込んだり、javascriptの動画として指定できたりする。

<video src="idmedia.php?RECORDID=12&KIND=1"></video>

いつもの

記事の正確性は無保証です。

  • この記事を書いた人
あっきー

あっきー

とある企業の研究者。研究分野以外に手を出しすぎて毎日が慌ただしい。 研究者の肩書きが正しいかどうかは万年の謎。 得意ジャンルはデータベースとセキュリティーですが、AIやIoT、アプリ開発など、手広く活動しています。

-PHP, セキュリティ, プログラミング
-, ,

Translate »