En ocasiones nos han preguntado de qué forma “limitar” a un sysadmin para que no pueda realizar alguna operación en concreto. Nuestra primera respuesta siempre es que si es sysadmin, debe poder realizar cualquier operación en la instancia de SQL Server y, si no debe poder hacer cualquier operación, no debería serlo. En muchas ocasiones se asignan permisos sysadmin a cuentas para evitar realizar una gestión de permisos. En muchos casos una cuenta relacionada con una aplicación se limita a utilizar una o varias bases de datos con lo que, en el peor de los casos, sería suficiente con que dicha cuenta fuese db_owner en cada una de estas bases de datos. En aquellos casos en los que no es posible reducir los permisos a una cuenta desde sysadmin, la alternativa más razonable es aislar en una instancia independiente al conjunto de bases de datos necesarias. No olvidemos que si damos permisos sysadmin a una cuenta de una aplicación de terceros podemos estar comprometiendo la seguridad y el cumplimiento de ciertas normativas de protección de datos sobre cualquier base de datos de la instancia.

En el caso que nada de lo anterior sea posible, la única alternativa que nos queda es “complicarle la vida” a ese usuario con permisos sysadmin. Ninguna medida que tomemos podrá ser 100% eficaz, eso debemos tenerlo claro desde el principio, pero puede que el simple hecho de complicar la vida a dicho usuario ya suponga un obstáculo suficiente. Por ejemplo, imaginemos una aplicación de reporting que tenga permisos sysadmin y por tanto pueda leer cualquier tabla de cualquier base de datos. Si necesitamos crear una tabla a la que no pueda acceder dicha aplicación podríamos optar por crear una tabla con un nombre “poco habitual”, lo cual puede que genere la imposibilidad de ser consultada por la aplicación. Por ejemplo, imaginemos que creamos la siguiente tabla:

create table [test table] (a int)

Tras crearla en cualquier base de datos, un usuario con sysadmin podría consultarla o incluso borrarla sin problemas haciendo un select o un drop:

SELECT * FROM [dbo].[test table]

GO

DROP TABLE [dbo].[test table]

Sin embargo, si renombramos dicha tabla incluyendo un caracter Unicode poco habitual, pero válido, podremos complicar un poco la situación:

declare @name nvarchar(max) = nchar(65535)

exec sp_rename ‘test table’,@name

Una vez renombrada, si intentamos realizar la consulta desde Management Studio nos encontraremos con errores:

SELECT * FROM [dbo].[?]

GO

DROP TABLE [dbo].[?]

Msg 1055, Level 16, State 1, Line 9

‘.’ is an invalid name because it contains a NULL character or an invalid unicode character.

Msg 1055, Level 16, State 1, Line 11

‘.’ is an invalid name because it contains a NULL character or an invalid unicode character.

Obviamente esto no impediría que el usuario pudiera hacer el renombrado a la inversa y volver a dejar la tabla con el nombre original para consultarla.  En otras ocasiones lo que queremos hacer es deshabilitar el acceso de ciertas aplicaciones a la base de datos. Esto habitualmente se implementa mediante un trigger de servidor capturando el evento de login. Si queremos por ejemplo evitar el acceso desde Office a nuestra instancia podríamos crear este trigger:

use master

go

CREATE TRIGGER [no_office_2013]

ON ALL SERVER

FOR LOGON

AS

BEGIN

IF APP_NAME()= ‘Microsoft Office 2013’

    ROLLBACK;

END;

Si intentamos conectar con Excel 2013 en este caso nos aparecería el siguiente error:

Lidiando con usuarios sysadmin

 

Desgraciadamente el trigger es bastante visible desde Management Studio, por lo que el usuario podría simplemente desactivarlo o borrarlo:

Lidiando con usuarios sysadmin Lidiando con usuarios sysadmin

 

 

