"로그인 유지"-최선의 방법


257

내 웹 응용 프로그램은 세션을 사용하여 로그인 한 사용자에 대한 정보를 저장하고 응용 프로그램 내에서 페이지를 이동할 때 해당 정보를 유지 관리합니다. 이 특정 응용 프로그램에서, 나는를 저장하고있어 user_id, first_name그리고 last_name사람의.

로그인시 "Keep Me Logged In (로그인 유지)"옵션을 제공하여 쿠키를 2 주 동안 사용자 컴퓨터에 저장하고 앱으로 돌아올 때 동일한 세부 정보로 세션을 다시 시작합니다.

이 작업을 수행하는 가장 좋은 방법은 무엇입니까? user_id쿠키에 쿠키 를 저장하고 싶지 않습니다 . 쿠키를 사용하면 한 사용자가 다른 사용자의 신원을 쉽게 만들고 위조 할 수있는 것처럼 보입니다.

답변:


735

자, 이것을 무뚝뚝하게하겠습니다 : 만약 사용자 데이터 또는이 목적을 위해 사용자 데이터에서 파생 된 것을 쿠키에 넣는다면, 당신은 뭔가 잘못하고있는 것입니다.

그곳에. 내가 말했어. 이제 실제 답변으로 넘어갈 수 있습니다.

사용자 데이터 해싱의 문제점은 무엇입니까? 글쎄, 그것은 모호함을 통해 노출 표면과 보안으로 귀착됩니다.

당신이 공격 자라고 잠시 상상해보십시오. 세션에서 remember-me에 대한 암호화 쿠키 세트가 표시됩니다. 32 자입니다. 사람. MD5 일 수도 있습니다.

또한 사용자가 사용한 알고리즘을 알고 있다고 잠시 상상해 봅시다. 예를 들면 다음과 같습니다.

md5(salt+username+ip+salt)

이제 공격자가해야 할 일은 "소금"(실제로 소금은 아니지만 나중에 더 자세히 설명)을 무차별 처리하는 것입니다. 이제 IP 주소의 모든 사용자 이름으로 원하는 모든 가짜 토큰을 생성 할 수 있습니다! 그러나 소금을 강제로 강제하는 것은 어렵습니다. 물론. 그러나 현대의 GPU는 그것을 능가합니다. 그리고 당신이 그것에 충분한 임의성을 사용하지 않으면 (충분히 크게 만드십시오), 그것은 빨리 떨어지고 성의 열쇠와 함께 떨어질 것입니다.

요컨대, 당신을 보호하는 유일한 것은 소금입니다. 생각만큼 많이 보호하지는 않습니다.

하지만 기다려!

이 모든 것이 공격자가 알고리즘을 알고 있다고 가정했습니다! 비밀스럽고 혼란 스러우면 안전합니까? 잘못되었습니다 . 그 사고 방식은 ' 불안을 통한 보안' 이라는 이름을 가지 는데, 절대로 의존 해서는 안됩니다 .

더 나은 방법

더 좋은 방법은 ID를 제외하고 사용자 정보가 서버를 떠나지 않도록하는 것입니다.

사용자가 로그인하면 큰 (128-256 비트) 임의 토큰을 생성하십시오. 토큰을 사용자 ID에 맵핑하는 데이터베이스 테이블에 추가 한 후 쿠키의 클라이언트로 전송하십시오.

침입자가 다른 사용자의 임의 토큰을 추측하면 어떻게됩니까?

자, 여기서 수학을하자. 128 비트 랜덤 토큰을 생성 중입니다. 즉, 다음이 있음을 의미합니다.

possibilities = 2^128
possibilities = 3.4 * 10^38

이제 그 숫자가 얼마나 터무니 없는지 보여주기 위해 인터넷상의 모든 서버 (오늘 5 만, 000이라고하자)가 초당 1,000,000,000의 속도로 그 숫자를 무차별 적으로 시도한다고 가정 해 봅시다. 실제로 서버는 이러한 부하로 인해 녹아 버릴 것입니다.

guesses_per_second = servers * guesses
guesses_per_second = 50,000,000 * 1,000,000,000
guesses_per_second = 50,000,000,000,000,000

따라서 초당 50 조의 추측. 빠릅니다! 권리?

time_to_guess = possibilities / guesses_per_second
time_to_guess = 3.4e38 / 50,000,000,000,000,000
time_to_guess = 6,800,000,000,000,000,000,000

그래서 6.8 sextillion 초 ...

좀 더 친근한 숫자로 가져 오도록합시다.

215,626,585,489,599 years

또는 더 나은 :

47917 times the age of the universe

예, 그것은 우주의 나이 47917 배입니다 ...

기본적으로 금이 간 것은 아닙니다.

요약하자면 다음과 같습니다.

내가 권장하는 더 좋은 방법은 쿠키를 세 부분으로 저장하는 것입니다.

function onLogin($user) {
    $token = GenerateRandomToken(); // generate a token, should be 128 - 256 bit
    storeTokenForUser($user, $token);
    $cookie = $user . ':' . $token;
    $mac = hash_hmac('sha256', $cookie, SECRET_KEY);
    $cookie .= ':' . $mac;
    setcookie('rememberme', $cookie);
}

그런 다음 유효성을 검사하십시오.

function rememberMe() {
    $cookie = isset($_COOKIE['rememberme']) ? $_COOKIE['rememberme'] : '';
    if ($cookie) {
        list ($user, $token, $mac) = explode(':', $cookie);
        if (!hash_equals(hash_hmac('sha256', $user . ':' . $token, SECRET_KEY), $mac)) {
            return false;
        }
        $usertoken = fetchTokenByUserName($user);
        if (hash_equals($usertoken, $token)) {
            logUserIn($user);
        }
    }
}

