개인적으로 프로그래밍의 재미있는 요소는 연산자가 아닐까 싶습니다. 작은 연산자들이 모여 큰 흐름을 만들고, 우리같은 해커는 이 흐름을 틀어 예상치도 못한 행위를 이끌어내니 굉장히 재미있는 부분이죠.
PHP에는 아주 재미있는 취약점이 하나 있습니다. 간단한 버그일 수 있지만 이 결과가 엄청난 데이터를 불러올 수 있죠. 오늘은 바로 PHP의 비교 연산자에 대한 이야기를 할까 합니다.
그럼 시작하겠습니다. :)
Comparison Operator?
개발쪽. 아니 IT인이라면 대부분 아는 연산자입니다. 양쪽 값을 비교하는 연산자로 대표적으로 Equal(==)이 있습니다.
예시로..
if(1==0)
{
// codecodecode
}
요런 구문에서 ==의 역할이죠.
관련해서는 아래 링크보시면 조금 더 도움될겁니다 http://php.net/manual/kr/language.operators.comparison.php
PHP Comparison Operator Vulnerability
아래 간단한 코드를 보겠습니다. param1 과 param2에 각각 다른 값의 md5 해쉬를 만들어 비교하는 구문입니다. 이 코드가 동작하면 어떤 결과를 나타낼까요?
<html><body style="background-color:black;color:white">
<h1>PHP Equal Vulnerability</h1>
<hr>
<?php
$param1 = md5('240610708'); // result: 0e462097431906509019562988736854
$param2 = md5('QNKCDZO'); // result: 0e830400451993494058024219903391
echo "param1 md5 value --> ".$param1."<br>";
echo "param2 md5 value --> ".$param2."<br>";
echo "<br><br>Result is ";
if($param1 == $param2)
{
echo "<font color='red'>Equal</font>";
}
else
{
echo "<font color='green'>Diff</font>";
}
?>
</body></html>
대부분 Diff가 나올 것이라고 생각하실 겁니다. 왜냐하면 두 값의 md5 해쉬는 다르기 때문이죠. 그러나 실제로 돌려보면…
위와 같이 Equal이 나옵니다(?!) 이제 뭔가 낌새를 느끼셨을 것 같습니다. 요약해서 먼저 말씀드리면 Equal(==) 자체에 있는 오류로 인해서 의도하지 않은 결과 값이 나타난 거죠.
Equal은 양쪽의 값을 비교하는 연산자입니다. 이 친구가 비교 연산을 할 때 문자던, 숫자던 다 숫자 형으로 변환한 후 비교를 하게 됩니다. 이 과정에서 맨 앞 숫자가 0e으로 시작되는 값(예시에서는 두 값의 해쉬값)은 숫자로 인식되어 0으로 나타내어집니다.
0e -> int!
원리는 간단합니다. 컴퓨터에서 숫자를 표현하는 방식 중 e를 이용한 방식이 있습니다. (엑셀보다보면 가끔 보이는…그런) 대표적으로
1E.~~ 0E~~~
이런식으로 숫자와 e를 혼용한 값인데, 이것은 문자열인 1E, 0E가 비교 시 숫자로 처리된다는 점입니다. 이 때문에 위 두 해시는 같은 값으로 인지되어 같다고 나온겁니다.
그래서 위 240610708과 QNKCDZO의 해쉬값은 Equal 연산에 의해 같다라고 판단하게 되는겁니다. 이러한 결과는 간단한 버그(?)이지만 시나리오에 따라 큰 결과를 가져올 수도 있습니다. 아래 시나리오와 함께 보죠.
Attack Scenario
그럼 실제로 어떤 시나리오에서 사용될 수 있는지 보도록 하겠습니다. 사실 제목부터 중간 이미지까지.. 시나리오에 대한 이야기가 워낙 많아 다들 눈치 채셨을겁니다. 아까 예제를 이용해서 간단한 로그인 페이지를 만들어보았습니다. php 코드에는 패스워드가 하드 코딩 되어있고 form 태그를 통해 입력 받은 값을 비교하여 로그인 여부를 판단하는 코드입니다.
<html><body style="background-color:black;color:white">
<h1>Sample Login page - by hahwul</h1>
<hr>
<br><br>
<form action="" method="get">
<input type="text" value="" name="pw">
<input type="submit" value="Login">
</form>
<br><br>
<h1>Result</h1>
<hr>
<?php
$user_pw = md5($_GET['pw']);
$password = md5('240610708'); // result: 0e462097431906509019562988736854
if($password == $user_pw)
{
echo "<font color='red'>Login success</font><br>";
echo "Your Input: ".$_GET['pw']."<br>";
echo "Real Passwd: 201610708<br>";
}
else
{
echo "<font color='green'>Fail..</font>";
}
?>
</body></html>
그럼 테스트삼아 아무거나 넣어봅시다.
물론.. Fail이 나오네요.(틀렸으니깐)
로그인 시 입력한 값과 DB(예제에서는 페이지에 넣어둔 값)에서 가져온 값의 해쉬를 비교하게 되고 if 문과 equal을 통해 로그인 여부를 결정짓게 됩니다. 아까 우리가 배운 기법을 활용하면.. 쉽게 넘어갈수도 있겠지요 :)
Password 입력 부분에 아까 0으로 시작하는 해쉬로 확인한 QNKCDZO를 입력하면 로그인이 통과되는걸 알 수 있습니다.
아주 간단하고 쉽지만.. 상황에 따라 굉장히 큰 영향을 끼칠 수 있습니다. QNKCDZO 같이 저런 해쉬값을 가지는 데이터는 꼭 BruteForce 과정에서는 필수로 담아야 하겠죠? 그럼 이만
Reference
http://php.net/manual/kr/language.operators.comparison.php http://stackoverflow.com/questions/22140204/why-md5240610708-is-equal-to-md5qnkcdzo http://hyunmini.tistory.com/76#comment12633578 http://stackoverflow.com/questions/8083034/integer-string-comparison-are-equal-php-bug http://stackoverflow.com/questions/6843030/why-does-php-consider-0-to-be-equal-to-a-string