LoginSignup
blue-chicken
@blue-chicken

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

csrf対策が機能しない

解決したいこと

画面から送信されたhiddenタグのトークンと、セッションに保存されたトークンを一致
させたい。

<状況>
mampを用いたローカル環境では、問題なくログインできていたウェブ日報登録システム
ですが、レンタルサーバーを借りて、ネットに公開すると、ログインできなくなってしま
いました。
具体的には、正しいログイン情報(id及びpassword)を入力し、システムにログインした
際に、CSRF対策として、hiddenタグのトークンとセッションに保存されたトークンが一致
しているか確認しています。
その際に、一致している場合は、業務リスト画面へ画面遷移、それ以外は、エラー表示を
するようにプログラムしていました。

その結果、正しくログインされなかった場合はerror.phpに遷移するようにプログラム設定
していたので、正しいログイン情報を入力しているにも関わらず、正しくログインされずに、
login.phpからerror.phpに遷移してしまいます。
本来、ローカル環境で実行した時は、login.php→list.phpに画面遷移しておりました。

<環境>
・レンタルサーバー(お名前ドットコムを使用)
・phpのバージョンは7.4

解決方法を教えていただければ幸いです。

該当するソースコード

【login.php】

<?php
require_once(dirname(__FILE__) . '/config/config.php');
require_once(dirname(__FILE__) . '/functions.php');

try {
  session_start();

  if (isset($_SESSION['USER'])) {
    //ログイン済みの場合はHOME画面へ
    redirect('./list.php');
  }

  if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    //POST処理時

    check_token();

    //1.入力値を取得
    $user_id = $_POST['user_id'];
    $password = $_POST['password'];

    //2.バリデーションチェック
    $err = array();
    if (!$user_id) {
      $err['user_id'] = '社員IDを入力して下さい。';
    } elseif (!preg_match('/^[0-9]+$/', $user_id)) {
      $err['user_id'] = '社員IDを正しく入力して下さい。';
    } elseif (mb_strlen($user_id, 'utf-8') > 20) {
      $err['user_id'] = '社員IDが長すぎます。';
    }
    if (!$password) {
      $err['password'] = 'パスワードを入力して下さい。';
    }

    if (empty($err)) {
      //3.データベースに照合
      $pdo = connect_db();
      $sql = "SELECT * FROM user WHERE user_id = :user_id LIMIT 1";
      $stmt = $pdo->prepare($sql);
      $stmt->bindValue(':user_id', $user_id, PDO::PARAM_STR);
      $stmt->execute();
      $user = $stmt->fetch(); 

      if ($user && password_verify($password, $user['password'])) {
        //4.ログイン処理(セッションに保存)
        $_SESSION['USER'] = $user;

        //5.HOME画面へ遷移
        redirect('./list.php');

      } else {
        $err['password'] = '認証に失敗しました。';
      }
    }
  } else {
    //画面初回アクセス時
    $user_id = "";
    $password = "";

    set_token();
  }
  $page_title = '日報登録システム';
} catch (Exception $e) {
  //エラー時の処理
  redirect('./error.php');
}
?>

<!doctype html>
<html lang="ja">

<?php include(dirname(__FILE__) . '/templates/head_tag.php'); ?>

<body class="text-center bg-white">
  <h1>Web日報登録</h1>

  <form class="border rounded form-login" method="post">
    <h2 class="h3 my-3">Login</h2>

    <div class="form-group pt-3">
      <input type="text" class="form-control rounded-pill<?php if (isset($err['user_id'])) echo ' is-invalid'; ?>" 
      name="user_id" value="<?= $user_id ?>" placeholder="社員ID" required>
      <div class="invalid-feedback"><?= $err['user_id'] ?></div>
    </div>

    <div class="form-group pt-3">
      <input type="password" class="form-control rounded-pill<?php if (isset($err['password'])) echo ' is-invalid'; ?>" 
      name="password" placeholder="password">
      <div class="invalid-feedback"><?= $err['password'] ?></div>
    </div>
    <button type="submit" class="btn btn-primary rounded-pill mt-3">ログイン</button>
    <input type="hidden" name="CSRF_TOKEN" value="<?= $_SESSION['CSRF_TOKEN'] ?>">
  </form>
【function.php】
  <?php
//データベースに接続する
function connect_db(){
    $param = '省略';
    $pdo = new PDO(省略);
    $pdo->query('SET NAMES utf8;');
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE,PDO::FETCH_ASSOC);
    return $pdo;
    }

    //日付を日(曜日)の形式に変換する
    function time_format_dw($date){
        $format_date = NULL;
        $week = array('日','月','火','水','木','金','土');
        if($date){
            $format_date = date('j('.$week[date('w',strtotime($date))].')',strtotime($date));
        }
        return $format_date;
    }

    // 時間のデータ形式を調整する
     function format_time($value) {
    if (!$value || $value == '00:00:00') {
        return NULL;
    } else {
        return date('H:i', strtotime($value));
    }
}