참고 : 데이터베이스에서 레코드를 조회하기 위해 토큰 또는 사용자와 토큰 조합을 사용하지 마십시오. 항상 사용자를 기준으로 레코드를 가져오고 타이밍 안전 비교 기능을 사용하여 나중에 가져온 토큰을 비교하십시오. 타이밍 공격에 대해 자세히 알아보십시오 .

이제 암호화 비밀이되는 것이 매우 중요합니다 SECRET_KEY( /dev/urandom높은 엔트로피 입력에서 생성 및 / 또는 파생 된 것). 또한, GenerateRandomToken()(강한 임의의 원천이 될 필요가 mt_rand()거의 강한 것만으로는 충분하지 않습니다. 같은 도서관, 사용 RandomLib 또는 random_compat , 또는 mcrypt_create_iv()과를 DEV_URANDOM) ...

hash_equals()방지하는 것입니다 타이밍 공격을 . PHP 5.6 이하의 PHP 버전을 사용하는 경우 기능 hash_equals()이 지원되지 않습니다. 이 경우 hash_equals()timingSafeCompare 기능으로 교체 할 수 있습니다 .

/**
 * A timing safe equals comparison
 *
 * To prevent leaking length information, it is important
 * that user input is always used as the second parameter.
 *
 * @param string $safe The internal (safe) value to be checked
 * @param string $user The user submitted (unsafe) value
 *
 * @return boolean True if the two strings are identical.
 */
function timingSafeCompare($safe, $user) {
    if (function_exists('hash_equals')) {
        return hash_equals($safe, $user); // PHP 5.6
    }
    // Prevent issues if string length is 0
    $safe .= chr(0);
    $user .= chr(0);

    // mbstring.func_overload can make strlen() return invalid numbers
    // when operating on raw binary strings; force an 8bit charset here:
    if (function_exists('mb_strlen')) {
        $safeLen = mb_strlen($safe, '8bit');
        $userLen = mb_strlen($user, '8bit');
    } else {
        $safeLen = strlen($safe);
        $userLen = strlen($user);
    }

    // Set the result to the difference between the lengths
    $result = $safeLen - $userLen;

    // Note that we ALWAYS iterate over the user-supplied length
    // This is to prevent leaking length information
    for ($i = 0; $i < $userLen; $i++) {
        // Using % here is a trick to prevent notices
        // It's safe, since if the lengths are different
        // $result is already non-0
        $result |= (ord($safe[$i % $safeLen]) ^ ord($user[$i]));
    }

    // They are only identical strings if $result is exactly 0...
    return $result === 0;
}

7
그러나이 방법이 다른 사용자로부터이 사용자 이름과 쿠키를 가져 와서이 사용자로 로그인 할 수 있다는 의미는 아닙니까?
더 간단한

8
lol :-), 47917 년이 추측 할 수있는 최대 시간이라는 점에 유의하십시오. 임의의 토큰도 1 시간 안에 추측 될 수 있습니다.
storm_buster

33
코드가 답변과 모순되기 때문에 이상합니다. "사용자 데이터를 쿠키에 넣으면 [...] 잘못된 일을하고 있습니다"라고 말하지만, 이것이 바로 코드가하는 일입니다! 쿠키에서 사용자 이름을 제거하고 토큰만으로 해시를 계산하고 쿠키 도난을 방지하기 위해 IP 주소를 추가하는 것이 낫지 않습니까? rememberMe ()에서 fetchTokenByUserName 대신 fetchUsernameByToken을 수행하십시오.
Leven

9
PHP 5.6부터 hash_equals 를 사용하여 문자열 비교를 수행 할 때 타이밍 공격을 방지 할 수 있습니다.
F21

5
@ Levit 누군가가 유효한 토큰을 가져 와서 첨부 된 사용자 ID를 변경하지 못하게합니다.
ircmaxell

93

보안 공지 : 결정적 데이터의 MD5 해시에서 쿠키를 만드는 것은 나쁜 생각입니다. CSPRNG에서 파생 된 임의 토큰을 사용하는 것이 좋습니다. 보다 안전한 접근 방법은이 질문에 대한 ircmaxell의 답변 을 참조하십시오 .

일반적으로 나는 다음과 같이합니다 :

  1. '로그인 상태 유지'로 사용자 로그인
  2. 세션 생성
  3. md5 (salt + username + ip + salt)를 포함하는 SOMETHING이라는 쿠키와 id를 포함하는 somethingElse라는 쿠키를 만듭니다.
  4. 데이터베이스에 쿠키 저장
  5. 사용자는 물건과 잎을한다 ----
  6. 사용자가 반환하고 다른 쿠키가 있는지 확인합니다. 쿠키가 존재하는 경우 해당 사용자의 데이터베이스에서 오래된 해시를 가져오고 쿠키의 내용을 확인합니다. 데이터베이스의 해시와 일치하는 항목이 있고 새로 계산 된 해시와 일치해야합니다. ip) 따라서 : cookieHash == databaseHash == md5 (salt + username + ip + salt), 만약 그렇다면, 2로 가십시오.

물론 다른 쿠키 이름 등을 사용할 수도 있습니다. 쿠키의 내용을 약간 변경할 수도 있습니다. 쿠키가 쉽게 만들어지지 않도록하십시오. 예를 들어 사용자를 만들 때 user_salt를 만들어 쿠키에 넣을 수도 있습니다.

