PHP Type Juggling 취약점

PHP Type Juggling 취약점

PHP 는 두 값의 동일함을 비교하기 위해 === 연산자와 == 연산자를 지원한다. 전자는 strict 비교, 후자는 loose 비교라고 부른다.

strict 비교는 피연산자의 타입과 값을 모두 비교하고, loose 비교는 값만 비교한다.

아래 이미지는 strict type 비교와 loose 비교의 실행 결과를 표로 보여준다,

Strict comparision

Loose comparision

위 표를 보면 strict 비교는 두 피연산자가 동일할 때만 true 를 반환하므로, 일반적으로 인식하는 비교 연산과 동일하지만 loose 비교의 결과는 그렇지 못하다. 이는 서로 다른 타입의 값을 비교하려 하면, 비교가 가능한 일반적인 타입으로 강제로 변환하기 때문이다.

가장 대표적인 예는 정수와 문자열을 비교하는 경우이다. 이 경우 문자열 시작 지점으로부터 정수 값을 추출해 비교하며, 정수로 시작하지 않으면 0으로 취급한다.

"0000" == 0 #true
"0e12" == 0 #true
"1abc" == 1 #true
"0abc" == 0 #true
"abc" == 0  #true. 정수가 없으므로 0으로 간주.
'1e0...000' == 1 #true. 1로 시작하므로 1로 간주.
'abc...000' == 0 #true. 정수로 시작하지 않으므로 0으로 간주.

위 내용을 이용하면 PHP 서버의 취약한 input 값 검증 과정을 우회할 수도 있다. 예를 들어 PHP 서버는 $_GET,$_POST,$_COOKIE 와 같은 변수를 통해 사용자 input 값을 저장한다. 이렇게 전달받은 데이터는 문자열로 저장되기 때문에 위 취약점을 이용할 수 없으나, json_decode(),unserialize() 와 같은 역직렬화 함수를 이용하면 입력값을 정수, boolean 타입으로 변경할 수 있다.

예를 들면 아래 php 코드는 json_decode() 함수를 통해 POST 입력 값을 처리하고 있다. 만약 입력 값의 username 값을 true 로 설정하면 strict 비교를 우회한 후, switch 문의 admin case 를 실행할 수 있을 것이다.

    $data = json_decode($_POST["username"]);

    $username = $data->username;

    if($username === "admin" ){
        exit("Not Allowed");
    }

    switch($username){
        case "admin":
            login_admin();
            break;
        default:
            login_guest();
    }