web-dev-qa-db-ja.com

この種の認証は攻撃される可能性がありますか?

私はサイバーセキュリティの専門家ではありませんが、ウェブマスターであり、この種の認証は危険なのでしょうか。 PHPを使用して、サーバー側で次のようなパスワードをテストします。

if (isset($_POST['pass_Word']) AND $_POST['pass_Word'] ==  $passwd)

$passwdは私のPostgreSQL DBからのものです。少なくともSQLインジェクションのリスクはないと考えました。他の種類の注射のリスクはありますか?認証が危険な方法である場合は、その理由を説明してください。

21
kevin ternet

これは、パスワードを平文で保存しているようですが、これは悪い考えです。 password_hash()password_verify() を使用してパスワードが保護されていることを確認する必要があります。これらは bcrypt を使用します=フードの下。これは単純で、簡単で、安全で、ほとんどのシナリオで完全に受け入れられます。

または、 Argon2 などの少し安全な方法を使用することもできます。これは Password Hashing Competition を獲得し、CPUとGPUのクラッキングに耐性があり、また、サイドチャネル攻撃。 libsodiumのPHPラッパーの一部としてArgon2を使用する方法、またはParagonの "Halite"ライブラリを直接使用する方法を説明する 記事 があり、Argon2に対称暗号化を提供します。対称キーがファイルとしてサーバーのディスクに格納されるため、データベースのみのアクセスで使用可能なハッシュが提供されないようにします。このオプションはより複雑ですが、本当に偏執的である場合は、追加のセキュリティが提供されます。私は安全な開発に慣れていない場合は、これを回避することをお勧めします。

RLクエリの配列 またはnullを使用して、等号の誤ったケースを回避するために、===の使用もお勧めします。

68
Polynomial

多項式の答え はスポットです。他の潜在的な脆弱性、つまり演算子の優先順位、レビューのしやすさ、テストのしやすさを指摘したかったのです。

foo AND bar == baz形式の何かは、優先順位を間違って取得することに対して脆弱です。復習すると、複雑な条件と優先順位のためにセキュリティ対策が失敗した長い歴史があることを精査します。

さて、あなたが書いたものに間違ったは何もありませんが、それを誤解することは非常に簡単です。ほとんどの言語では、 比較演算子 のような==ANDのような論理演算子よりも優先順位が高いため、条件は次のように読み取られます(明示的な括弧に注意)。

isset($_POST['pass_Word']) AND
  ($_POST['pass_Word'] ==  $passwd)

...しかし、それは間違いを犯すのが非常に簡単です。たとえば、AND&&の優先順位は同じではありません。 issetを使用する代わりに、あなた(またはあなたの後に来る人)がショートカット論理を使用して、またはデフォルト値を設定した場合はどうなりますか? (これはissetと同等ではありません。これは、優先順位が原因でセキュリティが間抜けになる例にすぎません)

$_POST['pass_Word'] || '' ==  $passwd

この落とし穴ラインは実際にはこれです:

$_POST['pass_Word'] || ('' ==  $passwd)

これで、おそらくそれを取得するエラーのために、空白のパスワードを持つ誰もが常にログインします。

安全なコードに関しては、括弧を明示的にして、レビュー担当者とプログラムに意図を知らせます。


さらに良いことに、安全なコードでは完全に複合条件を避けてください。使っていないものを台無しにすることはできません。たとえば、このコードはカプセル化と early return を利用して、非常に簡単にレビューおよびテストできるメソッドを提供します。

// This deals with input and normalizing it.
function can_login($user) {
    if( !isset($_POST['pass_Word']) ) {
        // Or it could throw an exception to give the
        // caller more information about why they didn't login
        return false;
    }

    return check_password($user, $_POST['pass_Word']);
}

// This only deals with checking a password.
// Don't use this, it still has all the flaws Polynomial
// pointed out.
function check_password($user, $check) {
    // get_password() should throw an exception if it cannot
    // find that user's password to avoid accidentally thinking
    // their password is false or 0 or '' or something. This
    // avoids relying on the caller doing the check for failure.    
    return $check === get_password($user);
}

これは最善の方法ではないかもしれませんが、小さく、テストとレビューが簡単です。これにより、ユーザーのログインの詳細(有効なユーザーのように、パスワードのチェック以上のものになる可能性があります)とパスワードのチェックが分離されます。ログインコードの残りの部分を調べなくても、問題を見つけて修正できます。レビュー担当者はget_passwordをチェックし、すべての場所check_passwordcan_loginが使用されます。

安全なコンポーネントを可能な限りシンプルなコードを使用して可能な限り小さな部分に分離し、それらを簡単にレビューおよびテストできるようにすることがどれほど重要であるかを強調することはできません。


[〜#〜]ボーナス[〜#〜]:これは私が考えていることですが、コードlessを安全にすることを決定しました。

私の最初のアイデアは、複数の値を返す1つの関数を持つことでした。パスワードを提供しなかった、パスワードが間違っていた、パスワードが正しかった。これにより、発信者は「パスワードが間違っている」と「パスワードを提供しなかった、何かが間違っている」の違いを知ることができます。

function can_login($user) {
    if( !isset($_POST['pass_Word']) ) {
        return NO_PASSWORD;
    }

    $password = get_password($user);
    if( $_POST['pass_Word'] === $password ) {
        return YES;
    }
    else {
        return NO;
    }
}

そして、あなたはそれを次のように呼びます:

$rslt = can_login($user);

if( $rslt == YES ) {
    echo "Login!\n";
}
else if( $rslt == NO ) {
    echo "Wrong password.\n";
}
else if( $rslt == NO_PASSWORD ) {
    echo "No password given.\n";
}

問題は、ルーチンの呼び出しを適切に複雑にすることです。さらに悪いことに、ドキュメントを読まなければ、それはこれだと思います:

if( can_login($user) ) {
    echo "Yes!\n";
}
else {
    echo "No!\n";
}

can_loginは常に真の値を返すため、誰でもログインできるようになりました。

重要な点は、レビュー担当者、テスター、および呼び出し元に対して、安全なコードを可能な限りシンプルで間違いのないものにすることです。この場合、例外はcan_loginを単純なブール値に保ちながら、より多くの情報を取得するためのチャネルを提供するため、適切な方法です。チェックを忘れるとプログラムがクラッシュする可能性がありますが、ログインは失敗します。

15
Schwern