또한 md5 (또는 거의 모든 알고리즘) 대신 sha1을 사용할 수 있습니다


30
해시에 IP를 포함시키는 이유는 무엇입니까? 또한 쿠키에 타임 스탬프 정보를 포함시키고이 정보를 사용하여 쿠키의 최대 수명을 설정하여 영원에 적합한 자격 증명 토큰을 만들지 않도록하십시오.
Scott Mitchell

4
@ Abhishek Dilliwal : 이것은 꽤 오래된 스레드이지만 Mathew와 동일한 답변을 찾고 있습니다. session_ID가 모든 session_start ()를 변경하기 때문에 db 해시, 쿠키 해시 및 현재 session_ID를 확인할 수 없기 때문에 session_ID를 사용하면 Pim의 답변에 효과가 없다고 생각합니다. 내가 이것을 지적 할 것이라고 생각했습니다.
Partack

3
미안하지만 미안하지만 두 번째 쿠키의 목적은 무엇입니까? 이 경우 id는 무엇입니까? 사용자가 로그인 상태 유지 기능을 사용 하려는지 여부를 나타내는 단순한 "true / false"값입니까? 그렇다면 쿠키 SOMETHING이 먼저 존재하는지 확인하는 것이 어떻습니까? 사용자가 로그인을 유지하지 않으려면 SOMETHING 쿠키가 처음에 존재하지 않습니까? 마지막으로, 해시를 동적으로 다시 생성하고 추가 보안 수단으로 쿠키와 DB에 대해 확인합니까?
itsmequinn

4
토큰은 임의의 방식으로 사용자 / 그의 IP / 그의 사용자 에이전트 / 아무 것과도 연결되지 않은 RANDOM이어야합니다. 주요 보안 결함입니다.
pamil

4
왜 두 개의 소금을 사용합니까? md5 (salt + username + ip + salt)
Aaron Kreider

77

소개

제목 "Keep Me Logged In"-최상의 접근 방식을 사용하는 경우 다음을 고려해야하므로 가장 좋은 접근 방식으로 시작 위치를 알기가 어렵습니다.

  • 신분증
  • 보안

쿠키

쿠키는 취약합니다. 일반적인 브라우저 쿠키 도난 취약점과 사이트 간 스크립팅 공격간에 쿠키가 안전하지 않다는 사실을 인정해야합니다. 보안을 향상 시키려면 다음 php setcookies과 같은 추가 기능이 있음에 유의해야합니다.

bool setcookie (string $ name [, string $ value [, int $ expire = 0 [, string $ path [, string $ domain [, bool $ secure = false [, bool $ httponly = false]]]]]])

  • 보안 (HTTPS 연결 사용)
  • httponly (XSS 공격을 통한 신원 도용 감소)

정의

  • 토큰 (예 : n 길이의 예상 할 수없는 임의 문자열 (예 : / dev / urandom))
  • 참조 (예를 들어, n 길이의 예측할 수없는 임의 문자열 (예 : / dev / urandom))
  • 서명 (HMAC 방법을 사용하여 키 해시 값 생성)

간단한 접근

간단한 해결책은 다음과 같습니다.

  • 사용자가 Remember Me로 로그온했습니다
  • 토큰 및 서명으로 발행 된 로그인 쿠키
  • 돌아올 때 서명이 확인됩니다
  • 서명이 정상이면 데이터베이스에서 사용자 이름 및 토큰을 찾습니다.
  • 유효하지 않은 경우 .. 로그인 페이지로 돌아 가기
  • 유효한 자동 로그인

위의 사례 연구는이 페이지에 제공된 모든 예제를 요약하지만 단점은

  • 쿠키를 도난당한 경우 알 수있는 방법이 없습니다
  • 공격자는 암호 변경 또는 개인 정보 및 베이킹 정보 등과 같은 데이터와 같은 민감한 작업에 액세스 할 수 있습니다.
  • 손상된 쿠키는 여전히 쿠키 수명 동안 유효합니다.

더 나은 솔루션

더 나은 해결책은

  • 사용자가 로그인했으며 내가 선택되었음을 기억하십시오
  • 토큰 생성 및 서명 및 쿠키 저장
  • 토큰은 무작위이며 단일 인증에만 유효합니다.
  • 사이트를 방문 할 때마다 토큰이 교체됩니다.
  • 로그인하지 않은 사용자가 사이트를 방문하면 서명, 토큰 및 사용자 이름이 확인됩니다.
  • 로그인은 액세스가 제한되어 있어야하며 비밀번호, 개인 정보 등을 수정해서는 안됩니다.

예제 코드

// Set privateKey
// This should be saved securely 
$key = 'fc4d57ed55a78de1a7b31e711866ef5a2848442349f52cd470008f6d30d47282';
$key = pack("H*", $key); // They key is used in binary form

// Am Using Memecahe as Sample Database
$db = new Memcache();
$db->addserver("127.0.0.1");

try {
    // Start Remember Me
    $rememberMe = new RememberMe($key);
    $rememberMe->setDB($db); // set example database

    // Check if remember me is present
    if ($data = $rememberMe->auth()) {
        printf("Returning User %s\n", $data['user']);

        // Limit Acces Level
        // Disable Change of password and private information etc

    } else {
        // Sample user
        $user = "baba";

        // Do normal login
        $rememberMe->remember($user);
        printf("New Account %s\n", $user);
    }
} catch (Exception $e) {
    printf("#Error  %s\n", $e->getMessage());
}

사용 된 클래스

class RememberMe {
    private $key = null;
    private $db;

    function __construct($privatekey) {
        $this->key = $privatekey;
    }