Lo que vamos a hacer a continuación es complicarle la vida precisamente para realizar esta operación. Para ello primero analizaremos de qué forma se muestran los triggers de servidor y encontramos una vista llamada sys.server_triggers que nos muestra los triggers existentes. Para ello volveremos a utilizar técnicas de tampering que ya mostramos en este blog hace un tiempo. En concreto, hace más de 5 años, pero siguen siendo igual de vigentes hoy en día, tanto para SQL Server 2012 como para SQL Server 2014 ya que Microsoft no ha mejorado la seguridad en este sentido. Para no repetir aquí todos los pasos os dejo el enlace al post con la información para “abrir la puerta trasera” para modificar los objetos de sistema en versiones SQL Server 2008+: https://blogvisionarios.com/elrincondeldba/post.aspx?id=25&title=seguridad+en+sql+server+2008%3A+rootkits

Una vez tenemos abierto el acceso, analizaremos la definición de la vista:

ALTER VIEW [sys].[server_triggers] AS

     SELECT o.name,

          object_id = o.id,

          parent_class = o.pclass,

          parent_class_desc = pc.name,

          parent_id = o.pid,

          type = o.type,

          type_desc = n.name,

          create_date = o.created,

          modify_date = o.modified,

          is_ms_shipped = sysconv(bit, o.status & 1),         — OBJALL_MSSHIPPED

          is_disabled = sysconv(bit, o.status & 256)          — OBJTRG_DISABLED

     FROM sys.sysschobjs o

     LEFT JOIN sys.syspalnames n ON n.class = ‘OBTY’ AND n.value = o.type

     LEFT JOIN sys.syspalvalues pc ON pc.class = ‘UNCL’ AND pc.value = o.pclass

     WHERE o.type IN (‘TA’,‘TR’) AND o.nsclass = 20 AND o.pclass = 100   — x_eonc_TrgOnServer:x_eunc_Server

          AND has_access(‘TR’, o.id, o.pid, o.nsclass) = 1

Vemos que el tipo de objetos nsclass=20 engloba a los triggers a nivel de servidor. En la vista sys.all_objects este tipo de objetos están explícitamente excluidos (nsclass 20) por lo que no deberemos realizar ninguna modificación en dicha vista:

ALTER VIEW [sys].[all_objects] AS

       SELECT

             o.name, o.id AS object_id, r.indepid AS principal_id,

             o.nsid AS schema_id, o.pid AS parent_object_id,

             o.type, n.name AS type_desc,

             o.created AS create_date, o.modified AS modify_date,

             convert(bit, o.status & 1) AS is_ms_shipped,        — OBJALL_MSSHIPPED

             convert(bit, o.status & 16) AS is_published,        — OBJALL_RPL_PUBLISHED

             convert(bit, o.status & 64) AS is_schema_published    — OBJALL_RPL_PUB_SCHONLY

       FROM sys.sysschobjs o

       LEFT JOIN sys.syssingleobjrefs r ON r.depid = o.id AND r.class = 97 AND r.depsubid = 0 — SRC_OBJOWNER

       LEFT JOIN sys.syspalnames n ON n.class = ‘OBTY’ AND n.value = o.type

       WHERE o.nsclass not in (20,21)        — excluding DDL trigger

             AND HAS_ACCESS(‘AO’, o.id) = 1

 

GO

Para modificar la vista server_triggers optaremos por añadir una condición adicional que excluya a nuestro trigger de servidor de ser mostrado. En concreto, para mayor discreción, nombraremos a nuestro futuro trigger con un único espacio en blanco. Por tanto la modificación que deberemos realizar será la siguiente marcada en amarillo:

ALTER VIEW [sys].[server_triggers] AS

     SELECT o.name,

          object_id = o.id,

          parent_class = o.pclass,

          parent_class_desc = pc.name,

          parent_id = o.pid,

          type = o.type,

          type_desc = n.name,

          create_date = o.created,

          modify_date = o.modified,

          is_ms_shipped = sysconv(bit, o.status & 1),         — OBJALL_MSSHIPPED

          is_disabled = sysconv(bit, o.status & 256)          — OBJTRG_DISABLED

     FROM sys.sysschobjs o

     LEFT JOIN sys.syspalnames n ON n.class = ‘OBTY’ AND n.value = o.type

     LEFT JOIN sys.syspalvalues pc ON pc.class = ‘UNCL’ AND pc.value = o.pclass

     WHERE o.type IN (‘TA’,‘TR’) AND o.nsclass = 20 AND o.pclass = 100   — x_eonc_TrgOnServer:x_eunc_Server

          AND has_access(‘TR’, o.id, o.pid, o.nsclass) = 1

          AND o.name <> ‘ ‘

 

