본문 바로가기

챌린지/Wargame.kr

bughela - md5_compare

wargame.kr의 10번째 문제인 md5_compare다.

값을 비교하기만 하면 된다고 한다. Start 버튼을 눌러 문제를 확인해본다.

단순히 값을 입력하는 입력폼 두 개가 주어진다. 아마 이 두 곳에 값을 입력하고 chk 버튼을 눌러서 비교하는 것 같은데 자세한 로직을 위해 소스를 확인해보자.

<?php
    if (isset($_GET['view-source'])) {
         show_source(__FILE__);
         exit();
    }

    if (isset($_GET['v1']) && isset($_GET['v2'])) {
        sleep(3); // anti brute force

        $chk = true;
        $v1 = $_GET['v1'];
        $v2 = $_GET['v2'];

        if (!ctype_alpha($v1)) {$chk = false;}
        if (!is_numeric($v2) ) {$chk = false;}
        if (md5($v1) != md5($v2)) {$chk = false;}

        if ($chk){
            include("../lib.php");
            echo "Congratulations! FLAG is : ".auth_code("md5_compare");
        } else {
            echo "Wrong...";
        }
    }
?>

첫번째 폼의 값이 v1, 두번째 폼의 값이 v2 파라미터로 전달된다. 길지 않은 로직이기 때문에 금방 이해할 수 있는데 먼저 GET으로 받은 각 파라미터 값을 변수에 저장하고 chk 변수를 true로 선언한다. 중간에 각 입력 파라미터에 대해 어떤 로직을 수행하여 $chk 변수를 false로 설정하는데 이 변수를 true로 유지하는게 플래그 획득 방법일 것이다. 그렇다면 v1, v2에 각각 수행되는 ctype_alpha()와 is_numeric() 함수는 무슨 함수일까? php 매뉴얼을 확인해보면 다음과 같다.

 

먼저 ctype_alpha()의 경우 매개변수로 주어진 변수가 오직 문자로만 이루어져 있는지 확인한다.

if(ctype_alpha("123")){
    echo "123 is alpha!\n";
}
if(ctype_alpha("asd")){
    echo "asd is alpha!\n";
} // "asd is alpha!"
if(ctype_alpha("한글")){
    echo "한글 is alpha!\n";
}

문자라곤 했지만 어쨌든 아스키 코드 내에 해당되는 알파벳 문자인지 확인하는 함수로 숫자가 포함되어 있거나 알파벳 문자가 아닌 경우 false를 반환한다. 즉 ctype_alpha($v1) 함수를 호출하는 것은 v1 파라미터 값이 알파벳으로만 이루어져있나 확인하는 것이며 그렇지 않다면 chk 변수를 false 값으로 설정하여 플래그를 출력시키지 않는다.

 

다음으로 is_numeric()의 경우 매개변수로 주어진 변수가 오직 숫자로만 이루어져 있는지 확인한다.

if(is_numeric("123")){
    echo "123 is numeric!\n";
} // "123 is numeric!"
if(is_numeric("asd")){
    echo "asd is numeric!\n";
}
if(is_numeric("asdf1234")){
    echo "asdf1234 is numeric!\n";
}

오직 숫자로만 이루어져 있는지 확인하기 때문에 다른 문자가 섞여있다면 false를 반환한다. 즉 is_numeric($v2) 함수를 호출하는 것은 v2 파라미터 값이 숫자로만 이루어져있나 확인하는 것이며 그렇지 않다면 chk 변수를 false 값으로 설정하여 플래그를 출력시키지 않는다.

 

결국 문제에서 요구하는 것은 문자로만 이루어진 v1, 숫자로만 이루어진 v2를 md5() 해싱하여 같은 해시값을 얻는 것을 원하고 있다. 하지만 이게 가능한걸까? 아무리 md5 해시가 충돌이 발견됐다고 해도 오직 문자, 오직 숫자 문자열의 충돌을 쉽사리 찾을 수는 없을 것 같은데 어떻게 찾을 수 있을까? 이는 php 매뉴얼 홈페이지에서 단서를 찾을 수 있었다.

 

PHP: md5 - Manual

I've found multiple sites suggesting the code:md5(file_get_contents($filename));Until recently, I hadn't noticed any issues with this locally... but then I tried to hash a 700MB file, with a 2048MB memory limit and kept getting out of memory errors...There