    public function setDB($db) {
        $this->db = $db;
    }

    public function auth() {

        // Check if remeber me cookie is present
        if (! isset($_COOKIE["auto"]) || empty($_COOKIE["auto"])) {
            return false;
        }

        // Decode cookie value
        if (! $cookie = @json_decode($_COOKIE["auto"], true)) {
            return false;
        }

        // Check all parameters
        if (! (isset($cookie['user']) || isset($cookie['token']) || isset($cookie['signature']))) {
            return false;
        }

        $var = $cookie['user'] . $cookie['token'];

        // Check Signature
        if (! $this->verify($var, $cookie['signature'])) {
            throw new Exception("Cokies has been tampared with");
        }

        // Check Database
        $info = $this->db->get($cookie['user']);
        if (! $info) {
            return false; // User must have deleted accout
        }

        // Check User Data
        if (! $info = json_decode($info, true)) {
            throw new Exception("User Data corrupted");
        }

        // Verify Token
        if ($info['token'] !== $cookie['token']) {
            throw new Exception("System Hijacked or User use another browser");
        }

        /**
         * Important
         * To make sure the cookie is always change
         * reset the Token information
         */

        $this->remember($info['user']);
        return $info;
    }

    public function remember($user) {
        $cookie = [
                "user" => $user,
                "token" => $this->getRand(64),
                "signature" => null
        ];
        $cookie['signature'] = $this->hash($cookie['user'] . $cookie['token']);
        $encoded = json_encode($cookie);

        // Add User to database
        $this->db->set($user, $encoded);

        /**
         * Set Cookies
         * In production enviroment Use
         * setcookie("auto", $encoded, time() + $expiration, "/~root/",
         * "example.com", 1, 1);
         */
        setcookie("auto", $encoded); // Sample
    }

    public function verify($data, $hash) {
        $rand = substr($hash, 0, 4);
        return $this->hash($data, $rand) === $hash;
    }

    private function hash($value, $rand = null) {
        $rand = $rand === null ? $this->getRand(4) : $rand;
        return $rand . bin2hex(hash_hmac('sha256', $value . $rand, $this->key, true));
    }

    private function getRand($length) {
        switch (true) {
            case function_exists("mcrypt_create_iv") :
                $r = mcrypt_create_iv($length, MCRYPT_DEV_URANDOM);
                break;
            case function_exists("openssl_random_pseudo_bytes") :
                $r = openssl_random_pseudo_bytes($length);
                break;
            case is_readable('/dev/urandom') : // deceze
                $r = file_get_contents('/dev/urandom', false, null, 0, $length);
                break;
            default :
                $i = 0;
                $r = "";
                while($i ++ < $length) {
                    $r .= chr(mt_rand(0, 255));
                }
                break;
        }
        return substr(bin2hex($r), 0, $length);
    }
}

Firefox 및 Chrome에서 테스트

여기에 이미지 설명을 입력하십시오

이점

  • 더 나은 보안
  • 공격자에 대한 제한된 액세스
  • 쿠키가 도난 당할 때 단일 액세스에만 유효합니다
  • 다음에 원래 사용자가 사이트에 액세스하면 도난을 자동으로 감지하여 사용자에게 알릴 수 있습니다

불리

  • 여러 브라우저 (모바일 및 웹)를 통한 지속적인 연결을 지원하지 않습니다
  • 사용자는 다음 로그인 후에 만 ​​알림을 받기 때문에 쿠키를 도난 당할 수 있습니다.

빠른 수정

  • 지속적인 연결이 필요한 각 시스템에 대한 승인 시스템 도입
  • 인증을 위해 여러 쿠키 사용

여러 쿠키 접근

공격자가 쿠키를 훔치려 고 할 때 쿠키는 특정 웹 사이트 나 도메인에만 집중합니다. example.com

그러나 실제로 두 개의 다른 도메인 ( example.com & fakeaddsite.com ) 에서 사용자를 인증하여 "광고 쿠키"처럼 보이게 할 수 있습니다

  • 사용자 나를 기억 하여 example.com 에 로그온
  • 쿠키에 사용자 이름, 토큰, 참조 저장
  • 데이터베이스에 사용자 이름, 토큰, 참조를 저장하십시오. Memcache
  • get 및 iframe을 통해 refrence ID를 fakeaddsite.com 으로 보냅니다.
  • fakeaddsite.com은이 참조를 사용하여 데이터베이스에서 사용자 및 토큰을 가져옵니다.
  • fakeaddsite.com은 서명을 저장합니다
  • 사용자가 fakeaddsite.com에서 iframe으로 가져 오기 서명 정보를 반환하는 경우
  • 데이터를 결합하고 유효성 검사를 수행하십시오.
  • ..... 당신은 나머지를 알고

어떤 사람들은 두 가지 다른 쿠키를 어떻게 사용할 수 있는지 궁금 할 것입니다. 가능은 잘 상상 example.com = localhost하고 fakeaddsite.com = 192.168.1.120. 쿠키를 검사하면 다음과 같습니다

여기에 이미지 설명을 입력하십시오

위 이미지에서

  • 현재 방문한 사이트는 localhost입니다
  • 192.168.1.120에서 설정된 쿠키도 포함합니다.

192.168.1.120

  • 정의 된 내용 만 수락 HTTP_REFERER
  • 지정된 연결 만 허용 REMOTE_ADDR
  • 자바 스크립트 없음, 콘텐츠 없음