Para poder realizar esta modificación, no se nos permite realizarla directamente, pero tal y como explicamos en el artículo al que os enlacé, podemos hacerlo atacando a las tablas internas que contienen la definición. Para ello lo primero será localizar el object_id correspondiente a la vista y comprobamos que efectivamente corresponde con la definición:

select * from sys.sysschobjs where type=‘V’ and name=‘server_triggers’

Lidiando con usuarios sysadmin

 

select object_definition(-236)

Lidiando con usuarios sysadmin

Comprobaremos que tenemos en la tabla sysobjvalues el objeto correspondiente con la definición:

select imageval,convert(varchar(max),imageval) definicion from sys.sysobjvalues

where objid=-236

Lidiando con usuarios sysadmin

El último paso es aplicar el update sobre la tabla y reiniciar la instancia con la nueva base de datos resourcedb “modificada”:

update sys.sysobjvalues

set imageval=

    convert(varbinary(max),

        replace(convert(varchar(max),imageval),

            ‘AND has_access(”TR”, o.id, o.pid, o.nsclass) = 1’,

            ‘AND has_access(”TR”, o.id, o.pid, o.nsclass) = 1 AND o.name <> ” ”’))

where objid=-236

Una vez reiniciada procederemos a borrar nuestro antiguo trigger no_office_2013 y a recrearlo con un nombre compuesto por un espacio:

use master

go

CREATE TRIGGER [ ]

ON ALL SERVER

FOR LOGON

AS

BEGIN

IF APP_NAME()= ‘Microsoft Office 2013’

    ROLLBACK;

END;

Comprobaremos que no aparece en la vista sys.server_Triggers y, por tanto, tampoco aparece en el Management Studio, aunque siga existiendo y esté activo:

Lidiando con usuarios sysadmin Lidiando con usuarios sysadmin

 

 

El objeto podría ser borrado sin problemas si conocemos el “truco” realizado con un simple DROP.

drop trigger [ ] ON ALL SERVER

En resumen, hemos revisitado algunas técnicas existentes para dificultar el acceso o modificación de partes de un sistema a un usuario sysadmin. Estas medidas únicamente pueden considerarse como barreras que un administrador con conocimientos podría saltar. Cualquier sistema que requiera de cierta seguridad nunca debería tener usuarios de aplicaciones con permisos sysadmin. Por la misma razón cuando un administrador debe tomar las riendas de un sistema debería revisar que parte de un entorno limpio y seguro.

Existen otras técnicas que, si bien no impiden la acción, si nos permitirán conocer quien la realizó. Estamos hablando por ejemplo de auditorías a fichero con ruta destino en ubicación remota. Por ejemplo podríamos ubicarlas en un servidor externo donde únicamente la cuenta de servicio de SQL Server tenga permisos para crear y escribir trazas/logs pero no pueda borrarlos. De esta forma, aunque el sistema original quedara comprometido, nos quedaría el “rastro” de las acciones previas en el servidor remoto. Obviamente un sysadmin podría deshabilitar esta auditoría por lo que la “ausencia de registros auditados” o los registros que indicaran la parada de la auditoría deberían monitorizarse y disparar medidas reactivas de forma urgente. En casos extremos podríamos reaccionar automáticamente ordenando un apagado físico del servidor mediante la iLO (o el equivalente en sistemas no HP) evitando el uso fraudulento del sistema ya comprometido.

 

0 Shares:
Deja una respuesta

Tu dirección de correo electrónico no será publicada.

You May Also Like