안전한 코딩 – 비밀번호, 보안 토큰 비교 방법

코딩시 비밀번호나 보안 토큰 등 중요한 값을 비교할 때, 사소한 코딩 습관이 보안 취약점을 예방할 수 있습니다.
바로 ‘일치 연산자(===)’와 ‘동등 연산자(==)’의 차이점을 알고, 가능한 ‘일치 연산자(===)’만 사용해야 합니다.

– 비밀번호 비교의 2가지 예제
– 잘못된 비교 방법의 문제
– 결론 – 안전한 코딩

비밀번호 비교의 2가지 예제

  • 올바른 예
if ($_POST['password'] !== $password_hash) {
  die('비밀번호가 일치하지 않습니다.');
}
echo '로그인 성공';
  • 잘못된 예
if ($_POST['password'] != $password_hash) {
  die('비밀번호가 일치하지 않습니다.');
}
echo '로그인 성공';

위 2가지 예제는 각각 ‘일치 연산자(!==)’와 ‘동등 연산자(!=)’ 를 사용한 차이가 있습니다.
잘못된 예의 경우 일부 상황에서 원하지 않는 결과로 인해 보안 취약점이 발생할 수 있습니다.

잘못된 비교 방법의 문제

  • 취약점 사례
$_POST['password'] = md5('240610708');
$password_hash = md5('314282422');

if ($_POST['password'] != $password_hash) {
    die('비밀번호가 일치하지 않습니다.');
}

echo '로그인 성공';

실행 결과는 ‘로그인 성공‘입니다. 분명히 사용자가 입력한 비밀번호는 240610708이고, DB에 저장된 비밀번호는 314282422 로 서로 다르지만 로그인이 되는 보안 문제가 발생했습니다. 물론 2개 문자열은 md5 해시 값이 서로 다르며, sha1 해시로 바꿔도 특정한 조건의 문자열은 비밀번호를 몰라도 로그인이 가능한 상황이 발생하게 됩니다.

위 취약점은 단순히 비교 연산자만 ‘동등 연산자(!=)’를 ‘일치 연산자(!==)로 바꿔주면 해결됩니다.

  • 잘못 비교되는 사례
    서버의 쉘에서 PHP 를 CLI 모드로 실행해보시면, 서로 다른 문자열이지만 모두 결과값은 “같다(true)”로 잘못 나옵니다.
php -r 'var_dump(md5("314282422") == md5("240610708"));'
bool(true)
php -r 'var_dump("0e123456789" == 0);'    
bool(true)
php -r 'var_dump("0e123456789" == "0e000000000");'   
bool(true)
php -r 'var_dump("61529519452809720693702583126814" == "61529519452809720000000000000000");'   # PHP 5.3 이하에서만 재현됨
bool(true)

이런 문제도 단순히 비교 연산자만 ‘동등 연산자(==)’를 ‘일치 연산자(===)로 바꿔주면 해결되어, 결과값이 “다르다(false)”로 정상적으로 나옵니다.

php -r 'var_dump(md5("314282422") === md5("240610708"));'
bool(false)
php -r 'var_dump("0e123456789" === 0);'    
bool(false)
php -r 'var_dump("0e123456789" === "0e000000000");'   
bool(false)
php -r 'var_dump("61529519452809720693702583126814" === "61529519452809720000000000000000");'   # PHP 5.3 이하에서만 재현됨
bool(false)

결론 – 안전한 코딩

  • 새로 코딩하는 루틴은 반드시 ‘일치 연산자(=== or !==)’만 사용하는 것이 좋습니다. JavaScript 에서도 마찬가지입니다.

  • 단, 이미 만들어진 프로그램도 ‘비밀번호 나 보안 토큰 비교 루틴’만큼은 ‘일치 연산자’나 그에 준하는 루틴(password_verify() 등)으로 변경하셔야 합니다.

참고1 – 동등 연산자를 일치 연산자로 변환

다음 조건은 비교할 대상의 자료형이 서로 다릅니다.

php -r "var_dump(1234 == '1234');"
bool(true)

이런 구조에서 단순히 일치 연산자로 바꿔버리면 다음과 같은 의도하지 않은 결과를 얻게 됩니다.

php -r "var_dump(1234 === '1234');"
bool(false)

이런 경우는 비교할 값의 자료형을 서로 맞추어 주면 됩니다.

php -r "var_dump((string)1234 === '1234');"
bool(true)

php -r "var_dump(1234 === (int)'1234');"
bool(true)

참고2 – 보다 향상된 비밀번호 비교 방법

  • PHP 5.5 이상에서는 기본 지원되는 password_hash(), password_verify() 함수 사용을 권장합니다.

http://php.net/manual/kr/function.password-hash.php
http://php.net/manual/kr/function.password-verify.php

  • PHP 4.4~ 5.5 까지 호환되는 비밀번호 해시 방법

PHPSCHOOL 곰탱이푸님 글 – http://phpschool.com/gnuboard4/bbs/board.php?bo_table=tipntech&wr_id=78825&sca=&sfl=mb_id%7C%7Csubject&stx=kijin

참고3 – in_array(), array_search()의 기본 비교 방법은 동등 연산!

  • ‘일치 연산자’로 비교하려면, 세번째 인자값인 $strict 를 true 로 선언해야 합니다.

완이님 글 – http://blog.wani.kr/posts/2016/03/21/equal-operator-what-is-the-problem/

 


관련자료