이점

  • 공격자를 속인 시간의 99 %
  • 공격자가 처음 시도 할 때 계정을 쉽게 잠글 수 있습니다
  • 다른 방법과 마찬가지로 다음 로그인 전에 공격을 방지 할 수 있습니다

불리

  • 단일 로그인만으로 서버에 여러 요청

개량

  • iframe 사용 완료 ajax

5
@ircmaxell이 이론을 잘 설명했지만이 접근법은 사용자 ID (원치 않는 공개)를 저장하지 않고도 훌륭하게 작동하며 사용자 ID 및 해시보다 더 많은 지문을 포함하므로 브라우저와 같은 사용자. 이로 인해 공격자가 도난당한 쿠키를 사용하기가 더욱 어려워집니다. 내가 지금까지 본 가장 안전하고 안전한 방법입니다. +1
Marcello Mönkemeyer

6

이 질문의 하나의 각도를 요청 여기 , 그리고 대답은 모두 당신이 필요로하는 토큰 기반 타이밍 쿠키 링크를 당신을 이끌 것입니다.

기본적으로 쿠키에는 userId를 저장하지 않습니다. 사용자가 이전 로그인 세션을 픽업하는 데 사용하는 일회성 토큰 (거대한 문자열)을 저장합니다. 그런 다음 실제로 보안을 유지하려면 암호 자체 변경과 같이 많은 작업에 필요한 암호를 요청하십시오.


6

오래된 스레드이지만 여전히 유효한 관심사입니다. 보안에 대해 좋은 반응을 보였고 '불분명을 통한 보안'사용을 피했지만 실제 기술 방법으로는 충분하지 않았습니다. 내 방법에 기여하기 전에 말해야 할 사항 :

  • 비밀번호를 일반 텍스트로 저장하지 마십시오 .
  • 사용자의 해시 비밀번호를 데이터베이스의 둘 이상의 위치에 저장하지 . 서버 백엔드는 항상 users 테이블에서 해시 된 비밀번호를 가져올 수 있습니다. 추가 DB 트랜잭션 대신 중복 데이터를 저장하는 것이 더 효율적이지 않습니다. 그 반대의 경우도 마찬가지입니다.
  • 두 개의 사용자 수 있도록 세션 ID의는 고유해야 지금 , 아이디의 따라서 목적 (운전 면허증 ID 번호 이제까지 다른 사람과 일치 수 있을까? 호)이 2에 따라, 두 조각 고유 한 조합을 생성을 ID를 공유하지 고유 한 문자열. 세션 테이블은 ID를 PK로 사용해야합니다. 자동 로그인을 위해 여러 장치를 신뢰할 수있게하려면 모든 검증 된 장치 목록이 포함 된 신뢰할 수있는 장치에 대해 다른 테이블을 사용하십시오 (아래 예 참조).
  • 알려진 데이터를 쿠키에 해시하려는 목적이 없으며 쿠키를 복사 할 수 있습니다. 우리가 찾고있는 것은 공격자가 사용자의 컴퓨터를 손상시키지 않고 얻을 수없는 확실한 정보를 제공하는 준수하는 사용자 장치입니다 (다시, 내 예 참조). 그러나 이는 자신의 컴퓨터의 정적 정보 (예 : MAC 주소, 장치 호스트 이름, 브라우저에 의해 제한되는 경우 useragent 등)를 일관되게 유지하지 못하도록 (또는 우선 스푸핑) 합법적 인 사용자가 할 수 없음을 의미합니다. 이 기능을 사용하십시오. 그러나 이것이 우려되는 경우 사용자에게 자동 로그인을 제공한다는 사실을 고려하십시오. 자신을 고유하게 식별하는따라서 MAC 스푸핑, 사용자 에이전트 스푸핑, 호스트 이름 스푸핑 / 변경, 프록시 뒤에 숨어 등으로 알려지기를 거부하는 경우 식별 할 수 없으며 자동 서비스에 대해 인증되지 않아야합니다. 원하는 경우 사용중인 장치의 ID를 설정하는 클라이언트 측 소프트웨어와 함께 제공되는 스마트 카드 액세스를 조사해야합니다.

모두 말하지만, 시스템에 자동 로그인하는 두 가지 좋은 방법이 있습니다.

첫째, 저렴하고 쉬운 방법으로 다른 모든 사람에게 적용 할 수 있습니다. 예를 들어 Google+ 계정으로 로그인하여 사이트를 지원하는 경우 사용자가 이미 Google에 로그인 한 경우 사용자를 로그인하는 간소화 된 Google+ 버튼이있을 것입니다. Google에 로그인). 사용자가 이미 신뢰할 수 있고 지원되는 인증 자로 로그인 한 상태에서 자동으로 로그인하고 확인란을 선택한 경우 클라이언트 측 스크립트가로드하기 전에 해당 '로그인 대상'버튼 뒤에서 코드를 수행하도록하십시오 사용자 이름, 세션 ID 및 인증자가 사용 된 자동 로그인 테이블에 서버가 고유 ID를 저장하도록하십시오. 이 로그인 방법은 AJAX를 사용하므로 어쨌든 응답을 기다리고 있습니다. 그 응답은 검증 된 응답 또는 거부입니다. 검증 된 응답을 받으면 정상적으로 사용하고 로그인 한 사용자를 정상적으로로드하십시오. 그렇지 않으면 로그인에 실패했지만 사용자에게 알리지 않고 로그인하지 않은 채 계속 진행하면 알 수 있습니다. 이는 쿠키를 훔친 (또는 권한 상승 시도로 쿠키를 위조 한) 공격자가 사용자가 사이트에 자동 로그인한다는 사실을 배우지 못하게하기위한 것입니다.

