Blog d'un DBA sur le SGBD Oracle et SQL

19 septembre 2018

Sécurité et commande STRINGS sous Unix : voir les données non cryptées d'une base, voir dans le passé...



Introduction
Vous connaissez l'expression OUT OF THE BOX? C'est pour dire qu'il faut parfois s'extraire des limites logiques d'un système et penser en dehors de celui-ci, penser en dehors de la boîte.

Avec Oracle, si je veux voir des données d'une table, je me connecte et, si j'ai les droits, je fais un SELECT sur cette table. Si je n'ai pas les droits, c'est cuit... HE BEN NON, si on pense OUT OF THE BOX (la boîte étant Oracle), on peut tenter notre chance sous Unix avec la commande STRINGS. Via celle-ci on peut voir directement les chaînes de caractères dans des fichiers binaires, s'ils sont non cryptés. Comme faille de sécurité, c'est quand même énorme, on est d'accord?

Cela n'a l'air de rien mais on peut voir non seulement les données d'une table sans se connecter à Oracle mais aussi l'historique de cette table sans utiliser les commandes FLASHBACK... Avec cette commande, on peut récupérer des adresses mail mais aussi des numéros de carte bancaire en filtrant l'affichage pour ne garder qu'une suite de 16 chiffres (au cas où le numéro de cb soit stockée dans un champ de type CHAR ou VARCHAR2).


 
Points d'attention
Aucun.



Base de tests
N'importe quelle base Oracle.



Exemples
============================================================================================
Forcer Oracle à écrire des données sur disque dur : flusher le buffer cache
============================================================================================

On crée d'abord un tablespace dédié à notre table de tests et on crée la table dans ce tablespace, pour qu'elle soit associée à un datafile précis.
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> alter user HR quota unlimited on TEST_STRINGS;
     SQL> CREATE TABLE HR.ZZTEST_STRINGS(ID VARCHAR2(20 CHAR)) tablespace test_strings;
     SQL> select tablespace_name from dba_tables where table_name = 'ZZTEST_STRINGS';
     TABLESPACE_NAME
     ------------------------------
     TEST_STRINGS
     
Les commandes STRINGS seront lancées dans un autre terminal que celui des commandes SQL pour plus de souplesse.
D'abord, on se déplace dans le répertoire où se trouvent les fichiers de données de notre base.
     [oracle@vbgeneric orcl]$ cd /u01/app/oracle/oradata/orcl12c/orcl
     
     [oracle@vbgeneric orcl]$ ls -l
     total 2156408
     -rw-r----- 1 oracle oinstall    7938048 Sep 16 06:02 APEX_1991375173370654.dbf
     -rw-r----- 1 oracle oinstall    2695168 Sep 16 06:02 APEX_1993195660370985.dbf
     -rw-r----- 1 oracle oinstall 1247813632 Sep 16 06:02 sysaux01.dbf
     -rw-r----- 1 oracle oinstall  367009792 Sep 16 06:02 system01.dbf
     -rw-r----- 1 oracle oinstall   67117056 Sep 16 05:57 temp01.dbf
     -rw-r----- 1 oracle oinstall   10493952 Sep 16 06:02 test_strings.dbf
     -rw-r----- 1 oracle oinstall  482353152 Sep 16 06:02 undotbs01.dbf
     -rw-r----- 1 oracle oinstall   81272832 Sep 16 06:02 users01.dbf
     [oracle@vbgeneric orcl]$
     
