PHP strcmp 인증 우회 취약점

PHP strcmp 인증 우회 취약점

이 글에서는 PHP Type Juggling 과 함께 자주 사용되는 strcmp() 함수의 문자열 검증을 우회하는 취약점에 대해 소개한다.

strcmp()

strcmp() 함수는 다른 언어에서 사용되는 함수와 마찬가지로, 두 문자열을 인자로 전달받아 동일한 문자열인지 검증하는 함수이다. 검증 결과 string1string2 보다 작으면 -1을 반환, 더 크면 1을 반환, 두 문자열이 동일하면 0을 반환한다.

출처 : https://www.php.net/manual/en/function.strcmp.php#refsect1-function.strcmp-returnvalues
Returns -1 if string1 is less than string2; 1 if string1 is greater than string2, and 0 if they are equal.

취약점

아래 php 코드는 strcmp() 함수를 이용해 $pw 에 전달받은 사용자 입력 값을 $admin_pw 값을 비교하는 예제 코드이다.

<?php 
	$admin_pw = "secret_password";
	$pw = "some_password";
	
	if (!strcmp($pw, $admin_pw)) {
		echo "Hello, admin!";
		return;
	}
?>

하지만PHP 5.3 이상, 8.0 미만의 버전에서는 strcmp() 에 전달되는 문자열에 Array() 타입의 데이터를 전달하면 항상 Null 을 반환하여 검증을 우회할 수 있다.

<?php 
	$admin_pw = "secret_password";
	//$pw = "some_password";
	$pw = array();
	
	if (!strcmp($pw, $admin_pw)) {
		echo "Hello, admin!";
		return;
	}
?>

위 코드를 약간 변경해서 strcmp() 결과 값이 0과 일치하는지 비교해도 동일하게 if 문을 실행하는 것을 알 수 있다. 이는 PHP 의 loose comparison 특성에 따라 Null 과 0은 == 로 비교 시 TRUE 를 반환하기 때문이다.

<?php 
	$admin_pw = "secret_password";
	//$pw = "some_password";
	$pw = array();
	
	if (strcmp($pw, $admin_pw)==0) {
		echo "Hello, admin!";
		return;
	}
?>
Loose Comparison 에서 NULL 과 0 을 연산한 결과

curl 로 POST method 패킷에 password 파라미터를 array 형태로 전달하는 예제는 다음과 같다.

curl http://strcmp.co.kr -X POST -d "password[]=qwer"

해결책

근본적인 해결 방법은 PHP 버전을 8.0 이상으로 서버를 운영하여 strcmp() 에 Array 가 인자로 전달되도 Null 을 반환하지 못하도록 하는 것이다.

또는 위에서 제시한 2번째 case처럼 strcmp() 결과를 0과 비교할 시, == 가 아닌 === 연산을 사용하여 strict comparison 하여 비교 결과가 FALSE 가 되도록 해야 한다.

Strict Comparison 에서 NULL 과 0 을 연산한 결과