저렴하고 일부 사용자에게는 말하지 않아도 이미 Google 및 Facebook과 같은 장소에서 이미 로그인 한 상태를 확인하려고하기 때문에 더러워 질 수 있습니다. 그러나 사이트 자동 로그인을 요청하지 않은 사용자에게는 사용해서는 안되며이 특정 방법은 Google+ 또는 FB와 같은 외부 인증에만 사용됩니다.

외부 인증자가 사용자의 유효성을 검사했는지 여부를 서버 뒤에 알려주기 때문에 공격자는 고유 한 ID 이외의 다른 정보를 얻을 수 없습니다. 내가 자세히 설명하겠습니다.

  • 사용자 'joe'가 처음으로 사이트를 방문하고 쿠키 'session'에 세션 ID가 배치되었습니다.
  • 사용자 'joe'로그인하여 권한을 에스컬레이션하고 새 세션 ID를 가져오고 쿠키 '세션'을 갱신합니다.
  • 사용자 'joe'는 Google+를 사용하여 자동 로그인하도록 선택하고 쿠키 'keepmesignedin'에 고유 ID를 가져옵니다.
  • 사용자 'joe'는 Google에 로그인 상태를 유지하므로 백엔드에서 Google을 사용하여 사이트에서 사용자를 자동 로그인 할 수 있습니다.
  • 공격자는 체계적으로 'keepmesignedin'에 대한 고유 ID를 시도하며 (모든 사용자에게 공개 된 지식) 다른 곳에서는 로그인하지 않습니다. 'joe'에게 부여 된 고유 ID를 시도합니다.
  • 서버는 'joe'에 대한 고유 ID를 받고 Google+ 계정에 대해 DB에서 일치 항목을 가져옵니다.
  • 서버는 AJAX 요청을 실행하는 로그인 페이지로 공격자를 Google에 로그인하여 보냅니다.
  • Google 서버는 요청을 받고 API를 사용하여 공격자가 현재 로그인하지 않은 것을 확인합니다.
  • Google은이 연결을 통해 현재 로그인 한 사용자가 없다는 응답을 보냅니다.
  • 공격자의 페이지가 응답을 받으면 스크립트가 URL에 인코딩 된 POST 값을 사용하여 로그인 페이지로 자동 리디렉션합니다.
  • 로그인 페이지는 POST 값을 가져 와서 'keepmesignedin'쿠키를 빈 값으로 전송하고 날짜 1-1-1970까지 유효하여 자동 시도를 막아 공격자의 브라우저가 쿠키를 간단히 삭제하도록합니다.
  • 공격자에게는 일반적인 최초 로그인 페이지가 제공됩니다.

공격자가 존재하지 않는 ID를 사용하더라도 검증 된 응답을받는 경우를 제외하고 모든 시도에서 시도가 실패합니다.

이 방법은 외부 인증자를 사용하여 사이트에 로그인하는 사람들을 위해 내부 인증 자와 함께 사용할 수 있으며 사용해야합니다.

=========

사용자를 자동 로그인 할 수있는 인증 시스템의 경우 다음과 같습니다.

DB에는 몇 가지 테이블이 있습니다.

TABLE users:
UID - auto increment, PK
username - varchar(255), unique, indexed, NOT NULL
password_hash - varchar(255), NOT NULL
...

사용자 이름의 길이는 255 자입니다. 내 서버 프로그램에서 시스템의 사용자 이름을 32 자로 제한하지만 외부 인증자는 @ domain.tld의 사용자 이름이 그보다 클 수 있으므로 최대 호환성을 위해 전자 메일 주소의 최대 길이 만 지원합니다.

TABLE sessions:
session_id - varchar(?), PK
session_token - varchar(?), NOT NULL
session_data - MediumText, NOT NULL

로그인 할 때 사용자 이름이 세션 데이터에 있고 프로그램이 널 데이터를 허용하지 않기 때문에이 테이블에는 사용자 필드가 없습니다. session_id 및 session_token은 임의의 md5 해시, sha1 / 128 / 256 해시, 임의의 문자열이 추가 된 날짜 시간 스탬프 또는 해시 또는 원하는 것을 사용하여 생성 할 수 있지만 출력의 엔트로피는 허용 가능한 한 높게 유지되어야합니다 무차별 대입 공격을 완전히 차단하여 세션 클래스에서 생성 된 모든 해시를 추가하기 전에 세션 테이블에서 일치하는지 확인해야합니다.

TABLE autologin:
UID - auto increment, PK
username - varchar(255), NOT NULL, allow duplicates
hostname - varchar(255), NOT NULL, allow duplicates
mac_address - char(23), NOT NULL, unique
token - varchar(?), NOT NULL, allow duplicates
expires - datetime code

MAC 주소는 본질적으로 고유해야하므로 각 항목마다 고유 한 값이 있습니다. 반면에 호스트 이름은 별도의 네트워크에서 합법적으로 복제 될 수 있습니다. "Home-PC"를 컴퓨터 이름 중 하나로 사용하는 사람은 몇 명입니까? 사용자 이름은 서버 백엔드에 의해 세션 데이터에서 가져 오므로 조작 할 수 없습니다. 토큰의 경우, 페이지의 세션 토큰을 생성하는 동일한 방법을 사용하여 사용자 자동 로그인을위한 쿠키로 토큰을 생성해야합니다. 마지막으로 날짜 시간 코드는 사용자가 자격 증명을 다시 확인해야 할 때 추가됩니다. 며칠 내에 사용자 로그인을 유지하면서이 날짜 / 시간을 업데이트하거나, 마지막 로그인에 관계없이 한 달 정도만 유지하도록 (디자인에 따라) 만료되도록 강제하십시오.

