Como proteger CREATE PUBLIC SYNONYM contra escalonamento de privilégios

This post is also available in: English

Durante a minha última apresentação no GUOB Tech Tour 2016 - Oracle Technology Tour LA - Brazil, demonstrei como poderíamos facilmente usar um privilégio de CREATE PUBLIC SYNONYM para escalonar até um privilégio DBA.

Neste artigo, eu vou compartilhar com vocês uma package que criei e uso em meus sistemas para permitir que os usuários sejam capazes de criar os seus próprios sinônimos públicos sem comprometer a segurança do Banco de Dados.

Então vamos começar.

Como funciona?

Essa package funciona permitindo a um determinado usuário que apenas crie sinônimos públicos aos seus próprios objetos ou que remova sinônimos públicos que estejam apontando para um de seus objetos.

Conceda os grants necessários ao dono da package

Antes de mais nada, essa package precisa ser criada em um usuário que possua os seguintes privilégios mínimos concedidos diretamente (não via ROLE):

  • SELECT on DBA_SYNONYMS
  • CREATE PUBLIC SYNONYM
  • DROP PUBLIC SYNONYM

1. Crie a package

CREATE OR REPLACE PACKAGE MANAGE_PUBLIC_SYNONYM AS
  -- Created by Rodrigo Jorge - www.dbarj.com.br --
  PROCEDURE CREATE_SYNONYM(SYNONYM_NAME IN VARCHAR2, OBJECT_NAME IN VARCHAR2);
  PROCEDURE DROP_SYNONYM(SYNONYM_NAME IN VARCHAR2);
END;
/

2 Crie o package body

CREATE OR REPLACE PACKAGE BODY MANAGE_PUBLIC_SYNONYM AS
  -- Created by Rodrigo Jorge - www.dbarj.com.br --
  FUNCTION CHECK_EXISTS(SYN_NAME IN VARCHAR2) RETURN BOOLEAN IS
    OUT_RESULT NUMBER;
  BEGIN
    SELECT 1
    INTO   OUT_RESULT
    FROM   DBA_SYNONYMS
    WHERE  OWNER = 'PUBLIC'
    AND    SYNONYM_NAME = SYN_NAME;
    RETURN TRUE;
  EXCEPTION
    WHEN NO_DATA_FOUND THEN
      RETURN FALSE;
  END;

  FUNCTION GET_PUBLIC_SYN_OWNER(SYN_NAME IN VARCHAR2) RETURN VARCHAR2 IS
    OUT_RESULT VARCHAR2(30);
  BEGIN
    SELECT TABLE_OWNER
    INTO   OUT_RESULT
    FROM   DBA_SYNONYMS
    WHERE  OWNER = 'PUBLIC'
    AND    SYNONYM_NAME = SYN_NAME;
    RETURN OUT_RESULT;
  END;

  PROCEDURE RAISE_ERROR(IN_CODE IN NUMBER) IS
  BEGIN
    CASE IN_CODE
      WHEN -20001 THEN
        RAISE_APPLICATION_ERROR(IN_CODE, 'Synonym already exists.');
      WHEN -20002 THEN
        RAISE_APPLICATION_ERROR(IN_CODE, 'Synonym does not exist.');
      WHEN -20003 THEN
        RAISE_APPLICATION_ERROR(IN_CODE, 'Synonym is not yours.');
      ELSE
        RAISE_APPLICATION_ERROR(-20999, 'Generic error.');
    END CASE;
  END;

  PROCEDURE CREATE_SYNONYM(SYNONYM_NAME IN VARCHAR2, OBJECT_NAME IN VARCHAR2) IS
    SESS_USER VARCHAR2(30);
  BEGIN
    IF CHECK_EXISTS(SYNONYM_NAME) = TRUE
    THEN
      RAISE_ERROR(-20001);
    END IF;
    SESS_USER := SYS_CONTEXT('USERENV', 'SESSION_USER');
    EXECUTE IMMEDIATE 'CREATE PUBLIC SYNONYM ' || DBMS_ASSERT.ENQUOTE_NAME(SYNONYM_NAME, FALSE) || ' FOR ' || DBMS_ASSERT.ENQUOTE_NAME(SESS_USER, FALSE) || '.' || DBMS_ASSERT.ENQUOTE_NAME(OBJECT_NAME, FALSE);
  END;

  PROCEDURE DROP_SYNONYM(SYNONYM_NAME IN VARCHAR2) IS
    OBJ_OWNER VARCHAR2(30);
    SESS_USER VARCHAR2(30);
  BEGIN
    IF CHECK_EXISTS(SYNONYM_NAME) = FALSE
    THEN
      RAISE_ERROR(-20002);
    END IF;
    OBJ_OWNER := GET_PUBLIC_SYN_OWNER(SYNONYM_NAME);
    SESS_USER := SYS_CONTEXT('USERENV', 'SESSION_USER');
    IF OBJ_OWNER <> SESS_USER
    THEN
      RAISE_ERROR(-20003);
    END IF;
    EXECUTE IMMEDIATE 'DROP PUBLIC SYNONYM ' || DBMS_ASSERT.ENQUOTE_NAME(SYNONYM_NAME, FALSE);
  END;

END;
/

3 Dê privilégio na package aos usuários que precisam de Sinônimos Públicos

Examplo:

GRANT EXECUTE ON MANAGE_PUBLIC_SYNONYM TO SCOTT;

Opcionalmente, você também pode criar um sinônimo para evitar que o owner da package seja sempre digitado:

CREATE SYNONYM SCOTT.MANAGE_PUBLIC_SYNONYM FOR MANAGE_PUBLIC_SYNONYM;

E é isso.

Como usar?

Para criar um sinônimo público

BEGIN
  MANAGE_PUBLIC_SYNONYM.CREATE_SYNONYM('EMP','EMP');
END;
/

Para remover um sinônimo público

BEGIN
  MANAGE_PUBLIC_SYNONYM.DROP_SYNONYM('EMP');
END;
/

E lembre-se: não será possível tocar em um sinônimo público de outro usuário.

Gostou? Não deixe de comentar ou deixar um 👍!

Deixe um comentário

Seu e-mail não será publicado.