Achando usuários Oracle com privilégio DBA oculto por roles

This post is also available in: English

Alguns usuários do Oracle podem possuir privilégios muito perigosos sem o seu consentimento, que podem causar um grande estrago no banco de dados. As vezes esse privilégio está oculto via uma cadeia de roles, o que torna difícil a percepção.

Ex:

SQL> CREATE USER SYSADM identified by "sysadm1";

SQL> CREATE ROLE A;
SQL> CREATE ROLE B;
SQL> CREATE ROLE SYSROLE;

SQL> GRANT A TO SYSADM;
SQL> GRANT B TO A;
SQL> GRANT SYSROLE TO B;

SQL> GRANT DBA TO SYSROLE;
SQL> GRANT CREATE SESSION TO SYSADM;

Neste exemplo, o GRANT DBA para o SYSADM foi oculto. Assim como a role DBA, outra role perigosa é o IMP_FULL_DATABASE, que concede praticamente tudo a um usuário.

Outra forma também é conceder manualmente os grants de sistemas para uma nova role e ocultar esta role através de uma cadeia de roles, como no exemplo acima.

Ex:

SQL> CREATE USER SYSADM identified by "sysadm1";

SQL> CREATE ROLE A;
SQL> CREATE ROLE B;
SQL> CREATE ROLE SYSROLE;

SQL> GRANT A TO SYSADM;
SQL> GRANT B TO A;
SQL> GRANT SYSROLE TO B;

SQL> GRANT GRANT ANY PRIVILEGE TO SYSROLE;
SQL> GRANT CREATE SESSION TO SYSADM;

Para rastrear esses tipos de privilégios, desenvolvi a query abaixo. Após executá-la, o próximo passo é revogar os privilégios desnecessário e em seguida executar a query novamente para verificar se o usuário já está limpo.

Segue:

SELECT GRANTEE,
       PRIVILEGE,
       ACCOUNT_STATUS
FROM   (SELECT GRANTEE,
               LTRIM(MAX(SYS_CONNECT_BY_PATH(PRIVILEGE, ', ')), ', ') PRIVILEGE
        FROM   (SELECT GRANTEE,
                       PRIVILEGE,
                       ROW_NUMBER() OVER(PARTITION BY GRANTEE ORDER BY PRIVILEGE) RN
                FROM   (SELECT DISTINCT NVL(A.GRANTEE, B.GRANTEE) GRANTEE,
                                        NVL(A.PRIVILEGE, B.PRIVILEGE) PRIVILEGE
                        FROM   (SELECT A.GRANTEE,
                                       A.PATH PRIVILEGE
                                FROM   (SELECT A.GRANTEE,
                                               A.GRANTED_ROLE_ROOT PRIVILEGE,
                                               A.PATH,
                                               A.NIVEL,
                                               RANK() OVER(PARTITION BY A.GRANTEE, A.FIRST_ROLE ORDER BY NIVEL ASC) RANK
                                        FROM   (SELECT A.GRANTEE,
                                                       GRANTED_ROLE FIRST_ROLE,
                                                       CONNECT_BY_ROOT GRANTED_ROLE GRANTED_ROLE_ROOT,
                                                       '(' || LTRIM(SYS_CONNECT_BY_PATH(GRANTED_ROLE, '->'), '->') || ')' PATH,
                                                       LEVEL NIVEL
                                                FROM   DBA_ROLE_PRIVS A
                                                CONNECT BY PRIOR GRANTEE = GRANTED_ROLE) A,
                                               DBA_SYS_PRIVS B
                                        WHERE  A.GRANTEE NOT IN (SELECT ROLE
                                                                 FROM   DBA_ROLES)
                                        AND    A.GRANTED_ROLE_ROOT = B.GRANTEE
                                        AND    (B.PRIVILEGE LIKE 'DROP ANY%' OR B.PRIVILEGE LIKE 'GRANT%' OR B.PRIVILEGE IN ('ADMINISTER DATABASE TRIGGER'))) A
                                WHERE  A.RANK = 1) A
                        FULL   OUTER JOIN (SELECT GRANTEE,
                                                 PRIVILEGE
                                          FROM   DBA_SYS_PRIVS
                                          WHERE  (PRIVILEGE LIKE 'DROP ANY%' OR PRIVILEGE LIKE 'GRANT%' OR PRIVILEGE IN ('ADMINISTER DATABASE TRIGGER'))
                                          AND    GRANTEE NOT IN (SELECT ROLE
                                                                 FROM   DBA_ROLES)) B ON B.GRANTEE = A.GRANTEE))
        START  WITH RN = 1
        CONNECT BY PRIOR RN = RN - 1
            AND    PRIOR GRANTEE = GRANTEE
        GROUP  BY GRANTEE) A,
       DBA_USERS B