이를 통해 누군가 자동 로그인 한 사용자의 MAC 및 호스트 이름을 체계적으로 스푸핑하지 못하게됩니다. NEVER사용자가 비밀번호, 일반 텍스트 또는 기타 방식으로 쿠키를 유지하도록합니다. 세션 토큰과 마찬가지로 각 페이지 탐색에서 토큰을 재생성하십시오. 이는 공격자가 유효한 토큰 쿠키를 얻어이를 사용하여 로그인 할 가능성을 크게 줄입니다. 어떤 사람들은 공격자가 피해자로부터 쿠키를 훔치고 로그인을 위해 세션 재생 공격을 할 수 있다고 말합니다. 공격자가 쿠키를 훔칠 수 있다면 (가능한 경우), 전체 장치를 손상했을 수 있습니다. 즉, 장치를 사용하여 로그인 만하면 쿠키를 완전히 훔치려는 목적을 상실 할 수 있습니다. 사이트가 HTTPS (비밀번호, CC 번호 또는 기타 로그인 시스템을 처리 할 때)를 통해 실행되는 경우 브라우저 내에서 가능한 모든 사용자를 보호 할 수 있습니다.

명심해야 할 사항 : 자동 로그인을 사용하는 경우 세션 데이터가 만료되지 않아야합니다. 세션을 거짓으로 계속하는 기능을 만료 할 수 있지만 시스템간에 유효성 검사를 수행하면 세션간에 계속 지속될 것으로 예상되는 영구 데이터 인 경우 세션 데이터를 다시 시작해야합니다. 영구 및 비 영구 세션 데이터를 모두 원한다면 사용자 이름을 PK로 사용하여 영구 세션 데이터에 다른 테이블을 사용하고 서버에서 일반 세션 데이터처럼 검색하도록 다른 변수를 사용하십시오.

이러한 방식으로 로그인이 이루어지면 서버는 여전히 세션의 유효성을 검사해야합니다. 여기에서 도난 당하거나 손상된 시스템에 대한 기대치를 코딩 할 수 있습니다. 세션 데이터 로그인 패턴 및 기타 예상 결과는 종종 액세스하기 위해 시스템이 하이재킹되었거나 쿠키가 위조되었다는 결론을 초래할 수 있습니다. 여기에서 ISS Tech는 자동 로그인 시스템에서 사용자의 계정 잠금 또는 자동 제거를 트리거하는 규칙을 설정하여 공격자가 사용자의 공격 방식 및 차단 방법을 결정할 수있을 정도로 오랫동안 사용자를 차단합니다.

마지막으로, 임계 값을 초과 한 복구 시도, 비밀번호 변경 또는 로그인 실패로 인해 사용자가 올바르게 유효성 검증하고이를 확인한 후에 자동 로그인이 사용 불가능하게됩니다.

내 대답에 코드가 제공 될 것으로 예상되는 사람에게는 사과드립니다. 여기서는 그런 일이 일어나지 않을 것입니다. 필자는 PHP, jQuery 및 AJAX를 사용하여 사이트를 운영하고 Windows를 서버로 사용하지 않는다고 말할 것입니다.



4

알고있는 비밀만으로 해시를 생성 한 다음 사용자와 연결할 수 있도록 DB에 저장하십시오. 잘 작동합니다.


이것은 사용자가 생성 될 때 생성되는 고유 식별자입니까, 아니면 사용자가 새로운 "로그인 유지"쿠키를 생성 할 때마다 변경됩니까?
Matthew

1
이 암호를 포함하지 않은 경우 팀 얀손의 대답은 내가 안전하게 느낄 것하지만 해시를 생산하는 좋은 방법을 설명합니다
자니 하티 카이넨에게

2

내 솔루션은 다음과 같습니다. 100 % 방탄은 아니지만 대부분의 경우 비용을 절약 할 수 있다고 생각합니다.

사용자가 성공적으로 로그인하면 다음 정보로 문자열을 작성하십시오.

$data = (SALT + ":" + hash(User Agent) + ":" + username 
                     + ":" + LoginTimestamp + ":"+ SALT)

암호화 $data, 유형을 HttpOnly로 설정 로 설정하고 쿠키를 설정하십시오.

사용자가 사이트를 다시 방문하면 다음 단계를 수행하십시오.

  1. 쿠키 데이터를 얻습니다. 쿠키 내부의 위험한 문자를 제거하십시오. 그것을 분해:캐릭터 .
  2. 유효성을 확인하십시오. 쿠키가 X 일보다 오래된 경우 사용자를 로그인 페이지로 리디렉션하십시오.
  3. 쿠키가 오래되지 않은 경우; 데이터베이스에서 최신 비밀번호 변경 시간을 가져옵니다. 사용자의 마지막 로그인 후 비밀번호가 변경된 경우 사용자를 로그인 페이지로 리디렉션하십시오.
  4. 패스가 최근에 변경되지 않은 경우; 사용자의 현재 브라우저 에이전트를 가져옵니다. (currentUserAgentHash == cookieUserAgentHash) 여부를 확인하십시오. 에이전트가 동일하면 다음 단계로 이동하고, 그렇지 않으면 로그인 페이지로 경로 재 지정하십시오.
  5. 모든 단계를 통과하면 사용자 이름을 인증합니다.

사용자가 로그 아웃하면이 쿠키를 제거하십시오. 사용자가 다시 로그인하면 새 쿠키를 만듭니다.


2