www.php.net

어떤 유저가 남긴 댓글에 의하면 240610708과 QNKCDZO은 해시값을 비교했을 때 true를 반환한다고 한다. 실제로 비교해보면 같다고 판단하는 것을 알 수 있는데 두 해시값을 각각 출력해보면 전혀 다른것을 볼 수 있다. 어떻게 된 일일까?

echo md5('QNKCDZO')."\n"; // 0e830400451993494058024219903391
echo md5('240610708')."\n"; // 0e462097431906509019562988736854
if(md5('QNKCDZO') == md5('240610708')){
    echo "How's this possible??"; // "How's this possible??"
}

그 이유는 php의 type juggling때문이었다. 이는 php에서 변수 선언 시 특정 자료형을 지정하지 않고 변수가 가지는 값에 따라 자료형이 결정되는 특성상 비교 연산 등에서 비교하기 쉬운 타입으로 변환하여 비교하기 위해 자동적으로 형변환이 발생할 수 있는데 이를 악용하여 공격하는 방법이다.

 

PHP Type Juggling Vulnerabilities

How PHP’s type comparison features lead to vulnerabilities, and how to avoid them

medium.com

다음과 같은 코드를 보자.

$var1 = '0';
$var2 = '0e123';

var_dump($var1); // string(1) "0"
var_dump($var2); // string(5) "0e123"

if($var1 == $var2){
    echo "What?"; // What?
}

분명히 서로 다른 문자열임에도 불구하고 비교하면 true로 판단되는 것을 볼 수 있다. 어째서일까? 이는 "0e123" 문자열의 "0e" 부분이 지수를 나타내는 과학적 표기법으로 읽히기 때문이다. 너무 긴 숫자를 공학용 계산기 등에서 표기할 때 1.23456789e-10처럼 표기하는 것을 본 적이 있을 것이다. php에서도 이를 인식하여 "0e123"을 0 * 10^123 값으로 변환하여 읽기 때문에 결국 정수 0이 되며 일반 문자열 "0"도 정수 0으로 변환되어 두 변수가 같은 값을 가지고 있다고 판단하는 것이다.

 

다시 문제로 돌아가서 240610708과 QNKCDZO의 해시값은 각각 0e830400451993494058024219903391, 0e462097431906509019562988736854을 가진다. 이는 0e 뒤에 얼마나 긴 값이 붙었든 결국 0에 곱하는 것이기 때문에 둘 다 0이 되므로 두 해시값은 비교 시 같다고 판단되어 chk 변수는 true를 유지, 플래그를 얻을 수 있다.

물론 이런 방법은 php에서도 '===' 연산자를 활용하여 엄격한 비교를 수행하면 통하지 않는다.

if (md5('240610708') === md5('QNKCDZO')) {
    echo "Can you pass this?";
}

'===' 연산자는 '=='와는 달리 자료형까지 동일한지 판단하여 비교하기 때문에 위처럼 type juggling으로 변환되지 않아 통과할 수 없다. 대신 '==' 연산자의 느슨한 비교처럼 유연하게 비교할 수 없으니 상황에 따라 사용하면 좋을 것이다. 이런 비교 연산자의 차이점은 매뉴얼에서 좀 더 확인할 수 있다.

 

PHP: PHP type comparison tables - Manual

Some function to write out your own comparisson table in tsv format. Can be easily modified to add more testcases and/or binary functions. It will test all comparables against each other with all functions. '==',        'ne' => '!=',        'gt' =>

www.php.net

 

 

이번 문제는 php의 type juggling, 엄격&느슨한 비교 등에 대해서 잘 알게 되는 계기가 되었다. 예전에 학교에서 웹 프로그래밍 수업을 들을때는 웹 서비스 자체를 구축하는 데 더 신경을 써서 그런지 아직도 php의 이런 특징에 대해서 모르는 부분이 많다. 나름대로 지식이 아예 없진 않다고 자부하고 있었는데... 아직은 좀 더 정진해야 할 것 같다.

'챌린지 > Wargame.kr' 카테고리의 다른 글

bughela - type confusion  (0) 2020.12.27
bughela - tmitter  (0) 2020.12.26
bughela - DB is really GOOD  (0) 2020.12.22
bughela - md5 password  (0) 2020.12.22
bughela - strcmp  (0) 2020.12.18