Finding Oracle users with DBA privilege hidden by roles

This post is also available in: Português

Some users in Oracle may have very dangerous privileges without your consent, they can cause great damage to the database. Sometimes this privilege is hidden via a chain of roles, which makes it difficult to perception.

Eg:

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;

In this example, the GRANT DBA to SYSADM was hidden. As the DBA role, IMP_FULL_DATABASE is another dangerous role, granting virtually everything a user.

Other form is also manually grant the "system grants" to a new role and hide this role through a chain of roles, as in the example above.

Eg:

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;

To track these hidden privileges, I developed the query below. After running it, the next step is to revoke unnecessary privileges and then run the query again to check if the user is clean.

Query:

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;

(You can modify it by putting other privileges you want to find)

If you are still using Oracle 10g and receive the error "ORA-00600: internal error code, arguments: [qctcte1], [0], [], [], [], [], [], []", try the adapted version below (less complex):

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;
Have you enjoyed? Please leave a comment or give a 👍!

8 comments

Skip to comment form

    • Dee Lal on February 10, 2017 at 13:03
    • Reply

    Hi Rodrigo, great post.

    I have a question. Is there any way of identifying which user granted the DBA role i.e. which user executed the 'grant dba to user;' command? Notably, user who may have granted themselves dba (understandably they would be connected as sys or system, unless you know of other means!).
    Guessing it would require auditing to be switched on for starters.

    Even better if this can be gleaned through OEM.

    Thanks again
    Dee

    1. Hi Dee Lal,

      Exactly, the only way to know who granted some user DBA is to enable the database auditing. This can be achieved by running "audit grant any role".

      Regards,
      RJ

    • Dee on February 13, 2017 at 09:36
    • Reply

    Great, thanks for the confirmation Rodrigo.

    • Robert on January 17, 2018 at 12:32
    • Reply

    This is the query I use to find users with DBA privileges:


    select distinct g.grantee username
    from dba_users u
    , ( select grantee,granted_role from dba_role_privs
    connect by prior grantee = granted_role start with granted_role='DBA'
    ) g
    where u.username = g.grantee
    order by 1;

    1. Hi Robert,
      Good query. However, as you are searching privileges based on Role name, not the system privileges granted, an attacker could clone DBA role with another name and you wouldn't notice.
      Regards,
      RJ

  1. There are many situations where ADMIN wanted to revoke DBA roles granted to the user. This is the best way to filter out users having dba privileges in Oracle

    • Muhammad Shafiq Khan on October 23, 2018 at 04:07
    • Reply

    for the first time i visit to your page ..
    i really appreciated your effort..
    thanks

    1. Hi Muhammad, glad you enjoyed =]

Leave a Reply

Your email address will not be published.