**Cross Site Request Forgery (CSRF)**는 임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점이다. CSRF 공격의 피해자는 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹 사이트에 요청하게 된다. CSRF 공격의 피해자는 의도치 않은 요청을 통해 임의 비밀번호 변경, 게시글 등록 및 삭제, 임의 메시지 전송 등 HTTP 요청으로 이루어지는 행위에 대해 인지하지 못한 채 피해를 당할 수 있다.
일반적으로 CSRF 취약점을 방어하기 위해 CSRF Token을 사용한다.
대표적으로 사용되는 CSRF 방어 기법은 CSRF Token 기법이다. CSRF Token은 같은 오리진에서만 접근 가능한 형태로 특정 Token을 저장해두고, HTTP 요청을 전송할 때 함께 전송한다. 웹 서버는 전송된 Token을 이용하여 제3자가 아닌 이용자로부터 요청이 왔다는 것을 인증할 수 있다.
CSRF Token 값은 보통 HTML form 태그의 hidden 속성에 입력되거나, 동적 요청에서도 사용될 수 있다.
아래 코드는 세션에 CSRF Token을 저장하고, HTTP 요청을 통해 전송된 CSRF Token과 세션에 저장된 Token이 같은지 확인하여 CSRF 공격을 탐지한다.
CSRF Token 방식은 캡챠나 암호화 방식과 달리 추가적인 사용자 상호작용이 불필요하다는 장점을 가지고 있다. 반에 XMLHttpRequest나 Fetch API 등을 통해 Authorization과 같은 이용자 인증 헤더를 설정하여 통신하는 것에 비해 여러 가지 보안 문제의 원인이 되고는 한다.
<?php
if (!isset($_SESSION["csrftoken"])) {
$csrftoken = bin2hex(random_bytes(32));
$_SESSION["csrftoken"] = $csrftoken;
} else {
$csrftoken = $_SESSION["csrftoken"];
}
$method = $_SERVER["HTTP_METHOD"];
if ($method !== "GET" && $method !== "HEAD") {
if (!isset($_POST["csrftoken"]) ||
!hash_equals($csrftoken, $_POST["csrftoken"]) {
header("HTTP/1.1 403 Forbidden");
die("CSRF detected");
}
echo "Input value: ";
echo htmlentities($_POST["query"], ENT_QUOTES|ENT_HTML5, 'utf-8');
}
?>
<form action="" method="POST">
<input name="csrftoken" type="hidden" value="<?=htmlentities($csrftoken, ENT_QUOTES|ENT_HTML5, 'utf-8'); ?>">
<input name="query" type="text" />
<input type="submit" />
</form>
짧은 CSRF Token
CSRF Token은 외부자가 예측할 수 없도록 설계해야 한다. 따라서 Token을 모르는 공격자가 무차별 대입 공격 (Brute-Force Attack)으로 Token을 획득할 수 없도록 Token의 길이가 충분히 길어야 한다.
예측 가능한 CSRF Token (PRNG 등)
Token의 길이가 충분하여도 공격자가 추측 가능한 데이터 (예: 현재 시간 등)를 기반으로 Token을 생성하거나, 암호학적으로 안전하지 않은 의사 난수 생성기 (Pseudorandom Number Generator, PRNG)를 사용해 Token을 생성해서는 안 된다. 이 경우 공격자는 Token을 추론하여 올바른 CSRF Token을 생성해낼 수 있게 된다. 이를 방어하기 위해 공격자가 예측할 수 없는 값을 이용해 Token을 생성하거나, 충분한 안전성이 보장된 난수 생성기 (Cryptographically-secure pseudorandom number generator, CSPRNG)를 사용하여야 예측 공격을 방지할 수 있다.