On insère une donnée dans la table : rien dans le fichier car DBWR n'a pas encore écrit sur le disque dur. A noter que deux chaînes de caractères sont déjà là : -ORCL12C et TEST_STRINGS, soit le nom de l'instance (je suis en 12c CDB, le nom de ma base est orcl mais l'instance est orcl12c) et le nom du tablespace. Noter aussi le trait d'union devant ORCL12C.
     [oracle@vbgeneric orcl]$ sqlplus HR/HR@orcl
     SQL> insert into ZZTEST_STRINGS values('TESTZZ01');
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     
Le COMMIT ne change rien puisque DBWR ne s'exécute pas lors de celui-ci, seul LGWR est appelé.
     SQL> commit;
     Commit complete.
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     
Alors comment forcer Oracle a écrire dans un datafile? La solution miracle consiste à flusher le database buffer cache!
     SQL> alter system flush BUFFER_CACHE;

A quoi correspond le AAAAAAAA du fichier? Mystère!   
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01


============================================================================================
Impossible de vider le datafile des anciennes données de la table
============================================================================================

Je vais essayer de supprimer dans ce datafile les données de la table mais c'est bien plus difficile que je ne pensais.
Test du ROLLBACK : tiens, l'INSERT annulé reste dans le fichier, même après le ROLLBACK!
     SQL> insert into ZZTEST_STRINGS values('TESTZZ02');
     SQL> select * from zztest_strings;
     ID
     --------------------
     TESTZZ01
     TESTZZ02
     
     SQL> alter system flush BUFFER_CACHE;
     SQL> rollback;
     SQL> alter system flush BUFFER_CACHE;
     SQL> select * from zztest_strings;
     ID
     --------------------
     TESTZZ01
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
Alors, comment forcer Oracle à nettoyer le fichier? Avec un arrêt/relance? Non, ça ne marche toujours pas.
     SQL> connect SYS/oracle@orcl as sysdba
     SQL> shutdown immediate;
     Pluggable Database closed.
     SQL> startup
     Pluggable Database opened.
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
Visiblement les données insérées doivent être marquées par Oracle dans le fichier comme annulées mais on n'a pas vraiment cette info avec STRINGS.
Testons maintenant le DELETE. Echec encore : le fichier n'est pas nettoyé.
     SQL> delete from HR.ZZTEST_STRINGS;
     SQL> commit;
     SQL> select * from HR.ZZTEST_STRINGS;
     no rows selected
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02,
     TESTZZ01
     
     SQL> alter system flush buffer_cache;
     System altered.
     
Bizarre, il y a un caractère < après les données éliminées, à l'exception toutefois de la dernière ligne.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02<
     TESTZZ01
     
Si on visualise le fichier avec vi, c'est plus précis : un caractère < devant chaque enregistrement supprimé et la suite de caractères <^B^A^.
     [oracle@vbgeneric orcl]$ vi test_strings.dbf
     <^B^A^HTESTZZ02<^B^A^HTESTZZ01^A^F<
    
Quid du TRUNCATE : cela doit ramener le HWM à 0, la table n'a plus de bloc mais qui dit que cela met à jour le fichier?
     SQL> truncate table HR.ZZTEST_STRINGS;
     
Comme prévu, cela n'a pas d'impact sur le contenu du datafile.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ02<
     TESTZZ01

Pour poursuivre mes tests, je supprime le tablespace puis le datafile sous Unix (il est impossible avec Oracle de supprimer l'unique fichier d'un tablespace); ensuite je les recrée à l'identique.
     SQL> drop tablespace test_strings including contents;
     [oracle@vbgeneric orcl]$ rm test_strings.dbf
     
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> CREATE TABLE HR.ZZTEST_STRINGS(ID VARCHAR2(20 CHAR)) tablespace test_strings;

Saturer le datafile    
Je veux voir maintenant s'il est possible d'effacer les anciennes données du fichier en remplissant celui-ci avec de nouvelles données.
     SQL> insert into HR.ZZTEST_STRINGS values('TESTZZ01');
     SQL> select * from HR.ZZTEST_STRINGS;
     ID
     --------------------
     TESTZZ01

     SQL> commit;
     SQL> alter system flush buffer_cache;
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01
     
Mon objectif est de voir maintenant si, suite à un  DELETE, la donnée TESTZZ01 est bien effacée du fichier.
     SQL> delete from HR.ZZTEST_STRINGS;
     SQL> commit;
     SQL> select * from HR.ZZTEST_STRINGS;
     no rows selected
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TESTZZ01


Normalement Oracle réutilise l'espace des données supprimées dans les blocs.
D'abord, suite à mon CREATE TABLE, Oracle a créé 8 blocs de données. Je n'avais qu'une donnée dans ma table, "TESTZZ01", alors si je fais des INSERTs, Oracle va insérer les données dans les blocs au max avant de réutiliser l'espace deleté ou bien il utilise celui-ci tout de suite?
     SQL> select BYTES, BLOCKS from dba_extents where SEGMENT_NAME =  'ZZTEST_STRINGS';
          BYTES     BLOCKS
     ---------- ----------
          65536        8
     
Je vais exécuter N boucles pour remplir le tablespace.    
     SQL> BEGIN
         FOR i IN 1..100 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /    
     PL/SQL procedure successfully completed.
     
     SQL> select count(*) from hr.zztest_strings;
       COUNT(*)
     ----------
            100
         
     SQL> commit;
     SQL> alter system flush buffer_cache;
     
Intéressant, le "TESTZZ01" est toujours là mais avec un < après la valeur précédente insérée, comme si ce < était un signe pour dire "Après, ce sont des valeurs délétées". Pour gagner de la place, j'efface les lignes entre DELETE99 et DELETE5.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
         DELETE100,
     DELETE99,
     ...
     DELETE5,
     DELETE4,
     DELETE3,
     DELETE2,
     DELETE1<
     TESTZZ01
     [oracle@vbgeneric orcl]$
     
On recommence, avec plus de données!    
     SQL> BEGIN
         FOR i IN 101..100000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /    
     PL/SQL procedure successfully completed.
     
     SQL> commit;
     SQL> alter system flush buffer_cache;
     
Aïe, test KO... Oracle n'a visiblement pas réutilisé l'emplacement de l'ancienne valeur.
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
     [oracle@vbgeneric orcl]$
     
On va essayer d'aller au max du tablespace : 10 Mo avec 1 million d'enregistrements.
     SQL> BEGIN
         FOR i IN 100001..1000000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /
     BEGIN
     *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     ORA-06512: at line 3
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
     
Essayons avec moins de données.
     SQL> BEGIN
         FOR i IN 100001..200000 LOOP
             insert into HR.ZZTEST_STRINGS values('DELETE' || to_char(i));    
         END LOOP;
     END;
     /  
     PL/SQL procedure successfully completed.
     
     SQL> /
     SQL> /
     BEGIN
     *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     ORA-06512: at line 3
     
Après de multiples INSERTs, j'arrive enfin à ne plus pouvoir en insérer ne serait-ce qu'un seul.
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     
     SQL> exec dbms_stats.gather_table_stats('HR', 'ZZTEST_STRINGS');
            
     SQL> select sum(BYTES), sum(BLOCKS) from dba_extents where SEGMENT_NAME =  'ZZTEST_STRINGS' group by segment_name;
     SUM(BYTES) SUM(BLOCKS)
     ---------- -----------
        9437184      1152

     [oracle@vbgeneric orcl]$ strings test_strings.dbf | wc -l
     516668

     

     SQL> select pct_free from dba_tables where table_name = 'ZZTEST_STRINGS';
       PCT_FREE
     ----------
         10
     
Je mets le PCTFREE à 0, mais ça ne change rien, plus d'insertion possible.    
     SQL> alter table HR.ZZTEST_STRINGS PCTFREE 0;
     
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2;    
     insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS

     SQL> select count(*) from HR.ZZTEST_STRINGS;
       COUNT(*)
     ----------
         467796
    
Essayons de shrinker l'espace de la table pour la réorganiser et voir si cela me donne de l'espace.
     SQL> alter table HR.ZZTEST_STRINGS enable row movement;
     SQL> alter table HR.ZZTEST_STRINGS shrink space;
 
Ben non, test KO : impossible de faire un nouvel INSERT.
     SQL> insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2;
     insert into hr.zztest_strings select * from hr.zztest_strings where rownum < 2
                    *
     ERROR at line 1:
     ORA-01653: unable to extend table HR.ZZTEST_STRINGS by 128 in tablespace TEST_STRINGS
     
     SQL> alter system flush buffer_cache;
     
Et la donnée supprimée est toujours présente dans le fichier! Incroyable!!!!!
     [oracle@vbgeneric orcl]$ strings test_strings.dbf | grep -i TESTZZ01
     TESTZZ01
 

============================================================================================
Accès à l'historique des données sur disque dur suite à des UPDATEs
============================================================================================

Nous allons maintenant pousser un peu plus loin nos tests et voir que Oracle nous donne même accès à l'historique des modifications d'une donnée, et ce sans passer par la techno FLASHBACK.

Je recrée la même table mais avec un nombre de colonnes différent, pour simplifier la commande UPDATE.
     SQL> connect HR/HR@orcl;    
     SQL> CREATE TABLESPACE TEST_STRINGS DATAFILE '/u01/app/oracle/oradata/orcl12c/orcl/test_strings.dbf' size 10m EXTENT MANAGEMENT LOCAL AUTOALLOCATE SEGMENT SPACE MANAGEMENT AUTO;
     SQL> CREATE TABLE ZZTEST_STRINGS(ID NUMBER, COMMENT_TEST VARCHAR2(20 CHAR)) tablespace test_strings;    
     
     SQL> insert into ZZTEST_STRINGS values(1, 'TEST1');
     SQL> insert into ZZTEST_STRINGS values(2, 'TEST2')
     SQL> insert into ZZTEST_STRINGS values(3, 'TEST3')
     SQL> commit;
     
     SQL> alter system flush BUFFER_CACHE;
     
Euh, j'ai les VARCHAR2 mais pas les nombres... Ah oui, STRINGS ne gère que les chaînes de caractères :-)
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST3,
     TEST2,
     TEST1
     [oracle@vbgeneric orcl]$


Voyons maintenant ce qui se passe avec un UPDATE.
     SQL> UPDATE ZZTEST_STRINGS set COMMENT_TEST = 'TEST1_UPDATE' where id = 1;
     SQL> commit;
     SQL> alter system flush BUFFER_CACHE;
     
Bingo, j'en étais sur, on voit l'historique de nos modifications :-)
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST1_UPDATE,
     TEST3,
     TEST2,
     TEST1
     
