web-dev-qa-db-ja.com

私のPHPログインシステムは安全ですか?

私はPHP=に不慣れです。これは私の最初のログインシステムでもあるので、皆さんが私のコードを調べて、セキュリティホールを見つけることができるかどうかを確認できればすばらしいでしょう。

注:ここには表示されていませんが、すべてのユーザー入力をサニタイズしています。

サインアップ:

ステップ1:ユーザーが選択したパスワードを取得し、この関数で実行します。

_encrypt($user_chosen_password, $salt);

function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}
_

ステップ2:次に、ハッシュとソルト(_$password['hash']_および_$password['salt']_)をデータベースのusersテーブルに格納します。

_id | username | password  | salt       | unrelated info...
-----------------------------------------------------------
1  | bobby    | 809a28377 | 809a28377f | ...
                fd131e5934
                180dc24e15
                bbe5f8be77
                371623ce36
                4d5b851e46
_

ログインする:

ステップ1:ユーザーが入力したユーザー名を取得し、データベースを検索して、行が返されるかどうかを確認します。私のサイトでは2人のユーザーが同じユーザー名を共有できないため、ユーザー名フィールドには常に一意の値があります。 1行が返された場合、そのユーザーのソルトを取得します。

ステップ2:次に、ユーザーが入力したパスワードを(前述のように)暗号化機能で実行しますが、今回はデータベースから取得したソルトも提供します。

_encrypt($user_entered_password, $salt);
_

ステップ3:この変数で照合する適切なパスワードを取得しました:_$password['hash']_。そこで、データベースの2回目の検索で、入力したユーザー名とハッシュ化されたパスワードが1つの行を返すかどうかを確認します。その場合、ユーザーの資格情報は正しいです。

ステップ4:資格情報が渡された後にユーザーをログインさせるために、ランダムな一意の文字列を生成してハッシュします。

_$random_string = uniqid(Rand(0, 1000000));
$session_key = hash('sha512', $random_string);
_

次に、データベースの_$session_key_テーブルに_active_sessions_を挿入します。

_user_id | key
------------------------------------------------------------
1       | 431b5f80879068b304db1880d8b1fa7805c63dde5d3dd05a5b
_

ステップ5:

最後のステップで生成された暗号化されていない一意の文字列(_$random_string_)を取得し、それを_active_session_と呼ぶCookieの値として設定します。

_setcookie('active_session', $random_string, time()+3600*48, '/');
_

ステップ6:

_header.php_インクルードの上部には、次のチェックがあります。

_if(isset($_COOKIE['active_session']) && !isset($_SESSION['userinfo'])) {
   get_userinfo();
}
_

get_userinfo()関数は、データベース内のusersテーブルを検索し、userinfoというセッションに格納されている連想配列を返します。

//最初に、この関数はactive_session Cookieの値を取得してハッシュし、session_keyを取得します。

_hash('sha512', $random_string);
_

//次に、_active_sessions_テーブルを検索して、このkeyによるレコードが存在するかどうかを確認します。存在する場合は、そのレコードに関連付けられている_user_id_を取得し、これを使用してusersテーブルをもう一度検索してuserinfoを取得します。

_    $_SESSION['userinfo'] = array(
        'user_id'           => $row->user_id,
        'username'          => $row->username,
        'dob'               => $row->dob,
        'country'           => $row->country,
        'city'              => $row->city,
        'Zip'               => $row->Zip,
        'email'             => $row->email,
        'avatar'            => $row->avatar,
        'account_status'    => $row->account_status,
        'timestamp'         => $row->timestamp,
    ); 
_

userinfoセッションが存在する場合、ユーザーが認証されていることがわかります。存在しないが_active_session_ Cookieが存在する場合、_header.php_ファイルの上部にあるチェックにより、そのセッションが作成されます。

セッションだけでなくCookieを使用しているのは、ログインを維持するためです。したがって、ユーザーがブラウザを閉じた場合、セッションは失われる可能性がありますが、Cookieは存在し続けます。また、_header.php_の上部にそのチェックがあるため、セッションは再作成され、ユーザーは通常どおりログインしたユーザーとして機能できます。

ログアウト:

ステップ1:userinfoセッションと_active_session_ Cookieの両方が設定されていません。

ステップ2:データベース内の_active_sessions_テーブルから関連付けられたレコードが削除されます。