쿠키를 암호화 한 버전 인 경우 해킹을 수행해야하는 쿠키에 암호화 된 항목을 저장하는 개념을 이해하지 못합니다. 누락 된 부분이 있으면 의견을 말하십시오.

나는 'Remember Me'에이 접근법을 취하는 것에 대해 생각하고 있습니다. 문제가 있으면 의견을 말하십시오.

  1. 여러 장치에서 로그인 할 수 있도록 사용자 테이블과 별도로 "Remember Me"데이터를 저장할 테이블을 만듭니다.

  2. 로그인에 성공하면 (나를 기억함) :

    a)이 시스템에서 사용자 ID로 사용할 고유 한 임의 문자열을 생성하십시오. bigUserID

    b) 고유 한 임의 문자열 생성 : bigKey

    c) 쿠키 저장 : bigUserID : bigKey

    d) "Remember Me"테이블에서 UserID, IP Address, bigUserID, bigKey를 사용하여 레코드를 추가하십시오.

  3. 로그인이 필요한 항목에 액세스하려는 경우 :

    a) 쿠키를 확인하고 일치하는 IP 주소로 bigUserID 및 bigKey를 검색하십시오.

    b) 찾을 경우 사람을 로그인하고 사용자 테이블 "소프트 로그인"에 플래그를 설정하여 위험한 작업에 대해 전체 로그인을 요구할 수 있습니다.

  4. 로그 아웃시 해당 사용자에 대한 모든 "내 정보 기억"레코드가 만료 된 것으로 표시하십시오.

내가 볼 수있는 유일한 취약점은 다음과 같습니다.

  • 쿠키를 사용하여 다른 사람의 랩톱을 잡고 IP 주소를 스푸핑 할 수 있습니다.
  • 매번 다른 IP 주소를 스푸핑하고 모든 것을 추측 할 수는 있지만 두 개의 큰 문자열을 사용하면 위와 비슷한 계산을 수행 할 수 있습니다 ... 나는 모른다 ... 거대한 확률?

안녕하세요,이 답변에 감사드립니다. 한 가지 질문 : 왜 2 개의 임의 문자열-bigUserID & bigKey를 생성해야합니까? 왜 1 만 생성하여 사용하지 않습니까?
Jeremy Belolo

2
bigKey는 사전 정의 된 시간이 지나면 만료되지만 bigUserID는 만료되지 않습니다. bigUserID는 동일한 IP 주소의 다른 장치에서 여러 세션을 가질 수 있도록합니다. 이해가 되길 희망합니다-잠시 생각해야했습니다 :)
Enigma Plus

hmac이 도움이 될 수있는 한 가지는 hmac이 변조 된 것을 발견하면 누군가가 쿠키를 훔치려 고 시도했다는 것을 확실히 알 수 있으며 모든 로그인 상태를 재설정 할 수 있다는 것입니다. 내가 맞아?
Suraj Jain

2

나는 모든 대답을 읽었지만 여전히 내가해야 할 것을 추출하는 것이 어렵다는 것을 알았습니다. 1k 단어의 가치가있는 사진이라면 Barry Jaspan의 향상된 영구 로그인 쿠키 모범 사례를 기반으로 안전한 영구 스토리지를 구현하는 데 도움이되기를 바랍니다.

여기에 이미지 설명을 입력하십시오

질문, 의견 또는 제안 사항이있는 경우 보안 영구 로그인을 구현하려는 초보자를 반영하도록 다이어그램을 업데이트하려고합니다.


0

"로그인 유지"기능을 구현하면 사용자에게 어떤 의미가 있는지 정확하게 정의해야합니다. 가장 간단한 경우에는 세션 시간이 2 시간이 아닌 2 일 (예 : 2 시간)보다 훨씬 길다는 것을 의미합니다. 이를 위해서는 데이터베이스에 자체 세션 스토리지가 필요하므로 세션 데이터에 대한 사용자 정의 만료 시간을 설정할 수 있습니다. 그런 다음 브라우저를 닫을 때 만료되는 것이 아니라 며칠 이상 지속되는 쿠키를 설정해야합니다.

"왜 2 일? 왜 2 주?"라고 묻는 소리가 들립니다. PHP에서 세션을 사용하면 자동으로 만료가 다시 발생하기 때문입니다. PHP에서 세션의 만료가 실제로 유휴 시간 초과이기 때문입니다.

이제 말했듯이 세션 자체에 저장하고 2 주 정도 정도 시간을 초과하여 더 엄격한 시간 초과 값을 구현하고 코드를 추가하여 세션을 강제로 무효화합니다. 또는 적어도 로그 아웃하십시오. 이는 사용자에게 주기적으로 로그인하라는 메시지가 표시됩니다. 야후! 이것을한다.


1
더 긴 세션을 설정하면 서버 리소스가 낭비되고 성능에 부정적인 영향을 미치므로 좋지
않을 수 있습니다.

0

나는 당신이 이것을 할 수 있다고 생각합니다 :

$cookieString = password_hash($username, PASSWORD_DEFAULT);

저장 $cookiestringDB에 하고 쿠키로 설정하십시오. 또한 사람의 사용자 이름을 쿠키로 설정하십시오. 해시의 요점은 리버스 엔지니어링 할 수 없다는 것입니다.

사용자가 나타나면 $cookieString다른 쿠키보다 쿠키에서 사용자 이름을 가져옵니다 . 경우 $cookieStringDB에 저장된 것과 일치하고 사용자가 인증됩니다. password_hash는 매번 다른 소금을 사용하므로 일반 텍스트와는 무관합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.