WHERE  A.GRANTEE = B.USERNAME
ORDER  BY 1;

(Você pode modificá-la colocando outros privilégios que deseja buscar)

Se você estiver no Oracle 10g e receber o erro "ORA-00600: código de erro interno, argumentos: [qctcte1], [0], [], [], [], [], [], []", use a versão adaptada que segue (menos complexa mas menos completa):

SELECT GRANTEE,
       PRIVILEGE,
       ACCOUNT_STATUS
FROM   (SELECT GRANTEE,
               LTRIM(MAX(SYS_CONNECT_BY_PATH(PRIVILEGE, ', ')), ', ') PRIVILEGE
        FROM   (SELECT GRANTEE,
                       PRIVILEGE,
                       ROW_NUMBER() OVER(PARTITION BY GRANTEE ORDER BY PRIVILEGE) RN
                FROM   (SELECT DISTINCT NVL(A.GRANTEE, B.GRANTEE) GRANTEE,
                                        NVL(A.PRIVILEGE, B.PRIVILEGE) PRIVILEGE
                        FROM   (SELECT A.GRANTEE,
                                       A.GRANTED_ROLE PRIVILEGE
                                FROM   (SELECT A.*
                                        FROM   DBA_ROLE_PRIVS A
                                        CONNECT BY PRIOR GRANTEE = GRANTED_ROLE) A,
                                       DBA_SYS_PRIVS B
                                WHERE  A.GRANTEE NOT IN (SELECT ROLE
                                                         FROM   DBA_ROLES)
                                AND    A.GRANTED_ROLE = B.GRANTEE
                                AND    (B.PRIVILEGE LIKE 'DROP ANY%' OR B.PRIVILEGE LIKE 'GRANT%' OR B.PRIVILEGE IN ('ADMINISTER DATABASE TRIGGER'))) A
                        FULL   OUTER JOIN (SELECT GRANTEE,
                                                 PRIVILEGE
                                          FROM   DBA_SYS_PRIVS
                                          WHERE  (PRIVILEGE LIKE 'DROP ANY%' OR PRIVILEGE LIKE 'GRANT%' OR PRIVILEGE IN ('ADMINISTER DATABASE TRIGGER'))
                                          AND    GRANTEE NOT IN (SELECT ROLE
                                                                 FROM   DBA_ROLES)) B ON B.GRANTEE = A.GRANTEE))
        START  WITH RN = 1
        CONNECT BY PRIOR RN = RN - 1
            AND    PRIOR GRANTEE = GRANTEE
        GROUP  BY GRANTEE) A,
       DBA_USERS B
WHERE  A.GRANTEE = B.USERNAME
ORDER  BY 1;
Gostou? Não deixe de comentar ou deixar um 👍!

5 comentários

Pular para o formulário de comentário

  1. Muito bom Rodrigo,

    Estava precisando dessa query, vou usar.

    obrigado,
    Marcos

    • Cristiano Vasconcelos em junho 25, 2015 às 09:03
    • Responder

    Rodrigo, bom dia!

    Amigo, sua query é o que preciso, só que está dando este erro:
    Erro na Linha de Comandos : 1 Coluna : 1
    Relatório de erros -
    Erro de SQL: ORA-00600: código de erro interno, argumentos: [qctcte1], [0], [], [], [], [], [], []
    00600. 00000 - "internal error code, arguments: [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s], [%s]"
    *Cause: This is the generic internal error number for Oracle program
    exceptions. It indicates that a process has encountered a low-level,
    unexpected condition. The first argument is the internal message
    number. This argument and the database version number are critical in
    identifying the root cause and the potential impact to your system.

    Meu oracle é "Oracle Database 10g Enterprise Edition Release 10.2.0.3.0", você teria como ajudar-me???

    1. Oi Cristiano,

      Cara, isso é com certeza algum Bug desta versão antiga. Você deveria instalar o último PSU desta release que provavelmente vai consertar esta falha.

      Eu tenho uma versão mais simples (mas também menos robusta) desta query, adicionei ela no post. Vê se funciona!

      Abraços,

      Rodrigo

        • Cristiano Vasconcelos em junho 25, 2015 às 10:52
        • Responder

        Caramba RODRIGO, cara, muito OBRIGADO PELO RETORNO, vou testar agora mesmo...

  2. Para mim funcionou perfeitamente, obrigado.

Deixe um comentário

Seu e-mail não será publicado.