注:私が見ることができる唯一の問題(そしておそらく他にも多くの問題がある)は、ユーザーがブラウザで自分自身を作成し​​て_active_session_ Cookieを偽造する場合です。もちろん、そのCookieの値として、暗号化された後、_active_sessions_テーブルのレコードと一致する文字列を設定する必要があります。このテーブルから_user_id_を取得して、そのセッションを作成します。ユーザーが(おそらく自動化されたプログラムを使用して)文字列を正しく推測し、それが知られていない文字列がsha512暗号化されて_active_sessions_の文字列と照合される可能性があるため、これが現実的にどのような可能性があるのか​​はわかりませんデータベース内のテーブルを使用して、そのセッションを構築するためのユーザーIDを取得します。

大きなエッセイで申し訳ありませんが、これは私のサイトの非常に重要な部分であり、経験不足のため、経験豊富な開発者が実際に安全であることを確認するために実行したかっただけです。

では、このルートにセキュリティホールはありますか、またどのように改善できますか?

40
TK123

ブルートフォース攻撃を防ぐために、何らかのタイムアウトまたはフェイルオーバーを含める必要があります。これには、IPベースのブロック、増分タイムアウトなど、いくつかの方法があります。これらはどれもstopハッカーではありませんが、はるかに困難になる可能性があります。

別のポイント(あなたが言及していないので、私はあなたの計画を知りません)は失敗メッセージです。失敗メッセージをできるだけ曖昧にします。 「そのユーザー名は存在しますが、パスワードが一致しませんでした」などのエラーメッセージを提供することは、エンドユーザーには役立つかもしれませんが、killsログイン機能です。 O(n^2)時間がかかるブルートフォース攻撃をO(n) + O(n)に変換しました。レインボーテーブルのすべての順列を試す必要はなく(たとえば)、ハッカーは失敗メッセージが変更されるまで、最初に(設定されたパスワードを使用して)ユーザー名のすべての値を試します。次に、それは有効なユーザーを認識し知っているため、パスワードを総当たりにする必要があります。

これらの行に沿って、ユーザー名が存在する場合と存在しない場合でも同じ時間が経過することを確認する必要があります。ユーザー名が実際に存在する場合、追加のプロセスを実行しています。そのため、ユーザー名が存在する場合と存在しない場合では、応答時間が長くなります。信じられないほど熟練したハッカーは、有効なユーザー名を見つけるためにページ要求の時間を計ることができます。

同様に、Cookieの期限切れに加えて、セッションテーブルも期限切れにする必要があります。

最後に、get_user_info()呼び出しで、同時にアクティブなログインが複数ある場合は、開いているすべてのセッションを終了する必要があります。一定の非アクティブな時間(30分など)が経過した後は、必ずセッションをタイムアウトしてください。

@Greg Hewgillが述べたことに沿って、次の項目は含まれていません。

  • サーバー/クライアント間のSSL /暗号化接続
  • 認証の処理に使用しているその他のトランスポートプロトコル(OAuthなど)

あなたserverは安全ですが、交換されたデータ(MITM)を誰かが読み取れる場合、アルゴリズムがどれほど安全かは関係ありません。暗号化されたプロトコルを介してのみ通信していることを確認する必要があります。

29
sethvargo

...ユーザーが入力したパスワードを暗号化機能で実行します...

では、パスワードはどのようにしてブラウザからサーバーに送られるのでしょうか。中間者攻撃からの保護については言及していません。

9
Greg Hewgill

このコード...

_function encrypt($plain_text, $salt) {
    if(!$salt) {
        $salt = uniqid(Rand(0, 1000000));
    }
    return array(
        'hash' => $salt.hash('sha512', $salt.$plain_text),
        'salt' => $salt
    );
}
_

...悪い。 新しいパスワードAPIを使用する で、これで完了です。専門家でない限り、独自の認証システムを設計しようとするべきではありません。 正しく理解するのは非常に難しい

資格情報が渡された後にユーザーをログインさせるために、ランダムな一意の文字列を生成してハッシュします。

ただ let PHPセッション管理を処理するRand()mt_Rand()はどちらも非常に安全でない乱数ジェネレータです。

6

作成したコードは、自動化された単体テストと統合テストではテストできないようです。これにより、本番環境での実行中に、時間の経過とともに実装に含まれる可能性のあるエラーを特定することが難しくなります。

厳密で正しい実行と正しいデータ処理のセキュリティがテスト/検証されていないため、これは通常セキュリティの問題につながります。

(これはリストの別のポイントです。トランスポート層を保護する方法についての回答も参照してください。また、セッションデータが改ざんされないように保護する方法も指定していません。)

3
hakre

Phpのパスワードについては、暗号化しないでください。 password_hash()を使用してハッシュし、ログイン時にpassword_verify()を使用して、htmlフォームを介したパスワードがデータベースに保存されているハッシュと一致することを確認する必要があります

2

Rand()の代わりにmt_Rand()を使用する必要があります

理由は次のとおりです。

1