PHP를 사용하여 CSRF (Cross-Site Request Forgery) 토큰을 올바르게 추가하는 방법


96

내 웹 사이트의 양식에 보안을 추가하려고합니다. 양식 중 하나는 AJAX를 사용하고 다른 하나는 간단한 "문의하기"양식입니다. CSRF 토큰을 추가하려고합니다. 내가 가지고있는 문제는 토큰이 HTML "값"에 가끔 표시된다는 것입니다. 나머지 시간에는 값이 비어 있습니다. 다음은 AJAX 양식에서 사용중인 코드입니다.

PHP :

if (!isset($_SESSION)) {
    session_start();
$_SESSION['formStarted'] = true;
}
if (!isset($_SESSION['token']))
{$token = md5(uniqid(rand(), TRUE));
$_SESSION['token'] = $token;

}

HTML

 <form>
//...
<input type="hidden" name="token" value="<?php echo $token; ?>" />
//...
</form>

어떤 제안?


궁금한 점은 무엇 token_time입니까?
zerkms 2011 년

@zerkms 나는 현재 사용하고 있지 않습니다 token_time. 토큰이 유효한 시간을 제한하려고했지만 아직 코드를 완전히 구현하지 않았습니다. 명확성을 위해 위의 질문에서 제거했습니다.

1
@Ken : 사용자가 양식을 열었을 때 케이스를 가져 와서 게시하고 잘못된 토큰을 얻을 수 있습니까? (무효화 이후)
zerkms 2011 년

@zerkms : 감사합니다.하지만 조금 혼란 스럽습니다. 예를 들어 줄 수 있나요?

2
@Ken : 물론입니다. 토큰이 오전 10시에 만료된다고 가정 해 보겠습니다. 이제 오전 9시 59 분입니다. 사용자가 양식을 열고 토큰을 얻습니다 (아직 유효 함). 그런 다음 사용자는 2 분 동안 양식을 작성하고 보냅니다. 현재 오전 10시 1 분이면 토큰이 유효하지 않은 것으로 간주되어 사용자에게 양식 오류가 발생합니다.
zerkms 2011 년

답변:


286

보안 코드의 경우 다음과 같이 토큰을 생성하지 마십시오. $token = md5(uniqid(rand(), TRUE));

이것을 시도하십시오 :

CSRF 토큰 생성

PHP 7

session_start();
if (empty($_SESSION['token'])) {
    $_SESSION['token'] = bin2hex(random_bytes(32));
}
$token = $_SESSION['token'];

(!) 참고 : 하나의 내 고용주의 오픈 소스 프로젝트가 백 포트에 대한 이니셔티브 random_bytes()random_int()PHP 5 프로젝트에. MIT 라이센스가 있으며 Github 및 Composer에서 paragonie / random_compat 로 사용할 수 있습니다 .

PHP 5.3 이상 (또는 ext-mcrypt 사용)

session_start();
if (empty($_SESSION['token'])) {
    if (function_exists('mcrypt_create_iv')) {
        $_SESSION['token'] = bin2hex(mcrypt_create_iv(32, MCRYPT_DEV_URANDOM));
    } else {
        $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));
    }
}
$token = $_SESSION['token'];

CSRF 토큰 확인

단지 사용하지 마십시오 ==또는 ===사용 hash_equals()(PHP 5.6+ 만하지만과 이전 버전에서 사용할 해시의 compat 라이브러리).

if (!empty($_POST['token'])) {
    if (hash_equals($_SESSION['token'], $_POST['token'])) {
         // Proceed to process the form data
    } else {
         // Log this as a warning and keep an eye on these attempts
    }
}

양식 별 토큰으로 더 나아 가기

을 사용하여 특정 양식에서만 사용할 수 있도록 토큰을 추가로 제한 할 수 있습니다 hash_hmac(). HMAC는 더 약한 해시 함수 (예 : MD5)에서도 사용하기에 안전한 특정 키 해시 함수입니다. 그러나 대신 SHA-2 해시 함수 제품군을 사용하는 것이 좋습니다.

먼저 HMAC 키로 사용할 두 번째 토큰을 생성 한 다음 다음과 같은 논리를 사용하여 렌더링합니다.

<input type="hidden" name="token" value="<?php
    echo hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
?>" />

그런 다음 토큰을 확인할 때 합동 작업을 사용합니다.

$calc = hash_hmac('sha256', '/my_form.php', $_SESSION['second_token']);
if (hash_equals($calc, $_POST['token'])) {
    // Continue...
}