//HTMLエスケープ処理(xssクロスサイド・スクリプティング対策)
function h($original_str){
    return htmlspecialchars($original_str, ENT_QUOTES, 'UTF-8');
}

//トークンを発行する処理
function set_token(){
    $token = sha1(uniqid(mt_rand(), true));
    $_SESSION['CSRF_TOKEN'] = $token;
}

//トークンをチェックする処理
function check_token(){
    if(empty($_SESSION['CSRF_TOKEN'])||($_SESSION['CSRF_TOKEN']!= $_POST['CSRF_TOKEN'])){
        unset($pdo);
        header('Location:./error.php');
        exit;
    }
}

//時間の形式チェックを行う
function check_time_format($time){
    if (preg_match('/^([01]?[0-9]|2[0-3]):([0-5][0-9])$/', $time)) {
        return true;
    }else{
            return false;
        }
}

//指定されたPHPへリダイレクトする
function redirect($path){
    unset($pdo);
    header("Location:$path");
    exit;
}
?>

自分で試したこと

・バリデーションチェックまではできているので、ログインボタンを押した後の動きで不具合が起きていると考え、セッションとトークンが一致していないと考えました。

0

1Answer

現状エラー表示がされます。

エラーメッセージなど詳細を記載してください。

csrf対策に問題があると判断されているようですが、その理由は何ですか?

1Like

Comments

  1. @blue-chicken

    Questioner
    @blue32a 様
    前回に引き続き、ご返信ありがとうございます。

    「現状エラー表示されます」についてですが、
    申し訳ございません。私の使用した言葉が誤っていました。
    正確には、正しくログインされなかった場合はerror.phpに遷移するようにプログラム設定したので、「正しいログイン情報を入力しているにも関わらず、正しくログインされずに、login.phpからerror.phpに遷移する」でございます。
    本来、ローカル環境で実行した時は、login.php→list.phpに画面遷移しておりました。

    はい。確証はないのですが、下記2点を確認し、csrf対策に問題があると考えました。
    ①トークンセットができているかの確認を行いました。
    //トークンを発行する処理
    function set_token(){
    $token = sha1(uniqid(mt_rand(), true));
    $_SESSION['CSRF_TOKEN'] = $token;

    echo'<pre>';
    var_dump($token);
    echo'</pre>';
    exit;
    }

    string(40) "1291af1e0cc4f5599b031b1fb91f70f18a1d0e85"
    →トークンが発行できている。

    ②グーグルデベロッパーツールで$_POST['CSRF_TOKEN']を探しました。
    実際にエラー表示される画面に移動し、グーグルデベロッパーツールを用いて、フォームから送られているデータが無いか探しましたが、ありませんでした。

    以上より、
    『 //トークンをチェックする処理  function check_token(){ 』で設定しているファンクションにより、『 ($_SESSION['CSRF_TOKEN']!= $_POST['CSRF_TOKEN']) 』と判断され、エラー画面に遷移しているものと考えました。
  2. > ②グーグルデベロッパーツールで$_POST['CSRF_TOKEN']を探しました。
    > 実際にエラー表示される画面に移動し、グーグルデベロッパーツールを用いて、フォームから送られているデータが無いか探しました

    error.phpで確認している場合、すでにリダイレクトした後なので$_POSTの値は無いはずです。

    1. ログインページ表示(GET login.php)
    2. ログイン(POST login.php)
    3. エラーページへリダイレクト(GET error.php)

    という流れになっていると思うので、1からPOSTしたデータは3には引き継がれません。

    コードの中でerror.phpにリダイレクトするケースは2つあります。
    どちらからリダイレクトしているのか、プログラムがどのように動いているのか確認しましょう。

    ちなみに、レンタルサーバーで動かしてからデータベース接続は確認しましたか?
    環境が変わってこのあたりで問題が出る可能性は高いです。
  3. @blue-chicken

    Questioner
    @blue32a 様
    本件解決できました。
    @blue32aさんがご指摘された全くその通りでした。
    エラーが発生していたのは、「データベース接続」に原因がありました。
    ローカル環境で正しく動いていたことから、改めてどのルートで処理が行われているか、レンタルサーバーに切り替えた後に編集したソースコードに誤りはないかを探した所、見つけることができました。

    //データベースに接続する
    function connect_db(){
    $param = '省略';
    $pdo = new PDO(省略);
    省略内に記載していたDBへアクセルするパスワードの後に";"を入力漏れがありました。

    今回も的確なアドバイスありがとうございました。半ば諦めていたので、非常に助かりました。
    誠にありがとうございました。

Your answer might help someone💌