Allez, encore une fois! Et même résultat : on a l'historique des données modifiées via cette commande!
     SQL> UPDATE ZZTEST_STRINGS set COMMENT_TEST = 'TEST1_UPDATE_ENCORE' where id = 1;
     SQL> commit;
     SQL> alter system flush BUFFER_CACHE;
     
     [oracle@vbgeneric orcl]$ strings test_strings.dbf
     }|{z
     -ORCL12C
     TEST_STRINGS
     AAAAAAAA
     TEST1_UPDATE_ENCORE,
     TEST1_UPDATE,
     TEST3,
     TEST2,
     TEST1


Bon, arrêtons là, preuve est faite que sous Unix et avec la commande STRINGS on peut lire les données d'une base sans être connecté à celle-ci et même lire des données d'une table supprimée et, plus fort, de voir son historique; attention, cela ne concerne que les données de type chaîne de caractères. Cela devrait vous convaincre de crypter vos données sensibles, non?

    

Posté par David DBA à 09:49 - - Permalien [#]
Tags : , ,


11 septembre 2018

Comment devenir un autre utilisateur sous Oracle?


Introduction
Comment un user peut-il interagir avec les objets d'un autre user? Est-ce facile? Impossible? Les problématiques de droits et de rôles sont complexes avec Oracle mais il existe différentes techniques permettant à un user U1 d'accéder aux objets d'un user U2 et c'est ce que nous allons voir.


 
Points d'attention
Trop pour les énumérer :-)



Base de tests
N'importe quelle base Oracle.



Exemples

Dans la suite de cet article, j'utiliserais les conventions suivantes :
- U1 : user zztest, avec le droit CREATE SESSION au minimum.
- Au début de chaque test, on créé une table zz01 appelée ci-après T1 ou un dblink.
- U2 : user zztest02, avec le droit CREATE SESSION au minimum voir DBA.
- Entre chaque test on supprime les users pour éviter que des rôles ou droits ne traînent et ne faussent les tests suivants.
      
Le test a réaliser pour vérifier si on interagit avec un autre schéma est simple : U1 doit supprimer ou interroger un objet du schéma U2.
      
============================================================================================
Solution 1 : user avec le rôle DBA ou bien user SYS
============================================================================================

Dans ce test, U1 a le rôle DBA ou bien il est SYS : il peut quasiment tout faire, y compris dropper des objets du schéma U2 (sauf les dblinks mais c'est une autre histoire).
     SQL> show user
     USER est "SYS"
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
      
     SQL> connect zztest02
     SQL> create table zz01 (id number);
      
     SQL> connect / as sysdba
      
Le test est OK : le user SYS peut intéragir avec les objets du schéma zztest02 par défaut; ici, il drop une table.
     SQL> drop table zztest02.zz01;
     Table supprimee.
      
     SQL> drop user zztest02 cascade;
     
      
============================================================================================
Solution 2 : user qui a reçu des droits sur les objets d'un autre user
============================================================================================
    
Cette fois U1 n'a pas le rôle DBA mais il a les droits sur les objets de U2, donnés justement par U2.
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant resource, create session to zztest02;
     SQL> connect zztest02
     SQL> create table zz01 (id number);
      
     SQL> connect / as sysdba
     SQL> show user
     USER est "SYS"
      
     SQL> connect zztest
     SQL> select * from zztest02.zz01;
     select * from zztest02.zz01
                            *
     ERREUR a la ligne 1 :
     ORA-00942: Table ou vue inexistante
      
     SQL> connect zztest02
     SQL> grant select on zz01 to zztest;
      
Cette fois c'est OK, U1 accède bien à la table de U2.
     SQL> connect zztest
     SQL> select * from zztest02.zz01;
     aucune ligne selectionnee
      
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;

     
============================================================================================
Solution 3 : user qui connait le password d'un autre user
============================================================================================
    
U1 cette fois connaît le mot de passe de U2 : soit on le lui a dit, soit il a été récupéré dans un fichier excel (ne riez pas, c'est courant), soit deviné... Quoi qu'il en soit, U1 devient à un instant t le user U2.
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> create table zz01 (id number);
      
Maintenant le user U1 se connecte puis il va se connecter comme U2 pour accéder à ses objets .
     SQL> connect zztest
     SQL> connect zztest02

U1 saisit le mot de passe de U2 et drop une de ses tables.
     SQL> drop table zz01;
     Table supprimee.
      
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
     

============================================================================================
Solution 4 : avoir le droit ANY
============================================================================================
    
Dans la longue liste des privilèges système, il en est une catégorie particulière, celle qui comprends le mot ANY. Elle permet à un utilisateur d'agir sur un autre schéma sans connaître son password... puissant, non?
     SQL> select name from SYSTEM_PRIVILEGE_MAP where name like '%ANY%' order by 1;
     NAME
     ------------
     ...
     ALTER ANY INDEX
     ALTER ANY PROCEDURE
     ALTER ANY ROLE
     ...
     CREATE ANY TABLE
     CREATE ANY TRIGGER
     ...

     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> grant create any table to zztest;

     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> create table zz01 (id number);


Et maintenant, le user U1 va créer une table dans le schéma du user U2; cette table sera en revanche propriété du user U2 et non pas de son créateur.
     SQL> connect zztest
     SQL> create table zztest02.test_any (id date);
     SQL> connect SYS@ORCL as sysdba
     SQL> select owner from dba_tables where table_name = 'TEST_ANY';
     OWNER
     ---------------
     ZZTEST02

Et on voit même que le créateur de la table ne peut pas faire de SELECT dessus; vous notez qu'on a pas le message "Table inexistante", ce qui prouve que avec le droit CREATE ANY, le user connaît la table qu'il a créé mais il lui faut le droit SELECT dessus pour l'interroger.
     SQL> connect zztest
     SQL> select * from zztest02.test_any;
     select * from zztest02.test_any
                            *
     ERROR at line 1:
     ORA-01031: insufficient privileges


============================================================================================
Solution 5 : changer de schéma courant avec CURRENT_SCHEMA
============================================================================================
    
Avec le paramètre CURRENT_SCHEMA, un user peut se connecter à un autre schéma mais, attention, pas à un autre compte. Il reste lui même mais Oracle va chercher les objets non préfixés par un nom de schéma dans le schéma identifié par CURRENT_SCHEMA.
     SQL> show user
     USER est "SYS" :
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> grant SELECT ANY DICTIONARY to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> create table zz01 (id number);
     SQL> connect zztest;
     SQL> show user
     USER est "ZZTEST"
      
     SQL> ALTER SESSION SET CURRENT_SCHEMA = "ZZTEST02";
     Session modifiee.
      
Voilà, on reste le user U1 mais on accède directement au schéma U2 sans avoir à préfixer les objets par le nom du propriétaire.
     SQL> select username, schemaname from v$session where sid in (select sid from v$mystat);
     USERNAME                       SCHEMANAME
     ------------------------------ ------------------------------
     ZZTEST                         ZZTEST02
      
     SQL> select * from zz01;
     select * from zz01
                   *
     ERREUR a la ligne 1 :
     ORA-00942: Table ou vue inexistante
      
OK, il faut ajouter pour le premier user le droit SELECT sur la table du user U2 :-)
     SQL> connect zztest02
     SQL> grant select on zz01 to zztest;
     SQL> connect zztest
     SQL> ALTER SESSION SET CURRENT_SCHEMA = "ZZTEST02";
      
     SQL> select username, schemaname from v$session where sid in (select sid from v$mystat);
     USERNAME                       SCHEMANAME
     ------------------------------ ------------------------------
     ZZTEST                         ZZTEST02
      
Et voilà, en tant que U1 j'ai accès à un objet du schéma U2 sans avoir à préfixer celui-ci par le nom du propriétaire.
     SQL> select * from zz01;
     aucune ligne selectionnee
      
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
      
      
============================================================================================
Solution 6 : lancer une procédure créée dans un autre schéma
============================================================================================

Plus complexe, comment supprimer le dblink d'un autre schéma? Même SYS ne le peut pas car il est impossible de préfixer le nom d'un dblink par le nom d'un schéma, comme le dit la doc Oracle.
https://docs.oracle.com/database/121/SQLRF/statements_8011.htm#SQLRF01514
"Restriction on Dropping Database Links
You cannot drop a database link in another user's schema, and you cannot qualify dblink with the name of a schema, because periods are permitted in names of database links. Therefore, Oracle Database interprets the entire name, such as ralph.linktosales, as the name of a database link in your schema rather than as a database link named linktosales in the schema ralph."
      
La solution est que U1 crée une procédure dans le schéma U2 et exécute celle-ci : le drop dblink est alors possible puisque la procédure s'exécutera dans le schéma U2!
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> CREATE DATABASE LINK DBLINK01 USING '10.142.128.208:1560/DLAD12';
      
     SQL> connect sys as sysdba
     SQL> show user
     USER est "SYS"
      
     SQL> select OWNER, DB_LINK from dba_db_links where db_link like '%01';
     OWNER           DB_LINK
     ---------------------------
     ZZTEST02        DBLINK01

SYS lui même ne peut pas dropper un dblink d'un autre user car s'il le préfixe par le nom du propriétaire, Oracle considère que le point (.) fait partie du nom de l'objet et le cherche dans le schéma courant, donc celui de SYS... où il n'existe pas!
     SQL> drop database link zztest02.DBLINK01;
     drop database link zztest02.DBLINK01
                        *
     ERREUR a la ligne 1 :
     ORA-02024: lien de base de donnees introuvable
      
On crée une procédure dans le schéma zztest02. Comme on appelle cette procédure depuis un autre schéma, on est donc virtuellement, lors de l'exécution de cette procédure, l'autre user!
     CREATE OR REPLACE PROCEDURE ZZTEST02.PROC_DROP_DBLINK IS
     BEGIN
         execute immediate 'drop database link dblink01';
         EXCEPTION
          WHEN OTHERS THEN
            dbms_output.put_line('PB : SQLCODE ' || to_char(SQLCODE) || ': ' || SQLERRM);
         END PROC_DROP_DBLINK;
     /
      
Attention, le message "Procedure PL/SQL terminee avec succes." ne signifie pas qu'il n'y a pas eu d'erreur mais juste que la procédure s'est exécutée.
     SQL> execute ZZTEST02.PROC_DROP_DBLINK;
     Procedure PL/SQL terminee avec succes.
      
C'est bon!! Plus de dblink dans le schéma U2 :-) On a bien exécuté du code comme étant ce user U2 mais sans connaître son password.
     SQL> select OWNER, DB_LINK from dba_db_links where db_link like '%01';
     aucune ligne selectionnee
      
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
      
      
============================================================================================
Solution 7 : package non documenté DBMS_SYS_SQL
============================================================================================

Avec le package PL/SQL de nom DBMS_SYS_SQL, un user peut exécuter du code en étant un autre user, avec tous ses droits. ATTENTION, ce package est non documenté par Oracle, ne l'utilisez pas en production, on ne connaît pas les effets secondaires :-)
Ce package permet de faire du SQL dynamique, comme DBMS_SQL, mais en ajoutant une subtilité qui est de dire quel user va réellement exécuter le code. Vous avez compris? Un user Stagiaire peut devenir SYS avec ce package! Attention, comme pré-requis il faut avoir le droit EXECUTE dessus et SELECT sur DBA_USERS.
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> grant select on dba_users to zztest;
     SQL> grant execute on dbms_sys_sql to zztest;
      
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> create table zz01 (id number);
      
     SQL> connect zztest
     SQL> select * from zztest02.zz01;
     select * from zztest02.zz01
                            *
     ERREUR a la ligne 1 :
     ORA-00942: Table ou vue inexistante
      
     SQL> connect sys as sysdba
     SQL> show user
     USER est "SYS"
      
     SQL> desc zztest02.zz01;
     Nom                                       NULL ?   Type
     ----------------------------------------- -------- ----------------------------
     ID                                                 NUMBER
      
     SQL> connect zztest
      
     SQL> desc zztest02.zz01
     ERROR:
     ORA-04043: objet zztest02.zz01 inexistant
      
     SQL> SET SERVEROUTPUT ON
            declare
                    v_n_uid number;
                    v_v_sqltext varchar2(100) := 'ALTER TABLE zztest02.zz01 ADD TESTDATE DATE';
                    v_i_myint integer;
       
            begin
                    SELECT USER_ID INTO V_N_UID FROM DBA_USERS WHERE USERNAME = 'SYS';
                    v_i_myint:=sys.dbms_sys_sql.open_cursor()
                   
sys.dbms_sys_sql.parse_as_user(v_i_myint,v_v_sqltext,dbms_sql.native,v_n_uid);
                    sys.dbms_sys_sql.close_cursor(v_i_myint);
     
            exception
                    when others then
                          dbms_output.put_line('failed to execute the specified statement for user: '|| to_char(v_n_uid));
                          dbms_output.put_line(substr(sqlerrm, 1, 100));
       end ;
     /
      
     Procedure PL/SQL terminee avec succes.
      
Pour voir si c'est OK, il faut se connecter comme zztest02 et faire un desc de la table.
Bingo, le test a réussi : U1 a bien modifié un objet appartenant à U2 sans en avoir les droits!
     SQL> connect zztest02
     SQL> desc zz01
     Nom                                       NULL ?   Type
     ----------------------------------------- -------- ----------------------------
     ID                                                 NUMBER
     TESTDATE                                           DATE
      
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
      
     
============================================================================================
Solution 8 : les proxy users
============================================================================================

La fonctionnalité Proxy User de Oracle permet de se connecter comme un autre user sans connaître son password! Regardons cela de plus près.
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest02 identified by TotoTiti31380000;
     SQL> grant dba to zztest02;
     SQL> create user zztest identified by YukioYukio31380000;
     SQL> grant create session to zztest;
      
La commande magique est ici : le user zztest pourra maintenant se connecter comme zztest02.
     SQL> alter user zztest02 grant connect through zztest ;
     
     SQL> connect zztest02
     SQL> CREATE DATABASE LINK DBLINK01 USING '10.142.128.208:1560/DLAD12';
     SQL> select OWNER, DB_LINK from dba_db_links where db_link like '%01';
     OWNER         DB_LINK
     ----------------------
     ZZTEST02      DBLINK01
      
On se connecte au compte zztest02, sans connaître son password, à partir du compte zztest.
Attention à la syntaxe : compte1[compte2]/password compte 1
     SQL> connect zztest[zztest02]/YukioYukio31380000;
     SQL> show user
     USER est "ZZTEST02"
     SQL> drop database link dblink01;
     SQL> select OWNER, DB_LINK from dba_db_links where db_link like '%01';
     aucune ligne selectionnee
      
     SQL> connect / as sysdba
     SQL> alter user zztest02 revoke connect through zztest ;
     
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
     

============================================================================================
Solution 9 : ALTER USER IDENTIFIED BY VALUES - changer le mot de passe temporairement d'un user
============================================================================================

Un user se connecte comme un autre user en changeant « temporairement » son mot de passe car il ne connaît pas son password.
L’ordre SQL est ALTER USER user IDENTIFIED BY VALUES "champ spare4 ou champ password" ;
Je teste sur une autre base, ne soyez pas surpris :-)
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> CREATE DATABASE LINK DBLINK01 USING '127.0.0.1/ORCL';
      
     SQL> connect SYS/oracle@ORCL as sysdba
     SQL> show user
     USER est "SYS"
    
     SQL> desc user$
      Name                       Null?    Type
      ----------------------------------------- ---------
      USER#                       NOT NULL NUMBER
      NAME                       NOT NULL VARCHAR2(128)
      TYPE#                       NOT NULL NUMBER
      PASSWORD                        VARCHAR2(4000)
      DATATS#                   NOT NULL NUMBER
      TEMPTS#                   NOT NULL NUMBER
      CTIME                       NOT NULL DATE
      PTIME                            DATE
      EXPTIME                        DATE
      LTIME                            DATE
      RESOURCE$                   NOT NULL NUMBER
      AUDIT$                         VARCHAR2(38)
      DEFROLE                   NOT NULL NUMBER
      DEFGRP#                        NUMBER
      DEFGRP_SEQ#                        NUMBER
      ASTATUS                   NOT NULL NUMBER
      LCOUNT                    NOT NULL NUMBER
      DEFSCHCLASS                        VARCHAR2(128)
      EXT_USERNAME                        VARCHAR2(4000)
      SPARE1                         NUMBER
      SPARE2                         NUMBER
      SPARE3                         NUMBER
      SPARE4                         VARCHAR2(1000)
      SPARE5                         VARCHAR2(1000)
      SPARE6                         DATE
      SPARE7                         VARCHAR2(4000)
      SPARE8                         VARCHAR2(4000)
      SPARE9                         NUMBER
      SPARE10                        NUMBER
      SPARE11                        TIMESTAMP(6)

Avant de changer le mot de passe du user, il faut avoir bien pris soin de récupérer son mot de passe crypté avant.      
     SQL> SELECT nvl(password, 'RIEN'), spare4 FROM user$ WHERE name = 'ZZTEST02';
     NVL(PASSWORD,'RIEN')    SPARE4
     ------------------------------------------
     RIEN                  S:B35FA9749EDA9A6C3CF3A198D2B8BD0F1DCB10101CEEFC48801700F8AFA0;T:B72307284B19A51A5E4BA7596DC3BDBCC99CF89D12FD23E662343EAA26A93B226DD7E60062890EE61E89D1032176311859FB0954CD781C1AC2536ECDBC25E12874F5EC6345D3E69C843F08FB63852026
     
On saisit un nouveau mot de passe pour U2 et on se contacte à son compte pour dropper le dblink.
     SQL> alter user ZZTEST02 identified by toto;
     SQL> connect zztest02/toto@orcl;
     SQL> drop database link dblink01;
     SQL> connect SYS/oracle@ORCL as sysdba

Et maintenant on remet l'ancien password du user en utilisant la valeur cryptée.    
     SQL> alter user zztest02 identified by values 'S:B35FA9749EDA9A6C3CF3A198D2B8BD0F1DCB10101CEEFC48801700F8AFA0;T:B72307284B19A51A5E4BA7596DC3BDBCC99CF89D12FD23E662343EAA26A93B226DD7E60062890EE61E89D1032176311859FB0954CD781C1AC2536ECDBC25E12874F5EC6345D3E69C843F08FB63852026';
     User altered.
    
C'est bon, l'ancien mot de passe est actif.
     SQL> connect zztest02/toto@ORCL;
     ERROR:
     ORA-01017: invalid username/password; logon denied
     Warning: You are no longer connected to ORACLE.
     
     SQL> connect zztest02/YukioYukio31380000@ORCL;
     Connected.
     SQL> show user
     USER is "ZZTEST02"
     SQL> select OWNER, DB_LINK from dba_db_links where db_link like '%01';
     no rows selected
     
     SQL> drop user zztest;
     SQL> drop user zztest02 cascade;
     
    
============================================================================================
Solution 10 : fonction système KUPP$PROC
============================================================================================

La fonction KUPP$PROC permet de  changer de user; cette astuce fonctionnait en 11 mais visiblement pas en 12.1.
https://vigilance.fr/vulnerabilite/Oracle-DB-elevation-de-privileges-via-BECOME-USER-7339
Les rôles DBA et IMP_FULL_DATABASE possèdent le privilège "BECOME USER" qui permet de changer d'identité. Ce changement d'identité se fait via sys.kupp$proc.change_user().
     SQL> show user
     USER est "SYS"
      
     SQL> create user zztest identified by TotoTiti31380000 ;
     SQL> grant create session to zztest;
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;
     SQL> connect zztest02
     SQL> CREATE DATABASE LINK DBLINK01 USING '127.0.0.1/ORCL';
      
     SQL> connect SYS/oracle@ORCL as sysdba
     SQL> show user
     USER est "SYS"
     
     SQL> exec sys.kupp$proc.change_user('ZZTEST02');
     BEGIN sys.kupp$proc.change_user('ZZTEST02'); END;
     
     *
     ERROR at line 1:
     ORA-31625: Schema ZZTEST02 is needed to import this object, but is unaccessible
     ORA-06512: at "SYS.KUPP$PROC", line 45
     ORA-06512: at "SYS.KUPP$PROC", line 1006
     ORA-06512: at line 1
     
Si message d'erreur, exécuter la requete ci-dessous et recommencer.
     SQL> select sys.kupp$proc.disable_multiprocess from dual;
     DISABLE_MULTIPROCESS
     --------------------
                1
     
Hum, visiblement cela a été corrigé en 12.1.           
     SQL> exec sys.kupp$proc.change_user('ZZTEST02');
     BEGIN sys.kupp$proc.change_user('ZZTEST02'); END;
     *
     ERROR at line 1:
     ORA-31625: Schema ZZTEST02 is needed to import this object, but is unaccessible
     ORA-06512: at "SYS.KUPP$PROC", line 45
     ORA-06512: at "SYS.KUPP$PROC", line 1006
     ORA-06512: at line 1
     
On parle d'une correction ici http://www.red-database-security.com/wp/best_of_oracle_security_2011.pdf mais c'est encore KO.
     SQL> show user
     USER is "SYS"
     
     SQL> create or replace PROCEDURE  CHANGE_USER_INT (USERNAME IN VARCHAR2, MP_ENABLED IN BINARY_INTEGER) IS
     EXTERNAL NAME "kuppchus" LANGUAGE C LIBRARY KUPP_PROC_LIB WITH CONTEXT
     PARAMETERS ( CONTEXT, USERNAME STRING, USERNAME INDICATOR SB2, MP_ENABLED SB4 );
     /
     Procedure created.
      
     SQL> exec CHANGE_USER_INT('ZZTEST02',0);
     BEGIN CHANGE_USER_INT('ZZTEST02',0); END;
     *
     ERROR at line 1:
     ORA-31625: Schema ZZTEST02 is needed to import this object, but is unaccessible
     ORA-06512: at "SYS.CHANGE_USER_INT", line 1
     ORA-06512: at line 1

OK, je laisse tomber, je n'arrive pas sur une 12.1 à effectuer cette manip.
     
     
============================================================================================
Solution 11 : Privilège "BECOME USER"
============================================================================================

Les rôles DBA et IMP_FULL_DATABASE possèdent le privilège "BECOME USER" qui permet de changer d'identité.  
En effet, quand le user U1 veut faire un export/import d'un autre schéma ou de la base entière, il doit accéder aux schémas autres que le sien.
Mais je ne pourrai pas tester un changement de user via ce privilège car impossible de trouver un test simple et clair.


============================================================================================
Solution 12 : Le compte PUBLIC
============================================================================================
    
On arrive au bout de notre voyage, ce sera avec le user très spécial PUBLIC.
Il faut savoir que pour Oracle ce user est en réalité un groupe d'utilisateurs et même un rôle. Et comme chaque user appartient à ce groupe, un user peut accéder naturellement aux objets de ce user; c'est l'objet de cet article.

PUBLIC est aussi un rôle caché; il est filtré dans la définition de DBA_ROLES. Attention, pour avoir la définition d'un objet du schéma SYS, il faut se connecter au CDB$ROOT si la base 12 est en mode CDB. Vous noterez qu'un rôle est stocké dans la table des users; de là à dire qu'un rôle est un user spécial...
     SQL> create user zztest identified by TotoTiti31380000;
     SQL> grant dba to zztest;
     SQL> connect SYS/oracle@ORCL as sysdba
     SQL> create user zztest02 identified by YukioYukio31380000;
     SQL> grant dba to zztest02;

     SQL> show user

     USER is "SYS"


     SQL> alter session set container=CDB$ROOT;

     Session altered.

Voyons voir un peu ce fameux user PUBLIC. Ah oui, il est juste après SYS, avec le code 1.
     SQL> select user#, name from user$ where user# < 11;
     USER# NAME
     ---------- --
     0 SYS
     1 PUBLIC
     2 CONNECT
     3 RESOURCE
     4 DBA
     5 PDB_DBA
     6 AUDIT_ADMIN
     7 AUDIT_VIEWER
     8 AUDSYS
     9 SYSTEM
    10 SELECT_CATALOG_ROLE

    11 rows selected.

     SQL> select dbms_metadata.get_ddl('VIEW', 'DBA_ROLES') from dual;
     DBMS_METADATA.GET_DDL('VIEW','DBA_ROLES')
     --------------------------------------------------------------------------------
     CREATE OR REPLACE FORCE NONEDITIONABLE VIEW "SYS"."DBA_ROLES" ("ROLE", "ROLE_I
     D", "PASSWORD_REQUIRED", "AUTHENTICATION_TYPE", "COMMON", "ORACLE_MAINTAINED", "
     INHERITED", "IMPLICIT") AS
       select name, user#,
              decode(password, null,
                  decode(spare4, null, 'NO',
                    decode(REGEXP_INSTR(spare4, '[ST]:'), 0, 'NO',
                       'YES')),
                       'EXTERNAL',    'EXTERNAL',
                       'GLOBAL',      'GLOBAL',
                       'YES'),
              decode(password, null,
                  decode(spare4, null, 'NONE',
                    decode(REGEXP_INSTR(spare4, '[ST]:'), 0, 'NONE',
                       'PASSWORD')),
                       'EXTERNAL',    'EXTERNAL',
                       'GLOBAL',      'GLOBAL',
                       'APPLICATION', 'APPLICATION',
                       'PASSWORD'),
              decode(bitand(spare1, 4224), 0, 'NO', 'YES'),
              decode(bitand(spare1, 256), 256, 'Y', 'N'),
              decode(bitand(spare1, 4224),
                 128, decode(SYS_CONTEXT('USERENV', 'CON_ID'),
                     1, 'NO', 'YES'),
                 4224, decode(SYS_CONTEXT('USERENV', 'IS_APPLICATION_PDB'),
                      'YES', 'YES', 'NO'),
                 'NO'),
              decode(bitand(spare1, 32768), 32768, 'YES', 'NO')
     from  user$
     where type# = 0 and name not in ('PUBLIC', '_NEXT_USER')
   
Je  crée un synonyme publique, qui, comme son nom l'indique, va dans le schéma PUBLIC.          
     SQL> connect zztest;
     SQL> create table zz01(id date);
     SQL> create public synonym PUB_SYN_ZZ01 for zz01;
     
     SQL> select owner from dba_objects where object_name = 'PUB_SYN_ZZ01';
     OWNER
     ------------------------
     PUBLIC

Ici, Oracle regarde dans le schéma courant, pas dans PUBLIC puisque l'objet zz01 existe dans le schéma du user connecté.    
     SQL> select * from zz01;
     no rows selected

Les users U1 et U2 ont bien accès au synonyme du schéma PUBLIC, sans faire quoi que ce soit de spécial. Attention à bien encadrer le schéma PUBLIC par des guillemets sinon, mesage d'erreur!     
     SQL> select * from "PUBLIC".PUB_SYN_ZZ01;
     no rows selected

Nous voyons ici que le user U2 accède de deux façons différentes au schéma PUBLIC : soit en préfixant le nom d'un objet par le nom PUBLIC soit en ne le préfixant pas et Oracle va chercher à deux endroits différents, dans le schéma courant et, si l'objet n'existe pas, dans le schéma PUBLIC puisque chaque user y a accès.     
     SQL> connect zztest02
     SQL> select * from "PUBLIC".PUB_SYN_ZZ01;
     no rows selected
     
     SQL> select * from PUB_SYN_ZZ01;
     no rows selected


Posté par David DBA à 10:33 - - Permalien [#]
Tags : ,