한 양식에 대해 생성 된 토큰은 알지 못하면 다른 컨텍스트에서 다시 사용할 수 없습니다 $_SESSION['second_token']. 페이지에 방금 놓은 토큰이 아닌 별도의 토큰을 HMAC 키로 사용하는 것이 중요합니다.

보너스 : Hybrid Approach + Twig Integration

Twig 템플릿 엔진 을 사용하는 사람은 누구나 Twig 환경에이 필터를 추가하여 단순화 된 이중 전략의 이점을 누릴 수 있습니다.

$twigEnv->addFunction(
    new \Twig_SimpleFunction(
        'form_token',
        function($lock_to = null) {
            if (empty($_SESSION['token'])) {
                $_SESSION['token'] = bin2hex(random_bytes(32));
            }
            if (empty($_SESSION['token2'])) {
                $_SESSION['token2'] = random_bytes(32);
            }
            if (empty($lock_to)) {
                return $_SESSION['token'];
            }
            return hash_hmac('sha256', $lock_to, $_SESSION['token2']);
        }
    )
);

이 Twig 함수를 사용하면 다음과 같이 범용 토큰을 모두 사용할 수 있습니다.

<input type="hidden" name="token" value="{{ form_token() }}" />

또는 잠긴 변형 :

<input type="hidden" name="token" value="{{ form_token('/my_form.php') }}" />

Twig는 템플릿 렌더링에만 관련됩니다. 여전히 토큰의 유효성을 올바르게 검증해야합니다. 제 생각에 Twig 전략은 최대의 보안 가능성을 유지하면서 더 큰 유연성과 단순성을 제공합니다.


일회용 CSRF 토큰

각 CSRF 토큰을 정확히 한 번만 사용할 수 있어야한다는 보안 요구 사항이있는 경우 가장 간단한 전략은 각 성공적인 유효성 검사 후에 토큰을 다시 생성합니다. 그러나 그렇게하면 한 번에 여러 탭을 탐색하는 사람들과 잘 섞이지 않는 모든 이전 토큰이 무효화됩니다.

Paragon Initiative Enterprises는 이러한 코너 케이스에 대해 Anti-CSRF 라이브러리 를 유지합니다 . 일회성 형식 별 토큰으로 만 작동합니다. 세션 데이터에 충분한 토큰이 저장되면 (기본 구성 : 65535) 가장 오래된 사용되지 않은 토큰이 먼저 순환됩니다.


좋지만 사용자가 양식을 제출 한 후 $ token을 변경하는 방법은 무엇입니까? 귀하의 경우 사용자 세션에 사용되는 하나의 토큰입니다.
Akam

1
github.com/paragonie/anti-csrf 가 어떻게 구현 되는지 자세히 살펴보십시오 . 토큰은 일회용이지만 여러 개를 저장합니다.
Scott Arciszewski

@ScottArciszewski 세션 ID에서 시크릿이있는 메시지 다이제스트를 생성 한 다음 수신 된 CSRF 토큰 다이제스트를 다시 이전 시크릿과 세션 ID를 해싱하는 것과 비교하려면 어떻게 생각하십니까? 무슨 말인지 이해 하시길 바랍니다.
MNR

1
CSRF 토큰 확인에 대한 질문이 있습니다. $ _POST [ 'token']이 비어 있다면이 포스트 요청이 토큰없이 보내 졌기 때문에 진행하지 말아야합니다.
Hiroki

1
HTML 형식에 반향 될 것이고 예측할 수 없어 공격자가 위조 할 수 없도록하기를 원하기 때문입니다. 공격자가이를 스푸핑 할 수 있기 때문에 단순히 "예이 양식은 합법적입니다"가 아니라 실제로 시도-응답 인증을 구현하고 있습니다.
Scott Arciszewski

24

보안 경고 : md5(uniqid(rand(), TRUE))난수를 생성하는 안전한 방법이 아닙니다. 자세한 내용과 암호화 보안 난수 생성기를 활용하는 솔루션 은 이 답변 을 참조하십시오 .

if에 다른 것이 필요한 것 같습니다.

if (!isset($_SESSION['token'])) {
    $token = md5(uniqid(rand(), TRUE));
    $_SESSION['token'] = $token;
    $_SESSION['token_time'] = time();
}
else
{
    $token = $_SESSION['token'];
}

11
참고 : md5(uniqid(rand(), TRUE));보안 컨텍스트를 신뢰하지 않습니다 .
Scott Arciszewski

2

변수 $token가 세션에있을 때 세션에서 검색되지 않습니다.

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