코딩시 비밀번호나 보안 토큰 등 중요한 값을 비교할 때, 사소한 코딩 습관이 보안 취약점을 예방할 수 있습니다.
바로 ‘일치 연산자(===)’와 ‘동등 연산자(==)’의 차이점을 알고, 가능한 ‘일치 연산자(===)’만 사용해야 합니다.
– 비밀번호 비교의 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/
관련자료
-
Magic Hash 보안 취약점 발표 – https://blog.whitehatsec.com/magic-hashes/
-
그누보드 4/5 : Magic Hash 취약점 패치 – http://sir.co.kr/bbs/board.php?bo_table=g5_pds&wr_id=2881
-
XE 1.7/1.8 : Magic Hash 취약점 이슈 – https://github.com/xpressengine/xe-core/issues/1472
-
Laravel 4 CSRF 토큰 취약점 발표 – http://blog.laravel.com/csrf-vulnerability-in-laravel-4/